mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-06-26 21:20:30 +02:00
525 lines
17 KiB
C
525 lines
17 KiB
C
#ifndef LARGE_OBJECT_SPACE_H
|
|
#define LARGE_OBJECT_SPACE_H
|
|
|
|
#include <pthread.h>
|
|
#include <malloc.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "gc-assert.h"
|
|
#include "gc-ref.h"
|
|
#include "gc-conservative-ref.h"
|
|
#include "gc-trace.h"
|
|
#include "address-map.h"
|
|
#include "address-set.h"
|
|
#include "background-thread.h"
|
|
#include "freelist.h"
|
|
|
|
// A mark-sweep space with generational support.
|
|
|
|
struct gc_heap;
|
|
|
|
enum large_object_state {
|
|
LARGE_OBJECT_NURSERY = 0,
|
|
LARGE_OBJECT_MARKED_BIT = 1,
|
|
LARGE_OBJECT_MARK_TOGGLE_BIT = 2,
|
|
LARGE_OBJECT_MARK_0 = LARGE_OBJECT_MARKED_BIT,
|
|
LARGE_OBJECT_MARK_1 = LARGE_OBJECT_MARKED_BIT | LARGE_OBJECT_MARK_TOGGLE_BIT
|
|
};
|
|
|
|
struct large_object {
|
|
uintptr_t addr;
|
|
size_t size;
|
|
};
|
|
struct large_object_node;
|
|
struct large_object_live_data {
|
|
uint8_t mark;
|
|
enum gc_trace_kind trace;
|
|
};
|
|
struct large_object_dead_data {
|
|
uint8_t age;
|
|
struct large_object_node **prev;
|
|
struct large_object_node *next;
|
|
};
|
|
struct large_object_data {
|
|
uint8_t is_live;
|
|
union {
|
|
struct large_object_live_data live;
|
|
struct large_object_dead_data dead;
|
|
};
|
|
};
|
|
|
|
#define SPLAY_TREE_PREFIX large_object_
|
|
typedef struct large_object large_object_key_span;
|
|
typedef uintptr_t large_object_key;
|
|
typedef struct large_object_data large_object_value;
|
|
static inline int
|
|
large_object_compare(uintptr_t addr, struct large_object obj) {
|
|
if (addr < obj.addr) return -1;
|
|
if (addr - obj.addr < obj.size) return 0;
|
|
return 1;
|
|
}
|
|
static inline uintptr_t
|
|
large_object_span_start(struct large_object obj) {
|
|
return obj.addr;
|
|
}
|
|
#include "splay-tree.h"
|
|
|
|
DEFINE_FREELIST(large_object_freelist, sizeof(uintptr_t) * 8 - 1, 2,
|
|
struct large_object_node*);
|
|
|
|
struct large_object_space {
|
|
// Lock for object_map, quarantine, nursery, and marked.
|
|
pthread_mutex_t lock;
|
|
// Lock for object_tree.
|
|
pthread_mutex_t object_tree_lock;
|
|
// Lock for remembered_edges.
|
|
pthread_mutex_t remembered_edges_lock;
|
|
// Locking order: You must hold the space lock when taking
|
|
// object_tree_lock. Take no other lock while holding
|
|
// object_tree_lock. remembered_edges_lock is a leaf; take no locks
|
|
// when holding it.
|
|
|
|
// The value for a large_object_node's "mark" field indicating a
|
|
// marked object; always nonzero, and alternating between two values
|
|
// at every major GC.
|
|
uint8_t marked;
|
|
|
|
// Splay tree of objects, keyed by <addr, size> tuple. Useful when
|
|
// looking up object-for-address.
|
|
struct large_object_tree object_tree;
|
|
|
|
// Hash table of objects, where values are pointers to splay tree
|
|
// nodes. Useful when you have the object address and just want to
|
|
// check something about it (for example its size).
|
|
struct address_map object_map;
|
|
|
|
// In generational configurations, we collect all allocations in the
|
|
// last cycle into the nursery.
|
|
struct address_map nursery;
|
|
|
|
// Size-segregated freelist of dead objects. Allocations are first
|
|
// served from the quarantine freelist before falling back to the OS
|
|
// if needed. Collected objects spend a second or two in quarantine
|
|
// before being returned to the OS. This is an optimization to avoid
|
|
// mucking about too much with the TLB and so on.
|
|
struct large_object_freelist quarantine;
|
|
|
|
// Set of edges from lospace that may reference young objects,
|
|
// possibly in other spaces.
|
|
struct address_set remembered_edges;
|
|
|
|
size_t page_size;
|
|
size_t page_size_log2;
|
|
size_t total_pages;
|
|
size_t free_pages;
|
|
size_t live_pages_at_last_collection;
|
|
size_t pages_freed_by_last_collection;
|
|
int synchronous_release;
|
|
};
|
|
|
|
static size_t
|
|
large_object_space_npages(struct large_object_space *space, size_t bytes) {
|
|
return (bytes + space->page_size - 1) >> space->page_size_log2;
|
|
}
|
|
|
|
static size_t
|
|
large_object_space_size_at_last_collection(struct large_object_space *space) {
|
|
return space->live_pages_at_last_collection << space->page_size_log2;
|
|
}
|
|
|
|
static inline int
|
|
large_object_space_contains_with_lock(struct large_object_space *space,
|
|
struct gc_ref ref) {
|
|
return address_map_contains(&space->object_map, gc_ref_value(ref));
|
|
}
|
|
|
|
static inline int
|
|
large_object_space_contains(struct large_object_space *space,
|
|
struct gc_ref ref) {
|
|
pthread_mutex_lock(&space->lock);
|
|
int ret = large_object_space_contains_with_lock(space, ref);
|
|
pthread_mutex_unlock(&space->lock);
|
|
return ret;
|
|
}
|
|
|
|
static inline struct gc_ref
|
|
large_object_space_object_containing_edge(struct large_object_space *space,
|
|
struct gc_edge edge) {
|
|
pthread_mutex_lock(&space->object_tree_lock);
|
|
struct large_object_node *node =
|
|
large_object_tree_lookup(&space->object_tree, gc_edge_address(edge));
|
|
uintptr_t addr = (node && node->value.is_live) ? node->key.addr : 0;
|
|
pthread_mutex_unlock(&space->object_tree_lock);
|
|
return gc_ref(addr);
|
|
}
|
|
|
|
static void
|
|
large_object_space_start_gc(struct large_object_space *space, int is_minor_gc) {
|
|
// Take the space lock to prevent
|
|
// large_object_space_process_quarantine from concurrently mutating
|
|
// the object map.
|
|
pthread_mutex_lock(&space->lock);
|
|
if (!is_minor_gc) {
|
|
space->marked ^= LARGE_OBJECT_MARK_TOGGLE_BIT;
|
|
space->live_pages_at_last_collection = 0;
|
|
}
|
|
}
|
|
|
|
static inline struct gc_trace_plan
|
|
large_object_space_object_trace_plan(struct large_object_space *space,
|
|
struct gc_ref ref) {
|
|
uintptr_t node_bits =
|
|
address_map_lookup(&space->object_map, gc_ref_value(ref), 0);
|
|
GC_ASSERT(node_bits);
|
|
struct large_object_node *node = (struct large_object_node*) node_bits;
|
|
switch (node->value.live.trace) {
|
|
case GC_TRACE_PRECISELY:
|
|
return (struct gc_trace_plan){ GC_TRACE_PRECISELY, };
|
|
case GC_TRACE_NONE:
|
|
return (struct gc_trace_plan){ GC_TRACE_NONE, };
|
|
#if GC_CONSERVATIVE_TRACE
|
|
case GC_TRACE_CONSERVATIVELY: {
|
|
return (struct gc_trace_plan){ GC_TRACE_CONSERVATIVELY, node->key.size };
|
|
}
|
|
// No large ephemerons.
|
|
#endif
|
|
default:
|
|
GC_CRASH();
|
|
}
|
|
}
|
|
|
|
static uint8_t*
|
|
large_object_node_mark_loc(struct large_object_node *node) {
|
|
GC_ASSERT(node->value.is_live);
|
|
return &node->value.live.mark;
|
|
}
|
|
|
|
static uint8_t
|
|
large_object_node_get_mark(struct large_object_node *node) {
|
|
return atomic_load_explicit(large_object_node_mark_loc(node),
|
|
memory_order_acquire);
|
|
}
|
|
|
|
static struct large_object_node*
|
|
large_object_space_lookup(struct large_object_space *space, struct gc_ref ref) {
|
|
return (struct large_object_node*) address_map_lookup(&space->object_map,
|
|
gc_ref_value(ref),
|
|
0);
|
|
}
|
|
|
|
static int
|
|
large_object_space_mark(struct large_object_space *space, struct gc_ref ref) {
|
|
struct large_object_node *node = large_object_space_lookup(space, ref);
|
|
if (!node)
|
|
return 0;
|
|
GC_ASSERT(node->value.is_live);
|
|
|
|
uint8_t *loc = large_object_node_mark_loc(node);
|
|
uint8_t mark = atomic_load_explicit(loc, memory_order_relaxed);
|
|
do {
|
|
if (mark == space->marked)
|
|
return 0;
|
|
} while (!atomic_compare_exchange_weak_explicit(loc, &mark, space->marked,
|
|
memory_order_acq_rel,
|
|
memory_order_acquire));
|
|
|
|
size_t pages = node->key.size >> space->page_size_log2;
|
|
atomic_fetch_add(&space->live_pages_at_last_collection, pages);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
large_object_space_is_marked(struct large_object_space *space,
|
|
struct gc_ref ref) {
|
|
struct large_object_node *node = large_object_space_lookup(space, ref);
|
|
if (!node)
|
|
return 0;
|
|
GC_ASSERT(node->value.is_live);
|
|
|
|
return atomic_load_explicit(large_object_node_mark_loc(node),
|
|
memory_order_acquire) == space->marked;
|
|
}
|
|
|
|
static int
|
|
large_object_space_is_survivor(struct large_object_space *space,
|
|
struct gc_ref ref) {
|
|
GC_ASSERT(large_object_space_contains(space, ref));
|
|
pthread_mutex_lock(&space->lock);
|
|
int old = large_object_space_is_marked(space, ref);
|
|
pthread_mutex_unlock(&space->lock);
|
|
return old;
|
|
}
|
|
|
|
static int
|
|
large_object_space_remember_edge(struct large_object_space *space,
|
|
struct gc_ref obj,
|
|
struct gc_edge edge) {
|
|
GC_ASSERT(large_object_space_contains(space, obj));
|
|
if (!large_object_space_is_survivor(space, obj))
|
|
return 0;
|
|
|
|
uintptr_t edge_addr = gc_edge_address(edge);
|
|
int remembered = 0;
|
|
pthread_mutex_lock(&space->remembered_edges_lock);
|
|
if (!address_set_contains(&space->remembered_edges, edge_addr)) {
|
|
address_set_add(&space->remembered_edges, edge_addr);
|
|
remembered = 1;
|
|
}
|
|
pthread_mutex_unlock(&space->remembered_edges_lock);
|
|
return remembered;
|
|
}
|
|
|
|
static void
|
|
large_object_space_forget_edge(struct large_object_space *space,
|
|
struct gc_edge edge) {
|
|
uintptr_t edge_addr = gc_edge_address(edge);
|
|
pthread_mutex_lock(&space->remembered_edges_lock);
|
|
GC_ASSERT(address_set_contains(&space->remembered_edges, edge_addr));
|
|
address_set_remove(&space->remembered_edges, edge_addr);
|
|
pthread_mutex_unlock(&space->remembered_edges_lock);
|
|
}
|
|
|
|
static void
|
|
large_object_space_clear_remembered_edges(struct large_object_space *space) {
|
|
address_set_clear(&space->remembered_edges);
|
|
}
|
|
|
|
static void
|
|
large_object_space_add_to_freelist(struct large_object_space *space,
|
|
struct large_object_node *node) {
|
|
node->value.is_live = 0;
|
|
struct large_object_dead_data *data = &node->value.dead;
|
|
memset(data, 0, sizeof(*data));
|
|
data->age = 0;
|
|
struct large_object_node **bucket =
|
|
large_object_freelist_bucket(&space->quarantine, node->key.size);
|
|
data->next = *bucket;
|
|
if (data->next)
|
|
data->next->value.dead.prev = &data->next;
|
|
data->prev = bucket;
|
|
*bucket = node;
|
|
}
|
|
|
|
static void
|
|
large_object_space_remove_from_freelist(struct large_object_space *space,
|
|
struct large_object_node *node) {
|
|
GC_ASSERT(!node->value.is_live);
|
|
struct large_object_dead_data *dead = &node->value.dead;
|
|
GC_ASSERT(dead->prev);
|
|
if (dead->next)
|
|
dead->next->value.dead.prev = dead->prev;
|
|
*dead->prev = dead->next;
|
|
dead->prev = NULL;
|
|
dead->next = NULL;
|
|
}
|
|
|
|
static void
|
|
large_object_space_sweep_one(uintptr_t addr, uintptr_t node_bits,
|
|
void *data) {
|
|
struct large_object_space *space = data;
|
|
struct large_object_node *node = (struct large_object_node*) node_bits;
|
|
if (!node->value.is_live)
|
|
return;
|
|
GC_ASSERT(node->value.is_live);
|
|
uint8_t mark = atomic_load_explicit(large_object_node_mark_loc(node),
|
|
memory_order_acquire);
|
|
if (mark != space->marked)
|
|
large_object_space_add_to_freelist(space, node);
|
|
}
|
|
|
|
static void
|
|
large_object_space_process_quarantine(void *data) {
|
|
struct large_object_space *space = data;
|
|
pthread_mutex_lock(&space->lock);
|
|
pthread_mutex_lock(&space->object_tree_lock);
|
|
for (size_t idx = 0; idx < large_object_freelist_num_size_classes(); idx++) {
|
|
struct large_object_node **link = &space->quarantine.buckets[idx];
|
|
for (struct large_object_node *node = *link; node; node = *link) {
|
|
GC_ASSERT(!node->value.is_live);
|
|
if (++node->value.dead.age < 2) {
|
|
link = &node->value.dead.next;
|
|
} else {
|
|
struct large_object obj = node->key;
|
|
large_object_space_remove_from_freelist(space, node);
|
|
address_map_remove(&space->object_map, obj.addr);
|
|
large_object_tree_remove(&space->object_tree, obj.addr);
|
|
gc_platform_release_memory((void*)obj.addr, obj.size);
|
|
}
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&space->object_tree_lock);
|
|
pthread_mutex_unlock(&space->lock);
|
|
}
|
|
|
|
static void
|
|
large_object_space_finish_gc(struct large_object_space *space,
|
|
int is_minor_gc) {
|
|
if (GC_GENERATIONAL) {
|
|
address_map_for_each(is_minor_gc ? &space->nursery : &space->object_map,
|
|
large_object_space_sweep_one,
|
|
space);
|
|
address_map_clear(&space->nursery);
|
|
} else {
|
|
address_map_for_each(&space->object_map,
|
|
large_object_space_sweep_one,
|
|
space);
|
|
}
|
|
size_t free_pages =
|
|
space->total_pages - space->live_pages_at_last_collection;
|
|
space->pages_freed_by_last_collection = free_pages - space->free_pages;
|
|
space->free_pages = free_pages;
|
|
pthread_mutex_unlock(&space->lock);
|
|
if (space->synchronous_release)
|
|
large_object_space_process_quarantine(space);
|
|
}
|
|
|
|
static void
|
|
large_object_space_add_to_allocation_counter(struct large_object_space *space,
|
|
uint64_t *counter) {
|
|
size_t pages = space->total_pages - space->free_pages;
|
|
pages -= space->live_pages_at_last_collection;
|
|
*counter += pages << space->page_size_log2;
|
|
}
|
|
|
|
static inline struct gc_ref
|
|
large_object_space_mark_conservative_ref(struct large_object_space *space,
|
|
struct gc_conservative_ref ref,
|
|
int possibly_interior) {
|
|
uintptr_t addr = gc_conservative_ref_value(ref);
|
|
|
|
if (!possibly_interior) {
|
|
// Addr not aligned on page boundary? Not a large object.
|
|
// Otherwise strip the displacement to obtain the true base address.
|
|
uintptr_t displacement = addr & (space->page_size - 1);
|
|
if (!gc_is_valid_conservative_ref_displacement(displacement))
|
|
return gc_ref_null();
|
|
addr -= displacement;
|
|
}
|
|
|
|
struct large_object_node *node;
|
|
if (possibly_interior) {
|
|
pthread_mutex_lock(&space->object_tree_lock);
|
|
node = large_object_tree_lookup(&space->object_tree, addr);
|
|
pthread_mutex_unlock(&space->object_tree_lock);
|
|
} else {
|
|
node = large_object_space_lookup(space, gc_ref(addr));
|
|
}
|
|
|
|
if (node && node->value.is_live &&
|
|
large_object_space_mark(space, gc_ref(node->key.addr)))
|
|
return gc_ref(node->key.addr);
|
|
|
|
return gc_ref_null();
|
|
}
|
|
|
|
static void*
|
|
large_object_space_alloc(struct large_object_space *space, size_t npages,
|
|
enum gc_trace_kind trace) {
|
|
void *ret = NULL;
|
|
pthread_mutex_lock(&space->lock);
|
|
|
|
size_t size = npages << space->page_size_log2;
|
|
for (size_t idx = large_object_freelist_size_class(size);
|
|
idx < large_object_freelist_num_size_classes();
|
|
idx++) {
|
|
struct large_object_node *node = space->quarantine.buckets[idx];
|
|
while (node && node->key.size < size)
|
|
node = node->value.dead.next;
|
|
if (node) {
|
|
// We found a suitable hole in quarantine. Unlink it from the
|
|
// freelist.
|
|
large_object_space_remove_from_freelist(space, node);
|
|
|
|
// Mark the hole as live.
|
|
node->value.is_live = 1;
|
|
memset(&node->value.live, 0, sizeof(node->value.live));
|
|
node->value.live.mark = LARGE_OBJECT_NURSERY;
|
|
node->value.live.trace = trace;
|
|
|
|
// If the hole is actually too big, trim its tail.
|
|
if (node->key.size > size) {
|
|
struct large_object tail = {node->key.addr + size, node->key.size - size};
|
|
struct large_object_data tail_value = {0,};
|
|
node->key.size = size;
|
|
pthread_mutex_lock(&space->object_tree_lock);
|
|
struct large_object_node *tail_node =
|
|
large_object_tree_insert(&space->object_tree, tail, tail_value);
|
|
pthread_mutex_unlock(&space->object_tree_lock);
|
|
uintptr_t tail_node_bits = (uintptr_t)tail_node;
|
|
address_map_add(&space->object_map, tail_node->key.addr,
|
|
tail_node_bits);
|
|
large_object_space_add_to_freelist(space, tail_node);
|
|
}
|
|
|
|
// Add the object to the nursery.
|
|
if (GC_GENERATIONAL)
|
|
address_map_add(&space->nursery, node->key.addr, (uintptr_t)node);
|
|
|
|
space->free_pages -= npages;
|
|
ret = (void*)node->key.addr;
|
|
memset(ret, 0, size);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we didn't find anything in the quarantine, get fresh pages from the OS.
|
|
if (!ret) {
|
|
ret = gc_platform_acquire_memory(size, 0);
|
|
if (ret) {
|
|
uintptr_t addr = (uintptr_t)ret;
|
|
struct large_object k = { addr, size };
|
|
struct large_object_data v = {0,};
|
|
v.is_live = 1;
|
|
v.live.mark = LARGE_OBJECT_NURSERY;
|
|
v.live.trace = trace;
|
|
|
|
pthread_mutex_lock(&space->object_tree_lock);
|
|
struct large_object_node *node =
|
|
large_object_tree_insert(&space->object_tree, k, v);
|
|
uintptr_t node_bits = (uintptr_t)node;
|
|
address_map_add(&space->object_map, addr, node_bits);
|
|
space->total_pages += npages;
|
|
pthread_mutex_unlock(&space->object_tree_lock);
|
|
}
|
|
}
|
|
|
|
pthread_mutex_unlock(&space->lock);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
large_object_space_init(struct large_object_space *space,
|
|
struct gc_heap *heap,
|
|
struct gc_background_thread *thread) {
|
|
memset(space, 0, sizeof(*space));
|
|
pthread_mutex_init(&space->lock, NULL);
|
|
pthread_mutex_init(&space->object_tree_lock, NULL);
|
|
pthread_mutex_init(&space->remembered_edges_lock, NULL);
|
|
|
|
space->page_size = getpagesize();
|
|
space->page_size_log2 = __builtin_ctz(space->page_size);
|
|
|
|
space->marked = LARGE_OBJECT_MARK_0;
|
|
|
|
large_object_tree_init(&space->object_tree);
|
|
address_map_init(&space->object_map);
|
|
address_map_init(&space->nursery);
|
|
large_object_freelist_init(&space->quarantine);
|
|
|
|
address_set_init(&space->remembered_edges);
|
|
|
|
if (thread)
|
|
gc_background_thread_add_task(thread, GC_BACKGROUND_TASK_START,
|
|
large_object_space_process_quarantine,
|
|
space);
|
|
else
|
|
space->synchronous_release = 1;
|
|
|
|
return 1;
|
|
}
|
|
|
|
#endif // LARGE_OBJECT_SPACE_H
|