mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-05-09 15:10:29 +02:00
Separate compilation!!!!!
This commit is contained in:
parent
fe9bdf6397
commit
b082f5f50d
28 changed files with 344 additions and 189 deletions
47
Makefile
47
Makefile
|
@ -2,32 +2,49 @@ TESTS=quads mt-gcbench # MT_GCBench MT_GCBench2
|
||||||
COLLECTORS=bdw semi whippet parallel-whippet generational-whippet parallel-generational-whippet
|
COLLECTORS=bdw semi whippet parallel-whippet generational-whippet parallel-generational-whippet
|
||||||
|
|
||||||
CC=gcc
|
CC=gcc
|
||||||
CFLAGS=-Wall -O2 -g -fno-strict-aliasing -fvisibility=hidden -Wno-unused -DNDEBUG
|
CFLAGS=-Wall -O2 -g -flto -fno-strict-aliasing -fvisibility=hidden -Wno-unused -DNDEBUG
|
||||||
INCLUDES=-I.
|
INCLUDES=-I.
|
||||||
LDFLAGS=-lpthread
|
LDFLAGS=-lpthread -flto
|
||||||
COMPILE=$(CC) $(CFLAGS) $(INCLUDES) $(LDFLAGS)
|
COMPILE=$(CC) $(CFLAGS) $(INCLUDES)
|
||||||
|
|
||||||
ALL_TESTS=$(foreach COLLECTOR,$(COLLECTORS),$(addprefix $(COLLECTOR)-,$(TESTS)))
|
ALL_TESTS=$(foreach COLLECTOR,$(COLLECTORS),$(addprefix $(COLLECTOR)-,$(TESTS)))
|
||||||
|
|
||||||
all: $(ALL_TESTS)
|
all: $(ALL_TESTS)
|
||||||
|
|
||||||
bdw-%: bdw.h conservative-roots.h %-types.h %.c
|
bdw-%-gc.o: semi.c %-embedder.h %.c
|
||||||
$(COMPILE) `pkg-config --libs --cflags bdw-gc` -DGC_BDW -o $@ $*.c
|
$(COMPILE) `pkg-config --cflags bdw-gc` -include $*-embedder.h -o $@ -c bdw.c
|
||||||
|
bdw-%.o: semi.c %.c
|
||||||
|
$(COMPILE) -include bdw-attrs.h -o $@ -c $*.c
|
||||||
|
bdw-%: bdw-%.o bdw-%-gc.o
|
||||||
|
$(CC) $(LDFLAGS) `pkg-config --libs bdw-gc` -o $@ $^
|
||||||
|
|
||||||
semi-%: semi.h precise-roots.h large-object-space.h %-types.h heap-objects.h %.c
|
semi-%-gc.o: semi.c %-embedder.h large-object-space.h assert.h debug.h %.c
|
||||||
$(COMPILE) -DGC_SEMI -o $@ $*.c
|
$(COMPILE) -DGC_PRECISE=1 -include $*-embedder.h -o $@ -c semi.c
|
||||||
|
semi-%.o: semi.c %.c
|
||||||
|
$(COMPILE) -DGC_PRECISE=1 -include semi-attrs.h -o $@ -c $*.c
|
||||||
|
|
||||||
whippet-%: whippet.h precise-roots.h large-object-space.h serial-tracer.h assert.h debug.h %-types.h heap-objects.h %.c
|
whippet-%-gc.o: whippet.c %-embedder.h large-object-space.h serial-tracer.h assert.h debug.h heap-objects.h %.c
|
||||||
$(COMPILE) -DGC_WHIPPET -o $@ $*.c
|
$(COMPILE) -DGC_PRECISE=1 -include $*-embedder.h -o $@ -c whippet.c
|
||||||
|
whippet-%.o: whippet.c %.c
|
||||||
|
$(COMPILE) -DGC_PRECISE=1 -include whippet-attrs.h -o $@ -c $*.c
|
||||||
|
|
||||||
parallel-whippet-%: whippet.h precise-roots.h large-object-space.h parallel-tracer.h assert.h debug.h %-types.h heap-objects.h %.c
|
parallel-whippet-%-gc.o: whippet.c %-embedder.h large-object-space.h parallel-tracer.h assert.h debug.h heap-objects.h %.c
|
||||||
$(COMPILE) -DGC_PARALLEL_WHIPPET -o $@ $*.c
|
$(COMPILE) -DGC_PARALLEL=1 -DGC_PRECISE=1 -include $*-embedder.h -o $@ -c whippet.c
|
||||||
|
parallel-whippet-%.o: whippet.c %.c
|
||||||
|
$(COMPILE) -DGC_PARALLEL=1 -DGC_PRECISE=1 -include whippet-attrs.h -o $@ -c $*.c
|
||||||
|
|
||||||
generational-whippet-%: whippet.h precise-roots.h large-object-space.h serial-tracer.h assert.h debug.h %-types.h heap-objects.h %.c
|
generational-whippet-%-gc.o: whippet.c %-embedder.h large-object-space.h serial-tracer.h assert.h debug.h heap-objects.h %.c
|
||||||
$(COMPILE) -DGC_GENERATIONAL_WHIPPET -o $@ $*.c
|
$(COMPILE) -DGC_GENERATIONAL=1 -DGC_PRECISE=1 -include $*-embedder.h -o $@ -c whippet.c
|
||||||
|
generational-whippet-%.o: whippet.c %.c
|
||||||
|
$(COMPILE) -DGC_GENERATIONAL=1 -DGC_PRECISE=1 -include whippet-attrs.h -o $@ -c $*.c
|
||||||
|
|
||||||
parallel-generational-whippet-%: whippet.h precise-roots.h large-object-space.h parallel-tracer.h assert.h debug.h %-types.h heap-objects.h %.c
|
parallel-generational-whippet-%-gc.o: whippet.c %-embedder.h large-object-space.h parallel-tracer.h assert.h debug.h heap-objects.h %.c
|
||||||
$(COMPILE) -DGC_PARALLEL_GENERATIONAL_WHIPPET -o $@ $*.c
|
$(COMPILE) -DGC_PARALLEL=1 -DGC_GENERATIONAL=1 -DGC_PRECISE=1 -include $*-embedder.h -o $@ -c whippet.c
|
||||||
|
parallel-generational-whippet-%.o: whippet.c %.c
|
||||||
|
$(COMPILE) -DGC_PARALLEL=1 -DGC_GENERATIONAL=1 -DGC_PRECISE=1 -include whippet-attrs.h -o $@ -c $*.c
|
||||||
|
|
||||||
|
%: %.o %-gc.o
|
||||||
|
$(CC) $(LDFLAGS) $($*_LDFLAGS) -o $@ $^
|
||||||
|
|
||||||
check: $(addprefix test-$(TARGET),$(TARGETS))
|
check: $(addprefix test-$(TARGET),$(TARGETS))
|
||||||
|
|
||||||
|
|
13
bdw-attrs.h
13
bdw-attrs.h
|
@ -2,6 +2,7 @@
|
||||||
#define BDW_ATTRS_H
|
#define BDW_ATTRS_H
|
||||||
|
|
||||||
#include "gc-attrs.h"
|
#include "gc-attrs.h"
|
||||||
|
#include "gc-assert.h"
|
||||||
|
|
||||||
static inline enum gc_allocator_kind gc_allocator_kind(void) {
|
static inline enum gc_allocator_kind gc_allocator_kind(void) {
|
||||||
return GC_ALLOCATOR_INLINE_FREELIST;
|
return GC_ALLOCATOR_INLINE_FREELIST;
|
||||||
|
@ -14,10 +15,10 @@ static inline size_t gc_allocator_large_threshold(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline size_t gc_allocator_allocation_pointer_offset(void) {
|
static inline size_t gc_allocator_allocation_pointer_offset(void) {
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
static inline size_t gc_allocator_allocation_limit_offset(void) {
|
static inline size_t gc_allocator_allocation_limit_offset(void) {
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline size_t gc_allocator_freelist_offset(size_t size) {
|
static inline size_t gc_allocator_freelist_offset(size_t size) {
|
||||||
|
@ -29,10 +30,10 @@ static inline size_t gc_allocator_alloc_table_alignment(void) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
static inline uint8_t gc_allocator_alloc_table_begin_pattern(void) {
|
static inline uint8_t gc_allocator_alloc_table_begin_pattern(void) {
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
static inline uint8_t gc_allocator_alloc_table_end_pattern(void) {
|
static inline uint8_t gc_allocator_alloc_table_end_pattern(void) {
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int gc_allocator_needs_clear(void) {
|
static inline int gc_allocator_needs_clear(void) {
|
||||||
|
@ -43,10 +44,10 @@ static inline enum gc_write_barrier_kind gc_small_write_barrier_kind(void) {
|
||||||
return GC_WRITE_BARRIER_NONE;
|
return GC_WRITE_BARRIER_NONE;
|
||||||
}
|
}
|
||||||
static inline size_t gc_small_write_barrier_card_table_alignment(void) {
|
static inline size_t gc_small_write_barrier_card_table_alignment(void) {
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
static inline size_t gc_small_write_barrier_card_size(void) {
|
static inline size_t gc_small_write_barrier_card_size(void) {
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // BDW_ATTRS_H
|
#endif // BDW_ATTRS_H
|
||||||
|
|
|
@ -1,8 +1,18 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#define GC_API_
|
||||||
|
#include "gc-api.h"
|
||||||
|
|
||||||
#include "bdw-attrs.h"
|
#include "bdw-attrs.h"
|
||||||
#include "conservative-roots.h"
|
|
||||||
|
#if GC_PRECISE
|
||||||
|
#error bdw-gc is a conservative collector
|
||||||
|
#else
|
||||||
|
#include "conservative-roots-embedder.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
// When pthreads are used, let `libgc' know about it and redirect
|
// When pthreads are used, let `libgc' know about it and redirect
|
||||||
// allocation calls such as `GC_MALLOC ()' to (contention-free, faster)
|
// allocation calls such as `GC_MALLOC ()' to (contention-free, faster)
|
||||||
|
@ -61,10 +71,10 @@ static void* allocate_small_slow(void **freelist, size_t idx,
|
||||||
size_t bytes = gc_inline_freelist_object_size(idx);
|
size_t bytes = gc_inline_freelist_object_size(idx);
|
||||||
GC_generic_malloc_many(bytes, kind, freelist);
|
GC_generic_malloc_many(bytes, kind, freelist);
|
||||||
void *head = *freelist;
|
void *head = *freelist;
|
||||||
if (UNLIKELY (!head)) {
|
if (GC_UNLIKELY (!head)) {
|
||||||
fprintf(stderr, "ran out of space, heap size %zu\n",
|
fprintf(stderr, "ran out of space, heap size %zu\n",
|
||||||
GC_get_heap_size());
|
GC_get_heap_size());
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
*freelist = *(void **)(head);
|
*freelist = *(void **)(head);
|
||||||
return head;
|
return head;
|
||||||
|
@ -74,25 +84,25 @@ static inline void *
|
||||||
allocate_small(void **freelist, size_t idx, enum gc_inline_kind kind) {
|
allocate_small(void **freelist, size_t idx, enum gc_inline_kind kind) {
|
||||||
void *head = *freelist;
|
void *head = *freelist;
|
||||||
|
|
||||||
if (UNLIKELY (!head))
|
if (GC_UNLIKELY (!head))
|
||||||
return allocate_small_slow(freelist, idx, kind);
|
return allocate_small_slow(freelist, idx, kind);
|
||||||
|
|
||||||
*freelist = *(void **)(head);
|
*freelist = *(void **)(head);
|
||||||
return head;
|
return head;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void* gc_allocate_large(struct mutator *mut, size_t size) {
|
void* gc_allocate_large(struct mutator *mut, size_t size) {
|
||||||
return GC_malloc(size);
|
return GC_malloc(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void* gc_allocate_small(struct mutator *mut, size_t size) {
|
void* gc_allocate_small(struct mutator *mut, size_t size) {
|
||||||
GC_ASSERT(size != 0);
|
GC_ASSERT(size != 0);
|
||||||
GC_ASSERT(size <= gc_allocator_large_threshold());
|
GC_ASSERT(size <= gc_allocator_large_threshold());
|
||||||
size_t idx = gc_inline_bytes_to_freelist_index(size);
|
size_t idx = gc_inline_bytes_to_freelist_index(size);
|
||||||
return allocate_small(&mut->freelists[idx], idx, GC_INLINE_KIND_NORMAL);
|
return allocate_small(&mut->freelists[idx], idx, GC_INLINE_KIND_NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void* gc_allocate_pointerless(struct mutator *mut,
|
void* gc_allocate_pointerless(struct mutator *mut,
|
||||||
size_t size) {
|
size_t size) {
|
||||||
// Because the BDW API requires us to implement a custom marker so
|
// Because the BDW API requires us to implement a custom marker so
|
||||||
// that the pointerless freelist gets traced, even though it's in a
|
// that the pointerless freelist gets traced, even though it's in a
|
||||||
|
@ -126,7 +136,7 @@ static void dump_available_gc_options(void) {
|
||||||
fprintf(stderr, "\n");
|
fprintf(stderr, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
static int gc_option_from_string(const char *str) {
|
int gc_option_from_string(const char *str) {
|
||||||
#define PARSE_OPTION(option, name) if (strcmp(str, name) == 0) return option;
|
#define PARSE_OPTION(option, name) if (strcmp(str, name) == 0) return option;
|
||||||
FOR_EACH_GC_OPTION(PARSE_OPTION)
|
FOR_EACH_GC_OPTION(PARSE_OPTION)
|
||||||
#undef PARSE_OPTION
|
#undef PARSE_OPTION
|
||||||
|
@ -145,8 +155,8 @@ struct options {
|
||||||
};
|
};
|
||||||
|
|
||||||
static size_t parse_size_t(double value) {
|
static size_t parse_size_t(double value) {
|
||||||
ASSERT(value >= 0);
|
GC_ASSERT(value >= 0);
|
||||||
ASSERT(value <= (size_t) -1);
|
GC_ASSERT(value <= (size_t) -1);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +173,7 @@ static int parse_options(int argc, struct gc_option argv[],
|
||||||
options->parallelism = parse_size_t(argv[i].value);
|
options->parallelism = parse_size_t(argv[i].value);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,8 +187,8 @@ static int parse_options(int argc, struct gc_option argv[],
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int gc_init(int argc, struct gc_option argv[],
|
int gc_init(int argc, struct gc_option argv[],
|
||||||
struct heap **heap, struct mutator **mutator) {
|
struct heap **heap, struct mutator **mutator) {
|
||||||
GC_ASSERT_EQ(gc_allocator_small_granule_size(), GC_INLINE_GRANULE_BYTES);
|
GC_ASSERT_EQ(gc_allocator_small_granule_size(), GC_INLINE_GRANULE_BYTES);
|
||||||
GC_ASSERT_EQ(gc_allocator_large_threshold(),
|
GC_ASSERT_EQ(gc_allocator_large_threshold(),
|
||||||
GC_INLINE_FREELIST_COUNT * GC_INLINE_GRANULE_BYTES);
|
GC_INLINE_FREELIST_COUNT * GC_INLINE_GRANULE_BYTES);
|
||||||
|
@ -208,8 +218,8 @@ static int gc_init(int argc, struct gc_option argv[],
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct mutator* gc_init_for_thread(uintptr_t *stack_base,
|
struct mutator* gc_init_for_thread(uintptr_t *stack_base,
|
||||||
struct heap *heap) {
|
struct heap *heap) {
|
||||||
pthread_mutex_lock(&heap->lock);
|
pthread_mutex_lock(&heap->lock);
|
||||||
if (!heap->multithreaded) {
|
if (!heap->multithreaded) {
|
||||||
GC_allow_register_threads();
|
GC_allow_register_threads();
|
||||||
|
@ -221,23 +231,23 @@ static struct mutator* gc_init_for_thread(uintptr_t *stack_base,
|
||||||
GC_register_my_thread(&base);
|
GC_register_my_thread(&base);
|
||||||
return add_mutator(heap);
|
return add_mutator(heap);
|
||||||
}
|
}
|
||||||
static void gc_finish_for_thread(struct mutator *mut) {
|
void gc_finish_for_thread(struct mutator *mut) {
|
||||||
GC_unregister_my_thread();
|
GC_unregister_my_thread();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void* gc_call_without_gc(struct mutator *mut,
|
void* gc_call_without_gc(struct mutator *mut,
|
||||||
void* (*f)(void*),
|
void* (*f)(void*),
|
||||||
void *data) {
|
void *data) {
|
||||||
return GC_do_blocking(f, data);
|
return GC_do_blocking(f, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gc_mutator_set_roots(struct mutator *mut,
|
void gc_mutator_set_roots(struct mutator *mut,
|
||||||
struct gc_mutator_roots *roots) {
|
struct gc_mutator_roots *roots) {
|
||||||
}
|
}
|
||||||
static void gc_heap_set_roots(struct heap *heap, struct gc_heap_roots *roots) {
|
void gc_heap_set_roots(struct heap *heap, struct gc_heap_roots *roots) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gc_print_stats(struct heap *heap) {
|
void gc_print_stats(struct heap *heap) {
|
||||||
printf("Completed %ld collections\n", (long)GC_get_gc_no());
|
printf("Completed %ld collections\n", (long)GC_get_gc_no());
|
||||||
printf("Heap size is %ld\n", (long)GC_get_heap_size());
|
printf("Heap size is %ld\n", (long)GC_get_heap_size());
|
||||||
}
|
}
|
12
conservative-roots-api.h
Normal file
12
conservative-roots-api.h
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#ifndef CONSERVATIVE_ROOTS_API_H
|
||||||
|
#define CONSERVATIVE_ROOTS_API_H
|
||||||
|
|
||||||
|
#include "conservative-roots-types.h"
|
||||||
|
|
||||||
|
#define HANDLE_TO(T) union { T* v; struct handle handle; }
|
||||||
|
#define HANDLE_REF(h) h.v
|
||||||
|
#define HANDLE_SET(h,val) do { h.v = val; } while (0)
|
||||||
|
#define PUSH_HANDLE(cx, h) do { (void) &h; } while (0)
|
||||||
|
#define POP_HANDLE(cx) do { } while (0)
|
||||||
|
|
||||||
|
#endif // CONSERVATIVE_ROOTS_API_H
|
21
conservative-roots-embedder.h
Normal file
21
conservative-roots-embedder.h
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#ifndef CONSERVATIVE_ROOTS_EMBEDDER_H
|
||||||
|
#define CONSERVATIVE_ROOTS_EMBEDDER_H
|
||||||
|
|
||||||
|
#include "gc-assert.h"
|
||||||
|
#include "conservative-roots-types.h"
|
||||||
|
|
||||||
|
static inline void gc_trace_mutator_roots(struct gc_mutator_roots *roots,
|
||||||
|
void (*trace_edge)(struct gc_edge edge,
|
||||||
|
void *trace_data),
|
||||||
|
void *trace_data) {
|
||||||
|
GC_CRASH();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void gc_trace_heap_roots(struct gc_heap_roots *roots,
|
||||||
|
void (*trace_edge)(struct gc_edge edge,
|
||||||
|
void *trace_data),
|
||||||
|
void *trace_data) {
|
||||||
|
GC_CRASH();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CONSERVATIVE_ROOTS_EMBEDDER_H
|
8
conservative-roots-types.h
Normal file
8
conservative-roots-types.h
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#ifndef CONSERVATIVE_ROOTS_TYPES_H
|
||||||
|
#define CONSERVATIVE_ROOTS_TYPES_H
|
||||||
|
|
||||||
|
struct handle { void *unused; };
|
||||||
|
struct gc_heap_roots { void *unused; };
|
||||||
|
struct gc_mutator_roots { void *unused; };
|
||||||
|
|
||||||
|
#endif // CONSERVATIVE_ROOTS_TYPES_H
|
|
@ -1,21 +0,0 @@
|
||||||
struct handle { void *unused; };
|
|
||||||
|
|
||||||
#define HANDLE_TO(T) union { T* v; struct handle handle; }
|
|
||||||
#define HANDLE_REF(h) h.v
|
|
||||||
#define HANDLE_SET(h,val) do { h.v = val; } while (0)
|
|
||||||
#define PUSH_HANDLE(cx, h) do { (void) &h; } while (0)
|
|
||||||
#define POP_HANDLE(cx) do { } while (0)
|
|
||||||
|
|
||||||
static inline void visit_thread_roots(void *thread_roots,
|
|
||||||
void (*trace_edge)(struct gc_edge edge,
|
|
||||||
void *trace_data),
|
|
||||||
void *trace_data) {
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void visit_roots(struct handle *roots,
|
|
||||||
void (*trace_edge)(struct gc_edge edge,
|
|
||||||
void *trace_data),
|
|
||||||
void *trace_data) {
|
|
||||||
GC_ASSERT(!roots);
|
|
||||||
}
|
|
8
gc-api.h
8
gc-api.h
|
@ -32,7 +32,7 @@ struct gc_mutator {
|
||||||
|
|
||||||
// FIXME: Conflict with bdw-gc GC_API. Switch prefix?
|
// FIXME: Conflict with bdw-gc GC_API. Switch prefix?
|
||||||
#ifndef GC_API_
|
#ifndef GC_API_
|
||||||
#define GC_API_ static
|
#define GC_API_ __attribute__((visibility("hidden")))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
GC_API_ int gc_option_from_string(const char *str);
|
GC_API_ int gc_option_from_string(const char *str);
|
||||||
|
@ -159,12 +159,12 @@ static inline void* gc_allocate(struct mutator *mut, size_t size) {
|
||||||
case GC_ALLOCATOR_INLINE_NONE:
|
case GC_ALLOCATOR_INLINE_NONE:
|
||||||
return gc_allocate_small(mut, size);
|
return gc_allocate_small(mut, size);
|
||||||
default:
|
default:
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: remove :P
|
// FIXME: remove :P
|
||||||
static inline void* gc_allocate_pointerless(struct mutator *mut, size_t bytes);
|
GC_API_ void* gc_allocate_pointerless(struct mutator *mut, size_t bytes);
|
||||||
|
|
||||||
static inline void gc_small_write_barrier(struct gc_ref obj, struct gc_edge edge,
|
static inline void gc_small_write_barrier(struct gc_ref obj, struct gc_edge edge,
|
||||||
struct gc_ref new_val) GC_ALWAYS_INLINE;
|
struct gc_ref new_val) GC_ALWAYS_INLINE;
|
||||||
|
@ -183,7 +183,7 @@ static inline void gc_small_write_barrier(struct gc_ref obj, struct gc_edge edge
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,14 @@
|
||||||
#define GC_UNLIKELY(e) __builtin_expect(e, 0)
|
#define GC_UNLIKELY(e) __builtin_expect(e, 0)
|
||||||
#define GC_LIKELY(e) __builtin_expect(e, 1)
|
#define GC_LIKELY(e) __builtin_expect(e, 1)
|
||||||
|
|
||||||
|
#define GC_CRASH() __builtin_trap()
|
||||||
|
|
||||||
#if GC_DEBUG
|
#if GC_DEBUG
|
||||||
#define GC_ASSERT(x) do { if (GC_UNLIKELY(!(x))) __builtin_trap(); } while (0)
|
#define GC_ASSERT(x) do { if (GC_UNLIKELY(!(x))) GC_CRASH(); } while (0)
|
||||||
|
#define GC_UNREACHABLE() GC_CRASH()
|
||||||
#else
|
#else
|
||||||
#define GC_ASSERT(x) do { } while (0)
|
#define GC_ASSERT(x) do { } while (0)
|
||||||
|
#define GC_UNREACHABLE() __builtin_unreachable()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define GC_ASSERT_EQ(a, b) GC_ASSERT((a) == (b))
|
#define GC_ASSERT_EQ(a, b) GC_ASSERT((a) == (b))
|
||||||
|
|
39
gc-attrs.h
Normal file
39
gc-attrs.h
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
#ifndef GC_ATTRS_H
|
||||||
|
#define GC_ATTRS_H
|
||||||
|
|
||||||
|
#include "gc-inline.h"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
enum gc_allocator_kind {
|
||||||
|
GC_ALLOCATOR_INLINE_BUMP_POINTER,
|
||||||
|
GC_ALLOCATOR_INLINE_FREELIST,
|
||||||
|
GC_ALLOCATOR_INLINE_NONE
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline enum gc_allocator_kind gc_allocator_kind(void) GC_ALWAYS_INLINE;
|
||||||
|
static inline size_t gc_allocator_large_threshold(void) GC_ALWAYS_INLINE;
|
||||||
|
static inline size_t gc_allocator_small_granule_size(void) GC_ALWAYS_INLINE;
|
||||||
|
|
||||||
|
static inline size_t gc_allocator_allocation_pointer_offset(void) GC_ALWAYS_INLINE;
|
||||||
|
static inline size_t gc_allocator_allocation_limit_offset(void) GC_ALWAYS_INLINE;
|
||||||
|
|
||||||
|
static inline size_t gc_allocator_freelist_offset(size_t size) GC_ALWAYS_INLINE;
|
||||||
|
|
||||||
|
static inline size_t gc_allocator_alloc_table_alignment(void) GC_ALWAYS_INLINE;
|
||||||
|
static inline uint8_t gc_allocator_alloc_table_begin_pattern(void) GC_ALWAYS_INLINE;
|
||||||
|
static inline uint8_t gc_allocator_alloc_table_end_pattern(void) GC_ALWAYS_INLINE;
|
||||||
|
|
||||||
|
static inline int gc_allocator_needs_clear(void) GC_ALWAYS_INLINE;
|
||||||
|
|
||||||
|
enum gc_write_barrier_kind {
|
||||||
|
GC_WRITE_BARRIER_NONE,
|
||||||
|
GC_WRITE_BARRIER_CARD
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline enum gc_write_barrier_kind gc_small_write_barrier_kind(void) GC_ALWAYS_INLINE;
|
||||||
|
static inline size_t gc_small_write_barrier_card_table_alignment(void) GC_ALWAYS_INLINE;
|
||||||
|
static inline size_t gc_small_write_barrier_card_size(void) GC_ALWAYS_INLINE;
|
||||||
|
|
||||||
|
#endif // GC_ATTRS_H
|
12
gc-config.h
12
gc-config.h
|
@ -5,4 +5,16 @@
|
||||||
#define GC_DEBUG 0
|
#define GC_DEBUG 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef GC_PARALLEL
|
||||||
|
#define GC_PARALLEL 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef GC_GENERATIONAL
|
||||||
|
#define GC_GENERATIONAL 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef GC_PRECISE
|
||||||
|
#define GC_PRECISE 0
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif // GC_CONFIG_H
|
#endif // GC_CONFIG_H
|
||||||
|
|
|
@ -8,6 +8,10 @@
|
||||||
#define GC_EMBEDDER_API static
|
#define GC_EMBEDDER_API static
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
struct gc_mutator_roots;
|
||||||
|
struct gc_heap_roots;
|
||||||
|
struct gc_atomic_forward;
|
||||||
|
|
||||||
GC_EMBEDDER_API inline void gc_trace_object(void *object,
|
GC_EMBEDDER_API inline void gc_trace_object(void *object,
|
||||||
void (*trace_edge)(struct gc_edge edge,
|
void (*trace_edge)(struct gc_edge edge,
|
||||||
void *trace_data),
|
void *trace_data),
|
||||||
|
|
7
gc-visibility.h
Normal file
7
gc-visibility.h
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#ifndef GC_VISIBILITY_H_
|
||||||
|
#define GC_VISIBILITY_H_
|
||||||
|
|
||||||
|
#define GC_INTERNAL __attribute__((visibility("hidden")))
|
||||||
|
#define GC_PUBLIC __attribute__((visibility("default")))
|
||||||
|
|
||||||
|
#endif // GC_VISIBILITY_H
|
|
@ -36,7 +36,7 @@ static inline void
|
||||||
visit_hole_fields(Hole *obj,
|
visit_hole_fields(Hole *obj,
|
||||||
void (*visit)(struct gc_edge edge, void *visit_data),
|
void (*visit)(struct gc_edge edge, void *visit_data),
|
||||||
void *visit_data) {
|
void *visit_data) {
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "simple-gc-embedder.h"
|
#include "simple-gc-embedder.h"
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
#ifndef GCBENCH_TYPES_H
|
#ifndef GCBENCH_TYPES_H
|
||||||
#define GCBENCH_TYPES_H
|
#define GCBENCH_TYPES_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
#define FOR_EACH_HEAP_OBJECT_KIND(M) \
|
#define FOR_EACH_HEAP_OBJECT_KIND(M) \
|
||||||
M(node, Node, NODE) \
|
M(node, Node, NODE) \
|
||||||
M(double_array, DoubleArray, DOUBLE_ARRAY) \
|
M(double_array, DoubleArray, DOUBLE_ARRAY) \
|
||||||
|
|
16
mt-gcbench.c
16
mt-gcbench.c
|
@ -44,16 +44,16 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
|
||||||
#include "mt-gcbench-types.h"
|
|
||||||
|
|
||||||
#include "assert.h"
|
#include "assert.h"
|
||||||
#include "simple-allocator.h"
|
|
||||||
#include "gc-api.h"
|
#include "gc-api.h"
|
||||||
|
#include "mt-gcbench-types.h"
|
||||||
#include "mt-gcbench-embedder.h"
|
#if GC_PRECISE
|
||||||
#include "gc.h"
|
#include "precise-roots-api.h"
|
||||||
|
#else
|
||||||
#include "gc-inline.h"
|
#include "conservative-roots-api.h"
|
||||||
|
#endif
|
||||||
|
#include "mt-gcbench-types.h"
|
||||||
|
#include "simple-allocator.h"
|
||||||
|
|
||||||
#define MAX_THREAD_COUNT 256
|
#define MAX_THREAD_COUNT 256
|
||||||
|
|
||||||
|
|
|
@ -149,7 +149,7 @@ static int
|
||||||
trace_deque_grow(struct trace_deque *q, int cur, size_t b, size_t t) {
|
trace_deque_grow(struct trace_deque *q, int cur, size_t b, size_t t) {
|
||||||
if (!trace_buf_grow(&q->bufs[cur], &q->bufs[cur + 1], b, t)) {
|
if (!trace_buf_grow(&q->bufs[cur], &q->bufs[cur + 1], b, t)) {
|
||||||
fprintf(stderr, "failed to grow deque!!\n");
|
fprintf(stderr, "failed to grow deque!!\n");
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
|
|
||||||
cur++;
|
cur++;
|
||||||
|
@ -359,7 +359,7 @@ trace_worker_thread(void *data) {
|
||||||
pthread_mutex_unlock(&worker->lock);
|
pthread_mutex_unlock(&worker->lock);
|
||||||
return NULL;
|
return NULL;
|
||||||
default:
|
default:
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
struct handle {
|
#ifndef PRECISE_ROOTS_API_H
|
||||||
void *v;
|
#define PRECISE_ROOTS_API_H
|
||||||
struct handle *next;
|
|
||||||
};
|
#include "precise-roots-types.h"
|
||||||
|
|
||||||
#define HANDLE_TO(T) union { T* v; struct handle handle; }
|
#define HANDLE_TO(T) union { T* v; struct handle handle; }
|
||||||
#define HANDLE_REF(h) h.v
|
#define HANDLE_REF(h) h.v
|
||||||
|
@ -18,10 +18,4 @@ static inline void pop_handle(struct handle **roots) {
|
||||||
*roots = (*roots)->next;
|
*roots = (*roots)->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void visit_roots(struct handle *roots,
|
#endif // PRECISE_ROOTS_API_H
|
||||||
void (*trace_edge)(struct gc_edge edge,
|
|
||||||
void *trace_data),
|
|
||||||
void *trace_data) {
|
|
||||||
for (struct handle *h = roots; h; h = h->next)
|
|
||||||
trace_edge(gc_edge(&h->v), trace_data);
|
|
||||||
}
|
|
31
precise-roots-embedder.h
Normal file
31
precise-roots-embedder.h
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#ifndef PRECISE_ROOTS_EMBEDDER_H
|
||||||
|
#define PRECISE_ROOTS_EMBEDDER_H
|
||||||
|
|
||||||
|
#include "gc-edge.h"
|
||||||
|
#include "precise-roots-types.h"
|
||||||
|
|
||||||
|
static inline void visit_roots(struct handle *roots,
|
||||||
|
void (*trace_edge)(struct gc_edge edge,
|
||||||
|
void *trace_data),
|
||||||
|
void *trace_data) {
|
||||||
|
for (struct handle *h = roots; h; h = h->next)
|
||||||
|
trace_edge(gc_edge(&h->v), trace_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void gc_trace_mutator_roots(struct gc_mutator_roots *roots,
|
||||||
|
void (*trace_edge)(struct gc_edge edge,
|
||||||
|
void *trace_data),
|
||||||
|
void *trace_data) {
|
||||||
|
if (roots)
|
||||||
|
visit_roots(roots->roots, trace_edge, trace_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void gc_trace_heap_roots(struct gc_heap_roots *roots,
|
||||||
|
void (*trace_edge)(struct gc_edge edge,
|
||||||
|
void *trace_data),
|
||||||
|
void *trace_data) {
|
||||||
|
if (roots)
|
||||||
|
visit_roots(roots->roots, trace_edge, trace_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // PRECISE_ROOTS_EMBEDDER_H
|
17
precise-roots-types.h
Normal file
17
precise-roots-types.h
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#ifndef PRECISE_ROOTS_TYPES_H
|
||||||
|
#define PRECISE_ROOTS_TYPES_H
|
||||||
|
|
||||||
|
struct handle {
|
||||||
|
void *v;
|
||||||
|
struct handle *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct gc_heap_roots {
|
||||||
|
struct handle *roots;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct gc_mutator_roots {
|
||||||
|
struct handle *roots;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PRECISE_ROOTS_TYPES_H
|
|
@ -1,6 +1,8 @@
|
||||||
#ifndef QUADS_EMBEDDER_H
|
#ifndef QUADS_EMBEDDER_H
|
||||||
#define QUADS_EMBEDDER_H
|
#define QUADS_EMBEDDER_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
#include "quads-types.h"
|
#include "quads-types.h"
|
||||||
|
|
||||||
#define DEFINE_METHODS(name, Name, NAME) \
|
#define DEFINE_METHODS(name, Name, NAME) \
|
||||||
|
|
11
quads.c
11
quads.c
|
@ -1,14 +1,17 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stddef.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
|
||||||
#include "assert.h"
|
#include "assert.h"
|
||||||
|
#include "gc-api.h"
|
||||||
|
#if GC_PRECISE
|
||||||
|
#include "precise-roots-api.h"
|
||||||
|
#else
|
||||||
|
#include "conservative-roots-api.h"
|
||||||
|
#endif
|
||||||
#include "quads-types.h"
|
#include "quads-types.h"
|
||||||
#include "simple-allocator.h"
|
#include "simple-allocator.h"
|
||||||
#include "gc-api.h"
|
|
||||||
|
|
||||||
#include "quads-embedder.h"
|
|
||||||
#include "gc.h"
|
|
||||||
|
|
||||||
typedef HANDLE_TO(Quad) QuadHandle;
|
typedef HANDLE_TO(Quad) QuadHandle;
|
||||||
|
|
||||||
|
|
11
semi-attrs.h
11
semi-attrs.h
|
@ -2,6 +2,7 @@
|
||||||
#define SEMI_ATTRS_H
|
#define SEMI_ATTRS_H
|
||||||
|
|
||||||
#include "gc-attrs.h"
|
#include "gc-attrs.h"
|
||||||
|
#include "gc-assert.h"
|
||||||
|
|
||||||
static const uintptr_t GC_ALIGNMENT = 8;
|
static const uintptr_t GC_ALIGNMENT = 8;
|
||||||
static const size_t GC_LARGE_OBJECT_THRESHOLD = 8192;
|
static const size_t GC_LARGE_OBJECT_THRESHOLD = 8192;
|
||||||
|
@ -24,7 +25,7 @@ static inline size_t gc_allocator_allocation_limit_offset(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline size_t gc_allocator_freelist_offset(size_t size) {
|
static inline size_t gc_allocator_freelist_offset(size_t size) {
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int gc_allocator_needs_clear(void) {
|
static inline int gc_allocator_needs_clear(void) {
|
||||||
|
@ -35,20 +36,20 @@ static inline size_t gc_allocator_alloc_table_alignment(void) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
static inline uint8_t gc_allocator_alloc_table_begin_pattern(void) {
|
static inline uint8_t gc_allocator_alloc_table_begin_pattern(void) {
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
static inline uint8_t gc_allocator_alloc_table_end_pattern(void) {
|
static inline uint8_t gc_allocator_alloc_table_end_pattern(void) {
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline enum gc_write_barrier_kind gc_small_write_barrier_kind(void) {
|
static inline enum gc_write_barrier_kind gc_small_write_barrier_kind(void) {
|
||||||
return GC_WRITE_BARRIER_NONE;
|
return GC_WRITE_BARRIER_NONE;
|
||||||
}
|
}
|
||||||
static inline size_t gc_small_write_barrier_card_table_alignment(void) {
|
static inline size_t gc_small_write_barrier_card_table_alignment(void) {
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
static inline size_t gc_small_write_barrier_card_size(void) {
|
static inline size_t gc_small_write_barrier_card_size(void) {
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // SEMI_ATTRS_H
|
#endif // SEMI_ATTRS_H
|
||||||
|
|
|
@ -5,9 +5,17 @@
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define GC_API_
|
||||||
|
#include "gc-api.h"
|
||||||
|
|
||||||
#include "semi-attrs.h"
|
#include "semi-attrs.h"
|
||||||
#include "large-object-space.h"
|
#include "large-object-space.h"
|
||||||
#include "precise-roots.h"
|
|
||||||
|
#if GC_PRECISE
|
||||||
|
#include "precise-roots-embedder.h"
|
||||||
|
#else
|
||||||
|
#error semi is a precise collector
|
||||||
|
#endif
|
||||||
|
|
||||||
struct semi_space {
|
struct semi_space {
|
||||||
uintptr_t hp;
|
uintptr_t hp;
|
||||||
|
@ -141,7 +149,7 @@ static void visit(struct gc_edge edge, void *visit_data) {
|
||||||
else if (large_object_space_contains(heap_large_object_space(heap), obj))
|
else if (large_object_space_contains(heap_large_object_space(heap), obj))
|
||||||
visit_large_object_space(heap, heap_large_object_space(heap), obj);
|
visit_large_object_space(heap, heap_large_object_space(heap), obj);
|
||||||
else
|
else
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void collect(struct mutator *mut) {
|
static void collect(struct mutator *mut) {
|
||||||
|
@ -167,11 +175,11 @@ static void collect_for_alloc(struct mutator *mut, size_t bytes) {
|
||||||
struct semi_space *space = mutator_semi_space(mut);
|
struct semi_space *space = mutator_semi_space(mut);
|
||||||
if (space->limit - space->hp < bytes) {
|
if (space->limit - space->hp < bytes) {
|
||||||
fprintf(stderr, "ran out of space, heap size %zu\n", space->size);
|
fprintf(stderr, "ran out of space, heap size %zu\n", space->size);
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void* gc_allocate_large(struct mutator *mut, size_t size) {
|
void* gc_allocate_large(struct mutator *mut, size_t size) {
|
||||||
struct heap *heap = mutator_heap(mut);
|
struct heap *heap = mutator_heap(mut);
|
||||||
struct large_object_space *space = heap_large_object_space(heap);
|
struct large_object_space *space = heap_large_object_space(heap);
|
||||||
struct semi_space *semi_space = heap_semi_space(heap);
|
struct semi_space *semi_space = heap_semi_space(heap);
|
||||||
|
@ -181,7 +189,7 @@ static void* gc_allocate_large(struct mutator *mut, size_t size) {
|
||||||
collect(mut);
|
collect(mut);
|
||||||
if (!semi_space_steal_pages(semi_space, npages)) {
|
if (!semi_space_steal_pages(semi_space, npages)) {
|
||||||
fprintf(stderr, "ran out of space, heap size %zu\n", semi_space->size);
|
fprintf(stderr, "ran out of space, heap size %zu\n", semi_space->size);
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,13 +199,13 @@ static void* gc_allocate_large(struct mutator *mut, size_t size) {
|
||||||
|
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
perror("weird: we have the space but mmap didn't work");
|
perror("weird: we have the space but mmap didn't work");
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void* gc_allocate_small(struct mutator *mut, size_t size) {
|
void* gc_allocate_small(struct mutator *mut, size_t size) {
|
||||||
struct semi_space *space = mutator_semi_space(mut);
|
struct semi_space *space = mutator_semi_space(mut);
|
||||||
while (1) {
|
while (1) {
|
||||||
uintptr_t addr = space->hp;
|
uintptr_t addr = space->hp;
|
||||||
|
@ -212,7 +220,7 @@ static void* gc_allocate_small(struct mutator *mut, size_t size) {
|
||||||
return (void *)addr;
|
return (void *)addr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static inline void* gc_allocate_pointerless(struct mutator *mut, size_t size) {
|
void* gc_allocate_pointerless(struct mutator *mut, size_t size) {
|
||||||
return gc_allocate(mut, size);
|
return gc_allocate(mut, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +258,7 @@ static void dump_available_gc_options(void) {
|
||||||
fprintf(stderr, "\n");
|
fprintf(stderr, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
static int gc_option_from_string(const char *str) {
|
int gc_option_from_string(const char *str) {
|
||||||
#define PARSE_OPTION(option, name) if (strcmp(str, name) == 0) return option;
|
#define PARSE_OPTION(option, name) if (strcmp(str, name) == 0) return option;
|
||||||
FOR_EACH_GC_OPTION(PARSE_OPTION)
|
FOR_EACH_GC_OPTION(PARSE_OPTION)
|
||||||
#undef PARSE_OPTION
|
#undef PARSE_OPTION
|
||||||
|
@ -269,8 +277,8 @@ struct options {
|
||||||
};
|
};
|
||||||
|
|
||||||
static size_t parse_size_t(double value) {
|
static size_t parse_size_t(double value) {
|
||||||
ASSERT(value >= 0);
|
GC_ASSERT(value >= 0);
|
||||||
ASSERT(value <= (size_t) -1);
|
GC_ASSERT(value <= (size_t) -1);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,7 +294,7 @@ static int parse_options(int argc, struct gc_option argv[],
|
||||||
options->parallelism = parse_size_t(argv[i].value);
|
options->parallelism = parse_size_t(argv[i].value);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,8 +310,8 @@ static int parse_options(int argc, struct gc_option argv[],
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int gc_init(int argc, struct gc_option argv[],
|
int gc_init(int argc, struct gc_option argv[],
|
||||||
struct heap **heap, struct mutator **mut) {
|
struct heap **heap, struct mutator **mut) {
|
||||||
GC_ASSERT_EQ(gc_allocator_allocation_pointer_offset(),
|
GC_ASSERT_EQ(gc_allocator_allocation_pointer_offset(),
|
||||||
offsetof(struct semi_space, hp));
|
offsetof(struct semi_space, hp));
|
||||||
GC_ASSERT_EQ(gc_allocator_allocation_limit_offset(),
|
GC_ASSERT_EQ(gc_allocator_allocation_limit_offset(),
|
||||||
|
@ -314,7 +322,7 @@ static int gc_init(int argc, struct gc_option argv[],
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
*mut = calloc(1, sizeof(struct mutator));
|
*mut = calloc(1, sizeof(struct mutator));
|
||||||
if (!*mut) abort();
|
if (!*mut) GC_CRASH();
|
||||||
*heap = mutator_heap(*mut);
|
*heap = mutator_heap(*mut);
|
||||||
|
|
||||||
struct semi_space *space = mutator_semi_space(*mut);
|
struct semi_space *space = mutator_semi_space(*mut);
|
||||||
|
@ -328,30 +336,30 @@ static int gc_init(int argc, struct gc_option argv[],
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gc_mutator_set_roots(struct mutator *mut,
|
void gc_mutator_set_roots(struct mutator *mut,
|
||||||
struct gc_mutator_roots *roots) {
|
struct gc_mutator_roots *roots) {
|
||||||
mut->roots = roots;
|
mut->roots = roots;
|
||||||
}
|
}
|
||||||
static void gc_heap_set_roots(struct heap *heap, struct gc_heap_roots *roots) {
|
void gc_heap_set_roots(struct heap *heap, struct gc_heap_roots *roots) {
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct mutator* gc_init_for_thread(uintptr_t *stack_base,
|
struct mutator* gc_init_for_thread(uintptr_t *stack_base,
|
||||||
struct heap *heap) {
|
struct heap *heap) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Semispace copying collector not appropriate for multithreaded use.\n");
|
"Semispace copying collector not appropriate for multithreaded use.\n");
|
||||||
exit(1);
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
static void gc_finish_for_thread(struct mutator *space) {
|
void gc_finish_for_thread(struct mutator *space) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void* gc_call_without_gc(struct mutator *mut, void* (*f)(void*),
|
void* gc_call_without_gc(struct mutator *mut, void* (*f)(void*),
|
||||||
void *data) {
|
void *data) {
|
||||||
// Can't be threads, then there won't be collection.
|
// Can't be threads, then there won't be collection.
|
||||||
return f(data);
|
return f(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gc_print_stats(struct heap *heap) {
|
void gc_print_stats(struct heap *heap) {
|
||||||
struct semi_space *space = heap_semi_space(heap);
|
struct semi_space *space = heap_semi_space(heap);
|
||||||
printf("Completed %ld collections\n", space->count);
|
printf("Completed %ld collections\n", space->count);
|
||||||
printf("Heap size is %zd\n", space->size);
|
printf("Heap size is %zd\n", space->size);
|
|
@ -85,7 +85,7 @@ static inline void
|
||||||
trace_queue_push(struct trace_queue *q, struct gcobj *p) {
|
trace_queue_push(struct trace_queue *q, struct gcobj *p) {
|
||||||
if (UNLIKELY(q->write - q->read == q->size)) {
|
if (UNLIKELY(q->write - q->read == q->size)) {
|
||||||
if (!trace_queue_grow(q))
|
if (!trace_queue_grow(q))
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
trace_queue_put(q, q->write++, p);
|
trace_queue_put(q, q->write++, p);
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ static inline void
|
||||||
trace_queue_push_many(struct trace_queue *q, struct gcobj **pv, size_t count) {
|
trace_queue_push_many(struct trace_queue *q, struct gcobj **pv, size_t count) {
|
||||||
while (q->size - (q->write - q->read) < count) {
|
while (q->size - (q->write - q->read) < count) {
|
||||||
if (!trace_queue_grow(q))
|
if (!trace_queue_grow(q))
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < count; i++)
|
for (size_t i = 0; i < count; i++)
|
||||||
trace_queue_put(q, q->write++, pv[i]);
|
trace_queue_put(q, q->write++, pv[i]);
|
||||||
|
|
|
@ -19,34 +19,15 @@ static inline void gc_trace_object(void *object,
|
||||||
FOR_EACH_HEAP_OBJECT_KIND(SCAN_OBJECT)
|
FOR_EACH_HEAP_OBJECT_KIND(SCAN_OBJECT)
|
||||||
#undef SCAN_OBJECT
|
#undef SCAN_OBJECT
|
||||||
default:
|
default:
|
||||||
abort ();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct handle;
|
#if GC_PRECISE
|
||||||
struct gc_heap_roots { struct handle *roots; };
|
#include "precise-roots-embedder.h"
|
||||||
struct gc_mutator_roots { struct handle *roots; };
|
#else
|
||||||
|
#include "conservative-roots-embedder.h"
|
||||||
static inline void visit_roots(struct handle *roots,
|
#endif
|
||||||
void (*trace_edge)(struct gc_edge edge,
|
|
||||||
void *trace_data),
|
|
||||||
void *trace_data);
|
|
||||||
|
|
||||||
static inline void gc_trace_mutator_roots(struct gc_mutator_roots *roots,
|
|
||||||
void (*trace_edge)(struct gc_edge edge,
|
|
||||||
void *trace_data),
|
|
||||||
void *trace_data) {
|
|
||||||
if (roots)
|
|
||||||
visit_roots(roots->roots, trace_edge, trace_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void gc_trace_heap_roots(struct gc_heap_roots *roots,
|
|
||||||
void (*trace_edge)(struct gc_edge edge,
|
|
||||||
void *trace_data),
|
|
||||||
void *trace_data) {
|
|
||||||
if (roots)
|
|
||||||
visit_roots(roots->roots, trace_edge, trace_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uintptr_t gc_object_forwarded_nonatomic(void *object) {
|
static inline uintptr_t gc_object_forwarded_nonatomic(void *object) {
|
||||||
uintptr_t tag = *tag_word(object);
|
uintptr_t tag = *tag_word(object);
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#define WHIPPET_ATTRS_H
|
#define WHIPPET_ATTRS_H
|
||||||
|
|
||||||
#include "gc-config.h"
|
#include "gc-config.h"
|
||||||
|
#include "gc-assert.h"
|
||||||
#include "gc-attrs.h"
|
#include "gc-attrs.h"
|
||||||
|
|
||||||
static inline enum gc_allocator_kind gc_allocator_kind(void) {
|
static inline enum gc_allocator_kind gc_allocator_kind(void) {
|
||||||
|
@ -22,7 +23,7 @@ static inline size_t gc_allocator_allocation_limit_offset(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline size_t gc_allocator_freelist_offset(size_t size) {
|
static inline size_t gc_allocator_freelist_offset(size_t size) {
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline size_t gc_allocator_alloc_table_alignment(void) {
|
static inline size_t gc_allocator_alloc_table_alignment(void) {
|
||||||
|
|
|
@ -1,11 +1,3 @@
|
||||||
#ifndef GC_PARALLEL_TRACE
|
|
||||||
#error define GC_PARALLEL_TRACE to 1 or 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef GC_GENERATIONAL
|
|
||||||
#error define GC_GENERATIONAL to 1 or 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
@ -15,11 +7,13 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define GC_API_
|
||||||
|
#include "gc-api.h"
|
||||||
|
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "gc-inline.h"
|
#include "gc-inline.h"
|
||||||
#include "large-object-space.h"
|
#include "large-object-space.h"
|
||||||
#include "precise-roots.h"
|
#if GC_PARALLEL
|
||||||
#if GC_PARALLEL_TRACE
|
|
||||||
#include "parallel-tracer.h"
|
#include "parallel-tracer.h"
|
||||||
#else
|
#else
|
||||||
#include "serial-tracer.h"
|
#include "serial-tracer.h"
|
||||||
|
@ -27,6 +21,12 @@
|
||||||
#include "spin.h"
|
#include "spin.h"
|
||||||
#include "whippet-attrs.h"
|
#include "whippet-attrs.h"
|
||||||
|
|
||||||
|
#if GC_PRECISE
|
||||||
|
#include "precise-roots-embedder.h"
|
||||||
|
#else
|
||||||
|
#error whippet only currently implements precise collection
|
||||||
|
#endif
|
||||||
|
|
||||||
#define GRANULE_SIZE 16
|
#define GRANULE_SIZE 16
|
||||||
#define GRANULE_SIZE_LOG_2 4
|
#define GRANULE_SIZE_LOG_2 4
|
||||||
#define MEDIUM_OBJECT_THRESHOLD 256
|
#define MEDIUM_OBJECT_THRESHOLD 256
|
||||||
|
@ -560,7 +560,7 @@ static inline int mark_space_evacuate_or_mark_object(struct mark_space *space,
|
||||||
case GC_FORWARDING_STATE_NOT_FORWARDED:
|
case GC_FORWARDING_STATE_NOT_FORWARDED:
|
||||||
case GC_FORWARDING_STATE_ABORTED:
|
case GC_FORWARDING_STATE_ABORTED:
|
||||||
// Impossible.
|
// Impossible.
|
||||||
abort();
|
GC_CRASH();
|
||||||
case GC_FORWARDING_STATE_ACQUIRED: {
|
case GC_FORWARDING_STATE_ACQUIRED: {
|
||||||
// We claimed the object successfully; evacuating is up to us.
|
// We claimed the object successfully; evacuating is up to us.
|
||||||
size_t object_granules = mark_space_live_object_granules(metadata);
|
size_t object_granules = mark_space_live_object_granules(metadata);
|
||||||
|
@ -641,7 +641,7 @@ static inline int trace_edge(struct heap *heap, struct gc_edge edge) {
|
||||||
return large_object_space_mark_object(heap_large_object_space(heap),
|
return large_object_space_mark_object(heap_large_object_space(heap),
|
||||||
obj);
|
obj);
|
||||||
else
|
else
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void trace_one(struct gcobj *obj, void *mark_data) {
|
static inline void trace_one(struct gcobj *obj, void *mark_data) {
|
||||||
|
@ -836,7 +836,7 @@ static void mutator_mark_buf_grow(struct mutator_mark_buf *buf) {
|
||||||
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
|
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
|
||||||
if (mem == MAP_FAILED) {
|
if (mem == MAP_FAILED) {
|
||||||
perror("allocating mutator mark buffer failed");
|
perror("allocating mutator mark buffer failed");
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
if (old_bytes) {
|
if (old_bytes) {
|
||||||
memcpy(mem, buf->objects, old_bytes);
|
memcpy(mem, buf->objects, old_bytes);
|
||||||
|
@ -905,11 +905,11 @@ static int mutator_should_mark_while_stopping(struct mutator *mut) {
|
||||||
return heap_should_mark_while_stopping(mutator_heap(mut));
|
return heap_should_mark_while_stopping(mutator_heap(mut));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gc_mutator_set_roots(struct mutator *mut,
|
void gc_mutator_set_roots(struct mutator *mut,
|
||||||
struct gc_mutator_roots *roots) {
|
struct gc_mutator_roots *roots) {
|
||||||
mut->roots = roots;
|
mut->roots = roots;
|
||||||
}
|
}
|
||||||
static void gc_heap_set_roots(struct heap *heap, struct gc_heap_roots *roots) {
|
void gc_heap_set_roots(struct heap *heap, struct gc_heap_roots *roots) {
|
||||||
heap->roots = roots;
|
heap->roots = roots;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1213,7 +1213,7 @@ static void detect_out_of_memory(struct heap *heap) {
|
||||||
// be able to yield more space: out of memory.
|
// be able to yield more space: out of memory.
|
||||||
fprintf(stderr, "ran out of space, heap size %zu (%zu slabs)\n",
|
fprintf(stderr, "ran out of space, heap size %zu (%zu slabs)\n",
|
||||||
heap->size, mark_space->nslabs);
|
heap->size, mark_space->nslabs);
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
|
|
||||||
static double clamp_major_gc_yield_threshold(struct heap *heap,
|
static double clamp_major_gc_yield_threshold(struct heap *heap,
|
||||||
|
@ -1762,7 +1762,7 @@ static void trigger_collection(struct mutator *mut) {
|
||||||
heap_unlock(heap);
|
heap_unlock(heap);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void* gc_allocate_large(struct mutator *mut, size_t size) {
|
void* gc_allocate_large(struct mutator *mut, size_t size) {
|
||||||
struct heap *heap = mutator_heap(mut);
|
struct heap *heap = mutator_heap(mut);
|
||||||
struct large_object_space *space = heap_large_object_space(heap);
|
struct large_object_space *space = heap_large_object_space(heap);
|
||||||
|
|
||||||
|
@ -1781,13 +1781,13 @@ static void* gc_allocate_large(struct mutator *mut, size_t size) {
|
||||||
|
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
perror("weird: we have the space but mmap didn't work");
|
perror("weird: we have the space but mmap didn't work");
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void* gc_allocate_small(struct mutator *mut, size_t size) {
|
void* gc_allocate_small(struct mutator *mut, size_t size) {
|
||||||
GC_ASSERT(size > 0); // allocating 0 bytes would be silly
|
GC_ASSERT(size > 0); // allocating 0 bytes would be silly
|
||||||
GC_ASSERT(size <= gc_allocator_large_threshold());
|
GC_ASSERT(size <= gc_allocator_large_threshold());
|
||||||
size = align_up(size, GRANULE_SIZE);
|
size = align_up(size, GRANULE_SIZE);
|
||||||
|
@ -1816,7 +1816,7 @@ static void* gc_allocate_small(struct mutator *mut, size_t size) {
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void* gc_allocate_pointerless(struct mutator *mut, size_t size) {
|
void* gc_allocate_pointerless(struct mutator *mut, size_t size) {
|
||||||
return gc_allocate(mut, size);
|
return gc_allocate(mut, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1832,7 +1832,7 @@ static void dump_available_gc_options(void) {
|
||||||
fprintf(stderr, "\n");
|
fprintf(stderr, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
static int gc_option_from_string(const char *str) {
|
int gc_option_from_string(const char *str) {
|
||||||
#define PARSE_OPTION(option, name) if (strcmp(str, name) == 0) return option;
|
#define PARSE_OPTION(option, name) if (strcmp(str, name) == 0) return option;
|
||||||
FOR_EACH_GC_OPTION(PARSE_OPTION)
|
FOR_EACH_GC_OPTION(PARSE_OPTION)
|
||||||
#undef PARSE_OPTION
|
#undef PARSE_OPTION
|
||||||
|
@ -1869,7 +1869,7 @@ static int parse_options(int argc, struct gc_option argv[],
|
||||||
options->parallelism = parse_size_t(argv[i].value);
|
options->parallelism = parse_size_t(argv[i].value);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
abort();
|
GC_CRASH();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1916,7 +1916,7 @@ static int heap_init(struct heap *heap, struct options *options) {
|
||||||
heap->size = options->fixed_heap_size;
|
heap->size = options->fixed_heap_size;
|
||||||
|
|
||||||
if (!tracer_init(heap, options->parallelism))
|
if (!tracer_init(heap, options->parallelism))
|
||||||
abort();
|
GC_CRASH();
|
||||||
|
|
||||||
heap->fragmentation_low_threshold = 0.05;
|
heap->fragmentation_low_threshold = 0.05;
|
||||||
heap->fragmentation_high_threshold = 0.10;
|
heap->fragmentation_high_threshold = 0.10;
|
||||||
|
@ -1960,7 +1960,7 @@ static int mark_space_init(struct mark_space *space, struct heap *heap) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int gc_init(int argc, struct gc_option argv[],
|
int gc_init(int argc, struct gc_option argv[],
|
||||||
struct heap **heap, struct mutator **mut) {
|
struct heap **heap, struct mutator **mut) {
|
||||||
GC_ASSERT_EQ(gc_allocator_small_granule_size(), GRANULE_SIZE);
|
GC_ASSERT_EQ(gc_allocator_small_granule_size(), GRANULE_SIZE);
|
||||||
GC_ASSERT_EQ(gc_allocator_large_threshold(), LARGE_OBJECT_THRESHOLD);
|
GC_ASSERT_EQ(gc_allocator_large_threshold(), LARGE_OBJECT_THRESHOLD);
|
||||||
|
@ -1982,10 +1982,10 @@ static int gc_init(int argc, struct gc_option argv[],
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
*heap = calloc(1, sizeof(struct heap));
|
*heap = calloc(1, sizeof(struct heap));
|
||||||
if (!*heap) abort();
|
if (!*heap) GC_CRASH();
|
||||||
|
|
||||||
if (!heap_init(*heap, &options))
|
if (!heap_init(*heap, &options))
|
||||||
abort();
|
GC_CRASH();
|
||||||
|
|
||||||
struct mark_space *space = heap_mark_space(*heap);
|
struct mark_space *space = heap_mark_space(*heap);
|
||||||
if (!mark_space_init(space, *heap)) {
|
if (!mark_space_init(space, *heap)) {
|
||||||
|
@ -1995,24 +1995,24 @@ static int gc_init(int argc, struct gc_option argv[],
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!large_object_space_init(heap_large_object_space(*heap), *heap))
|
if (!large_object_space_init(heap_large_object_space(*heap), *heap))
|
||||||
abort();
|
GC_CRASH();
|
||||||
|
|
||||||
*mut = calloc(1, sizeof(struct mutator));
|
*mut = calloc(1, sizeof(struct mutator));
|
||||||
if (!*mut) abort();
|
if (!*mut) GC_CRASH();
|
||||||
add_mutator(*heap, *mut);
|
add_mutator(*heap, *mut);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct mutator* gc_init_for_thread(uintptr_t *stack_base,
|
struct mutator* gc_init_for_thread(uintptr_t *stack_base,
|
||||||
struct heap *heap) {
|
struct heap *heap) {
|
||||||
struct mutator *ret = calloc(1, sizeof(struct mutator));
|
struct mutator *ret = calloc(1, sizeof(struct mutator));
|
||||||
if (!ret)
|
if (!ret)
|
||||||
abort();
|
GC_CRASH();
|
||||||
add_mutator(heap, ret);
|
add_mutator(heap, ret);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gc_finish_for_thread(struct mutator *mut) {
|
void gc_finish_for_thread(struct mutator *mut) {
|
||||||
remove_mutator(mutator_heap(mut), mut);
|
remove_mutator(mutator_heap(mut), mut);
|
||||||
mutator_mark_buf_destroy(&mut->mark_buf);
|
mutator_mark_buf_destroy(&mut->mark_buf);
|
||||||
free(mut);
|
free(mut);
|
||||||
|
@ -2042,7 +2042,7 @@ static void reactivate_mutator(struct heap *heap, struct mutator *mut) {
|
||||||
heap_unlock(heap);
|
heap_unlock(heap);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void* gc_call_without_gc(struct mutator *mut,
|
void* gc_call_without_gc(struct mutator *mut,
|
||||||
void* (*f)(void*),
|
void* (*f)(void*),
|
||||||
void *data) {
|
void *data) {
|
||||||
struct heap *heap = mutator_heap(mut);
|
struct heap *heap = mutator_heap(mut);
|
||||||
|
@ -2052,7 +2052,7 @@ static void* gc_call_without_gc(struct mutator *mut,
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gc_print_stats(struct heap *heap) {
|
void gc_print_stats(struct heap *heap) {
|
||||||
printf("Completed %ld collections (%ld major)\n",
|
printf("Completed %ld collections (%ld major)\n",
|
||||||
heap->count, heap->count - heap->minor_count);
|
heap->count, heap->count - heap->minor_count);
|
||||||
printf("Heap size with overhead is %zd (%zu slabs)\n",
|
printf("Heap size with overhead is %zd (%zu slabs)\n",
|
Loading…
Add table
Add a link
Reference in a new issue