Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial changes to garbage collection #1174

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2f69066
use std::swap
stevenmeker Nov 19, 2024
fa25f36
make megabye_malloc() private member function; added data members cur…
stevenmeker Nov 19, 2024
a94b6a9
make ptr_diff() a simple subtraction
stevenmeker Nov 20, 2024
259290d
reimplement class arena
stevenmeker Nov 21, 2024
41a9975
deleted decl for megabyte_malloc(), fresh_block(), code cleaning
stevenmeker Nov 21, 2024
30a0b71
added initialize_semispace() to avoid relying on UB for initializatio…
stevenmeker Nov 22, 2024
62a8ee7
keep track of notional number of blocks in each semispace
stevenmeker Nov 22, 2024
3d5ebe8
more accurate simulation of old collection policy; deleted slow_alloc…
stevenmeker Nov 23, 2024
31321ea
used just a pointer addition for move_ptr()
stevenmeker Nov 25, 2024
f3972a9
made move_ptr() inline and added check for end of allocation
stevenmeker Nov 26, 2024
d8ef882
disable garbage collection to try and isolate bug
stevenmeker Nov 26, 2024
4318df9
re-enabled garbage collection - inifinte loop problem has disappeared
stevenmeker Nov 26, 2024
7a2df99
fixed formatting
stevenmeker Nov 27, 2024
a75b285
deleted MEM_BLOCK_START; removed corner case from collector
stevenmeker Nov 28, 2024
e27ec1b
fix call to initialize_semispace()
stevenmeker Dec 3, 2024
6af3f6b
deleted dead code youngspace_size() and arena_size()
stevenmeker Dec 4, 2024
bfba1ca
replaced memory_block_header and associated functionality
stevenmeker Dec 4, 2024
8e31c96
fix formatting; clean code and comments
stevenmeker Dec 4, 2024
9f2e3aa
fix formatting
stevenmeker Dec 4, 2024
98b8404
make arena_end_ptr() return char *
stevenmeker Dec 5, 2024
9833230
get rid of num_blocks and track touched bytes instead
stevenmeker Dec 5, 2024
6d12050
move tripwire in 1 MB chucks
stevenmeker Dec 6, 2024
dc9388f
revise tripwire calculations
stevenmeker Dec 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/runtime/alloc.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ extern "C" {

char youngspace_collection_id(void);
char oldspace_collection_id(void);
size_t youngspace_size(void);
//size_t youngspace_size(void);

// allocates exactly requested bytes into the young generation
void *kore_alloc(size_t requested);
Expand Down
180 changes: 127 additions & 53 deletions include/runtime/arena.h
Original file line number Diff line number Diff line change
@@ -1,67 +1,69 @@
#ifndef ARENA_H
#define ARENA_H

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <sys/types.h>
#include <utility>

#include "runtime/alloc.h"

extern "C" {

size_t const HYPERBLOCK_SIZE = (size_t)BLOCK_SIZE * 1024 * 1024;
size_t const margin = 1024 * 1024;

// An arena can be used to allocate objects that can then be deallocated all at
// once.
class arena {
public:
arena(char id)
: allocation_semispace_id(id) { }
: allocation_semispace_id(id) {
initialize_semispace();
}

// Allocates the requested number of bytes as a contiguous region and returns a
// pointer to the first allocated byte.
// If called with requested size greater than the maximun single allocation
// size, the space is allocated in a general (not garbage collected pool).
void *kore_arena_alloc(size_t requested);

// Returns the address of the first byte that belongs in the given arena.
// Returns 0 if nothing has been allocated ever in that arena.
char *arena_start_ptr() const;
// Returns nullptr if nothing has been allocated ever in that arena.
char *arena_start_ptr() const { return current_addr_ptr; }

// Returns a pointer to a location holding the address of last allocated
// byte in the given arena plus 1.
// This address is 0 if nothing has been allocated ever in that arena.
char **arena_end_ptr();

// return the total number of allocatable bytes currently in the arena in its
// active semispace.
size_t arena_size() const;
// This address is nullptr if nothing has been allocated ever in that arena.
char *arena_end_ptr() { return allocation_ptr; }

// Clears the current allocation space by setting its start back to its first
// block. It is used during garbage collection to effectively collect all of the
// arena.
// arena. Resets the tripwire.
void arena_clear();

// Resizes the last allocation as long as the resize does not require a new
// block allocation.
// Returns the address of the byte following the last newlly allocated byte when
// the resize succeeds, returns 0 otherwise.
void *arena_resize_last_alloc(ssize_t increase);
// Resizes the last allocation.
// Returns the address of the byte following the last newly allocated byte.
void *arena_resize_last_alloc(ssize_t increase) {
return (allocation_ptr += increase);
}

// Returns the given arena's current collection semispace ID.
// Each arena has 2 semispace IDs one equal to the arena ID and the other equal
// to the 1's complement of the arena ID. At any time one of these semispaces
// is used for allocation and the other is used for collection.
char get_arena_collection_semispace_id() const;
char get_arena_collection_semispace_id() const {
return ~allocation_semispace_id;
}

// Exchanges the current allocation and collection semispaces and clears the new
// current allocation semispace by setting its start back to its first block.
// It is used before garbage collection.
void arena_swap_and_clear();

// Given two pointers to objects allocated in the same arena, return the number
// of bytes they are separated by within the virtual block of memory represented
// by the blocks of that arena. This difference will include blocks containing
// sentinel bytes. Undefined behavior will result if the pointers belong to
// different arenas.
static ssize_t ptr_diff(char *ptr1, char *ptr2);
// of bytes they are apart. Undefined behavior will result if the pointers
// don't belong to the same arena
static ssize_t ptr_diff(char *ptr1, char *ptr2) { return ptr1 - ptr2; }

// Given a starting pointer to an address allocated in an arena and a size in
// bytes, this function returns a pointer to an address allocated in the
Expand All @@ -70,44 +72,54 @@ class arena {
// 1st argument: the starting pointer
// 2nd argument: the size in bytes to add to the starting pointer
// 3rd argument: the address of last allocated byte in the arena plus 1
// Return value: the address allocated in the arena after size bytes from the
// starting pointer, or 0 if this is equal to the 3rd argument.
static char *move_ptr(char *ptr, size_t size, char const *arena_end_ptr);
// Return value: starting pointer + size unless this points to unallocated space
// in which case nullptr is returned
static char *move_ptr(char *ptr, size_t size, char const *end_ptr) {
char *next_ptr = ptr + size;
return (next_ptr == end_ptr) ? nullptr : next_ptr;
}

// Returns the ID of the semispace where the given address was allocated.
// The behavior is undefined if called with an address that has not been
// allocated within an arena.
static char get_arena_semispace_id_of_object(void *ptr);

private:
struct memory_block_header {
char *next_block;
char semispace;
};

void fresh_block();
static memory_block_header *mem_block_header(void *ptr);

// helper function for `kore_arena_alloc`. Do not call directly.
void *do_alloc_slow(size_t requested);

char *first_block; // beginning of first block
char *block; // where allocations are being made in current block
char *block_start; // start of current block
char *block_end; // 1 past end of current block
char *first_collection_block; // beginning of other semispace
size_t num_blocks; // number of blocks in current semispace
size_t num_collection_blocks; // number of blocks in other semispace
void initialize_semispace();
//
// Current semispace where allocations are being made.
//
char *current_addr_ptr; // pointer to start of current address space
char *allocation_ptr; // next available location in current semispace
char *tripwire; // allocating past this triggers slow allocation
size_t nr_touched_bytes; // number of bytes that are touched
char allocation_semispace_id; // id of current semispace
//
// Semispace where allocations will be made during and after garbage collect.
// We only track the start of the address space and the number of bytes
// that have been touched.
//
char *collection_addr_ptr = nullptr;
size_t nr_collection_touched_bytes = 0;
};

inline char arena::get_arena_semispace_id_of_object(void *ptr) {
//
// We don't have to deal with the "1 past the end of block" case because
// a valid pointer will always point into our hyperblock - we will never return
// an allocation anywhere near the end of our hyperblock.
//
// Set the low bits to 1 to get the address of the last byte in the hyperblock.
//
uintptr_t end_address
= reinterpret_cast<uintptr_t>(ptr) | (HYPERBLOCK_SIZE - 1);
return *reinterpret_cast<char *>(end_address);
}

// Macro to define a new arena with the given ID. Supports IDs ranging from 0 to
// 127.
#define REGISTER_ARENA(name, id) static thread_local arena name(id)

#define MEM_BLOCK_START(ptr) \
((char *)(((uintptr_t)(ptr)-1) & ~(BLOCK_SIZE - 1)))

#ifdef __MACH__
//
// thread_local disabled for Apple
Expand All @@ -120,16 +132,78 @@ extern thread_local bool time_for_collection;
size_t get_gc_threshold();

inline void *arena::kore_arena_alloc(size_t requested) {
if (block + requested > block_end) {
return do_alloc_slow(requested);
if (allocation_ptr + requested >= tripwire) {
//
// We got close to or past the last location accessed in this address range so far,
// depending on the requested size and tripwire setting. This triggers a garbage
// collect when allowed.
//
time_for_collection = true;
//
// We move the tripwire to 1 past the end of our hyperblock so that we have
// a well defined comparison that will always be false until the next arena swap.
//
tripwire = current_addr_ptr + HYPERBLOCK_SIZE;
}
void *result = block;
block += requested;
void *result = allocation_ptr;
allocation_ptr += requested;
MEM_LOG(
"Allocation at %p (size %zd), next alloc at %p (if it fits)\n", result,
requested, block);
"Allocation at %p (size %zd), next alloc at %p\n", result, requested,
block);
return result;
}

inline void arena::arena_clear() {
//
// We set the allocation pointer to the first available address.
//
allocation_ptr = arena_start_ptr();
//
// Put the tripwire pass the end of the blocks we've touched.
size_t nr_touched_blocks = (nr_touched_bytes == 0) ? 0 : (nr_touched_bytes - 1) / BLOCK_SIZE + 1;
tripwire = current_addr_ptr + nr_touched_bytes * BLOCK_SIZE;
//
// If we've touched enough blocks, pull back by margin, so we schedule a collection,
// before we need to touch virgin address space.
//
if (nr_touched_blocks >= get_gc_threshold())
tripwire -= margin;
//
// If the number of blocks we've touched is >= threshold, we want to trigger
// a garbage collection if we get within 1 block of the end of this area.
// Otherwise we only want to generate a garbage collect if we allocate off the
// end of this area.
//
//tripwire = current_addr_ptr
// + (num_blocks - (num_blocks >= get_gc_threshold())) * BLOCK_SIZE;
}

inline void arena::arena_swap_and_clear() {
//
// Calculate how many bytes we handled out from the current semispace
// before the swap.
//
size_t nr_allocated_bytes = allocation_ptr - current_addr_ptr;
//
// If this is greater than the number of bytes we touched on previous cycles,
// update the number of touched bytes.
//
if (nr_allocated_bytes > nr_touched_bytes)
nr_touched_bytes = nr_allocated_bytes;
//
// Swap the two key pieces of information with the other semispace and update the id.
//
std::swap(current_addr_ptr, collection_addr_ptr);
std::swap(nr_touched_bytes, nr_collection_touched_bytes);
allocation_semispace_id = ~allocation_semispace_id;
//
// The semispace that is now current must either be initialized or cleared.
//
if (current_addr_ptr == nullptr)
initialize_semispace();
else
arena_clear();
}

}
#endif // ARENA_H
Loading
Loading