in source/hack_parallel/hack_parallel/heap/hh_shared.c [1498:1593]
CAMLprim value hh_collect(void) {
// NOTE: explicitly do NOT call CAMLparam or any of the other functions/macros
// defined in caml/memory.h .
// This function takes a boolean and returns unit.
// Those are both immediates in the OCaml runtime.
assert_master();
assert_allow_removes();
// Step 1: Walk the hashtbl entries, which are the roots of our marking pass.
for (size_t i = 0; i < hashtbl_size; i++) {
// Skip empty slots
if (hashtbl[i].addr == NULL) {
continue;
}
// No workers should be writing at the moment. If a worker died in the
// middle of a write, that is also very bad
assert(hashtbl[i].addr != HASHTBL_WRITE_IN_PROGRESS);
// The hashtbl addr will be wrong after we relocate the heap entry, but we
// don't know where the heap entry will relocate to yet. We need to first
// move the heap entry, then fix up the hashtbl addr.
//
// We accomplish this by storing the heap header in the now useless addr
// field and storing a pointer to the addr field where the header used to
// be. Then, after moving the heap entry, we can follow the pointer to
// restore our original header and update the addr field to our relocated
// address.
//
// This is all super unsafe and only works because we constrain the size of
// an hh_header_t struct to the size of a pointer.
// Location of the addr field (8 bytes) in the hashtable
char** hashtbl_addr = (char**)&hashtbl[i].addr;
// Location of the header (8 bytes) in the heap
char* heap_addr = (char*)hashtbl[i].addr;
// Swap
hh_header_t header = *(hh_header_t*)heap_addr;
*(hh_header_t*)hashtbl_addr = header;
*(uintptr_t*)heap_addr = (uintptr_t)hashtbl_addr;
}
// Step 2: Walk the heap and relocate entries, updating the hashtbl to point
// to relocated addresses.
// Pointer to free space in the heap where moved values will move to.
char* dest = heap_init;
// Pointer that walks the heap from bottom to top.
char* src = heap_init;
size_t aligned_size;
hh_header_t header;
while (src < *heap) {
if (*(uint64_t*)src & 1) {
// If the lsb is set, this is a header. If it's a header, that means the
// entry was not marked in the first pass and should be collected. Don't
// move dest pointer, but advance src pointer to next heap entry.
header = *(hh_header_t*)src;
aligned_size = ALIGNED(Heap_entry_total_size(header));
} else {
// If the lsb is 0, this is a pointer to the addr field of the hashtable
// element, which holds the header bytes. This entry is live.
char* hashtbl_addr = *(char**)src;
header = *(hh_header_t*)hashtbl_addr;
aligned_size = ALIGNED(Heap_entry_total_size(header));
// Fix the hashtbl addr field to point to our new location and restore the
// heap header data temporarily stored in the addr field bits.
*(uintptr_t*)hashtbl_addr = (uintptr_t)dest;
*(hh_header_t*)src = header;
// Move the entry as far to the left as possible.
memmove(dest, src, aligned_size);
dest += aligned_size;
}
src += aligned_size;
}
// TODO: Space between dest and *heap is unused, but will almost certainly
// become used again soon. Currently we will never decommit, which may cause
// issues when there is memory pressure.
//
// If the kernel supports it, we might consider using madvise(MADV_FREE),
// which allows the kernel to reclaim the memory lazily under pressure, but
// would not force page faults under healthy operation.
*heap = dest;
*wasted_heap_size = 0;
return Val_unit;
}