diff --git a/Makefile b/Makefile index 3cfc68013..ee20d5647 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ BUILD_CFLAGS=$(BUILD_CFLAGS_$(or $(BUILD),$(DEFAULT_BUILD))) CC=gcc CFLAGS=-Wall -flto -fno-strict-aliasing -fvisibility=hidden -Wno-unused $(BUILD_CFLAGS) -INCLUDES=-I. +INCLUDES=-Iapi LDFLAGS=-lpthread -flto COMPILE=$(CC) $(CFLAGS) $(INCLUDES) PLATFORM=gnu-linux @@ -38,113 +38,113 @@ ALL_TESTS=$(foreach COLLECTOR,$(COLLECTORS),$(addprefix $(COLLECTOR)-,$(TESTS))) all: $(ALL_TESTS) -gc-platform.o: gc-platform.h gc-platform-$(PLATFORM).c gc-visibility.h - $(COMPILE) -o $@ -c gc-platform-$(PLATFORM).c +gc-platform.o: src/gc-platform.h src/gc-platform-$(PLATFORM).c api/gc-visibility.h + $(COMPILE) -o $@ -c src/gc-platform-$(PLATFORM).c -gc-stack.o: gc-stack.c +gc-stack.o: src/gc-stack.c $(COMPILE) -o $@ -c $< -gc-options.o: gc-options.c gc-options.h gc-options-internal.h +gc-options.o: src/gc-options.c api/gc-options.h src/gc-options-internal.h $(COMPILE) -o $@ -c $< -gc-ephemeron-%.o: gc-ephemeron.c gc-ephemeron.h gc-ephemeron-internal.h %-embedder.h - $(COMPILE) -include $*-embedder.h -o $@ -c $< +gc-ephemeron-%.o: src/gc-ephemeron.c api/gc-ephemeron.h src/gc-ephemeron-internal.h benchmarks/%-embedder.h + $(COMPILE) -include benchmarks/$*-embedder.h -o $@ -c $< -bdw-%-gc.o: bdw.c %-embedder.h %.c - $(COMPILE) -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 `pkg-config --cflags bdw-gc` -include $*-embedder.h -o $@ -c bdw.c -bdw-%.o: bdw.c %.c - $(COMPILE) -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 -include bdw-attrs.h -o $@ -c $*.c +bdw-%-gc.o: src/bdw.c benchmarks/%-embedder.h benchmarks/%.c + $(COMPILE) -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 `pkg-config --cflags bdw-gc` -include benchmarks/$*-embedder.h -o $@ -c src/bdw.c +bdw-%.o: src/bdw.c benchmarks/%.c + $(COMPILE) -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 -include api/bdw-attrs.h -o $@ -c benchmarks/$*.c bdw-%: bdw-%.o bdw-%-gc.o gc-stack.o gc-options.o gc-platform.o gc-ephemeron-%.o $(CC) $(LDFLAGS) `pkg-config --libs bdw-gc` -o $@ $^ -semi-%-gc.o: semi.c %-embedder.h large-object-space.h assert.h debug.h %.c - $(COMPILE) -DGC_PRECISE_ROOTS=1 -include $*-embedder.h -o $@ -c semi.c -semi-%.o: semi.c %.c - $(COMPILE) -DGC_PRECISE_ROOTS=1 -include semi-attrs.h -o $@ -c $*.c +semi-%-gc.o: src/semi.c benchmarks/%-embedder.h + $(COMPILE) -DGC_PRECISE_ROOTS=1 -include benchmarks/$*-embedder.h -o $@ -c src/semi.c +semi-%.o: src/semi.c benchmarks/%.c + $(COMPILE) -DGC_PRECISE_ROOTS=1 -include api/semi-attrs.h -o $@ -c benchmarks/$*.c semi-%: semi-%.o semi-%-gc.o gc-stack.o gc-options.o gc-platform.o gc-ephemeron-%.o $(CC) $(LDFLAGS) -o $@ $^ -whippet-%-gc.o: whippet.c %-embedder.h large-object-space.h serial-tracer.h assert.h debug.h heap-objects.h %.c - $(COMPILE) -DGC_PRECISE_ROOTS=1 -include $*-embedder.h -o $@ -c whippet.c -whippet-%.o: whippet.c %.c - $(COMPILE) -DGC_PRECISE_ROOTS=1 -include whippet-attrs.h -o $@ -c $*.c +whippet-%-gc.o: src/whippet.c benchmarks/%-embedder.h + $(COMPILE) -DGC_PRECISE_ROOTS=1 -include benchmarks/$*-embedder.h -o $@ -c src/whippet.c +whippet-%.o: src/whippet.c benchmarks/%.c + $(COMPILE) -DGC_PRECISE_ROOTS=1 -include api/whippet-attrs.h -o $@ -c benchmarks/$*.c whippet-%: whippet-%.o whippet-%-gc.o gc-stack.o gc-options.o gc-platform.o gc-ephemeron-%.o $(CC) $(LDFLAGS) -o $@ $^ -stack-conservative-whippet-%-gc.o: whippet.c %-embedder.h large-object-space.h serial-tracer.h assert.h debug.h heap-objects.h %.c - $(COMPILE) -DGC_CONSERVATIVE_ROOTS=1 -include $*-embedder.h -o $@ -c whippet.c -stack-conservative-whippet-%.o: whippet.c %.c - $(COMPILE) -DGC_CONSERVATIVE_ROOTS=1 -include whippet-attrs.h -o $@ -c $*.c +stack-conservative-whippet-%-gc.o: src/whippet.c benchmarks/%-embedder.h + $(COMPILE) -DGC_CONSERVATIVE_ROOTS=1 -include benchmarks/$*-embedder.h -o $@ -c src/whippet.c +stack-conservative-whippet-%.o: src/whippet.c benchmarks/%.c + $(COMPILE) -DGC_CONSERVATIVE_ROOTS=1 -include api/whippet-attrs.h -o $@ -c benchmarks/$*.c stack-conservative-whippet-%: stack-conservative-whippet-%.o stack-conservative-whippet-%-gc.o gc-stack.o gc-options.o gc-platform.o gc-ephemeron-%.o $(CC) $(LDFLAGS) -o $@ $^ -heap-conservative-whippet-%-gc.o: whippet.c %-embedder.h large-object-space.h serial-tracer.h assert.h debug.h heap-objects.h %.c - $(COMPILE) -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 -include $*-embedder.h -o $@ -c whippet.c -heap-conservative-whippet-%.o: whippet.c %.c - $(COMPILE) -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 -include whippet-attrs.h -o $@ -c $*.c +heap-conservative-whippet-%-gc.o: src/whippet.c benchmarks/%-embedder.h + $(COMPILE) -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 -include benchmarks/$*-embedder.h -o $@ -c src/whippet.c +heap-conservative-whippet-%.o: src/whippet.c benchmarks/%.c + $(COMPILE) -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 -include api/whippet-attrs.h -o $@ -c benchmarks/$*.c heap-conservative-whippet-%: heap-conservative-whippet-%.o heap-conservative-whippet-%-gc.o gc-stack.o gc-options.o gc-platform.o gc-ephemeron-%.o $(CC) $(LDFLAGS) -o $@ $^ -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=1 -DGC_PRECISE_ROOTS=1 -include $*-embedder.h -o $@ -c whippet.c -parallel-whippet-%.o: whippet.c %.c - $(COMPILE) -DGC_PARALLEL=1 -DGC_PRECISE_ROOTS=1 -include whippet-attrs.h -o $@ -c $*.c +parallel-whippet-%-gc.o: src/whippet.c benchmarks/%-embedder.h + $(COMPILE) -DGC_PARALLEL=1 -DGC_PRECISE_ROOTS=1 -include benchmarks/$*-embedder.h -o $@ -c src/whippet.c +parallel-whippet-%.o: src/whippet.c benchmarks/%.c + $(COMPILE) -DGC_PARALLEL=1 -DGC_PRECISE_ROOTS=1 -include api/whippet-attrs.h -o $@ -c benchmarks/$*.c parallel-whippet-%: parallel-whippet-%.o parallel-whippet-%-gc.o gc-stack.o gc-options.o gc-platform.o gc-ephemeron-%.o $(CC) $(LDFLAGS) -o $@ $^ -stack-conservative-parallel-whippet-%-gc.o: whippet.c %-embedder.h large-object-space.h serial-tracer.h assert.h debug.h heap-objects.h %.c - $(COMPILE) -DGC_PARALLEL=1 -DGC_CONSERVATIVE_ROOTS=1 -include $*-embedder.h -o $@ -c whippet.c -stack-conservative-parallel-whippet-%.o: whippet.c %.c - $(COMPILE) -DGC_PARALLEL=1 -DGC_CONSERVATIVE_ROOTS=1 -include whippet-attrs.h -o $@ -c $*.c +stack-conservative-parallel-whippet-%-gc.o: src/whippet.c benchmarks/%-embedder.h + $(COMPILE) -DGC_PARALLEL=1 -DGC_CONSERVATIVE_ROOTS=1 -include benchmarks/$*-embedder.h -o $@ -c src/whippet.c +stack-conservative-parallel-whippet-%.o: src/whippet.c benchmarks/%.c + $(COMPILE) -DGC_PARALLEL=1 -DGC_CONSERVATIVE_ROOTS=1 -include api/whippet-attrs.h -o $@ -c benchmarks/$*.c stack-conservative-parallel-whippet-%: stack-conservative-parallel-whippet-%.o stack-conservative-parallel-whippet-%-gc.o gc-stack.o gc-options.o gc-platform.o gc-ephemeron-%.o $(CC) $(LDFLAGS) -o $@ $^ -heap-conservative-parallel-whippet-%-gc.o: whippet.c %-embedder.h large-object-space.h serial-tracer.h assert.h debug.h heap-objects.h %.c - $(COMPILE) -DGC_PARALLEL=1 -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 -include $*-embedder.h -o $@ -c whippet.c -heap-conservative-parallel-whippet-%.o: whippet.c %.c - $(COMPILE) -DGC_PARALLEL=1 -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 -DGC_FULLY_CONSERVATIVE=1 -include whippet-attrs.h -o $@ -c $*.c +heap-conservative-parallel-whippet-%-gc.o: src/whippet.c benchmarks/%-embedder.h + $(COMPILE) -DGC_PARALLEL=1 -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 -include benchmarks/$*-embedder.h -o $@ -c src/whippet.c +heap-conservative-parallel-whippet-%.o: src/whippet.c benchmarks/%.c + $(COMPILE) -DGC_PARALLEL=1 -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 -DGC_FULLY_CONSERVATIVE=1 -include api/whippet-attrs.h -o $@ -c benchmarks/$*.c heap-conservative-parallel-whippet-%: heap-conservative-parallel-whippet-%.o heap-conservative-parallel-whippet-%-gc.o gc-stack.o gc-options.o gc-platform.o gc-ephemeron-%.o $(CC) $(LDFLAGS) -o $@ $^ -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=1 -DGC_PRECISE_ROOTS=1 -include $*-embedder.h -o $@ -c whippet.c -generational-whippet-%.o: whippet.c %.c - $(COMPILE) -DGC_GENERATIONAL=1 -DGC_PRECISE_ROOTS=1 -include whippet-attrs.h -o $@ -c $*.c +generational-whippet-%-gc.o: src/whippet.c benchmarks/%-embedder.h + $(COMPILE) -DGC_GENERATIONAL=1 -DGC_PRECISE_ROOTS=1 -include benchmarks/$*-embedder.h -o $@ -c src/whippet.c +generational-whippet-%.o: src/whippet.c benchmarks/%.c + $(COMPILE) -DGC_GENERATIONAL=1 -DGC_PRECISE_ROOTS=1 -include api/whippet-attrs.h -o $@ -c benchmarks/$*.c generational-whippet-%: generational-whippet-%.o generational-whippet-%-gc.o gc-stack.o gc-options.o gc-platform.o gc-ephemeron-%.o $(CC) $(LDFLAGS) -o $@ $^ -stack-conservative-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=1 -DGC_CONSERVATIVE_ROOTS=1 -include $*-embedder.h -o $@ -c whippet.c -stack-conservative-generational-whippet-%.o: whippet.c %.c - $(COMPILE) -DGC_GENERATIONAL=1 -DGC_CONSERVATIVE_ROOTS=1 -include whippet-attrs.h -o $@ -c $*.c +stack-conservative-generational-whippet-%-gc.o: src/whippet.c benchmarks/%-embedder.h + $(COMPILE) -DGC_GENERATIONAL=1 -DGC_CONSERVATIVE_ROOTS=1 -include benchmarks/$*-embedder.h -o $@ -c src/whippet.c +stack-conservative-generational-whippet-%.o: src/whippet.c benchmarks/%.c + $(COMPILE) -DGC_GENERATIONAL=1 -DGC_CONSERVATIVE_ROOTS=1 -include api/whippet-attrs.h -o $@ -c benchmarks/$*.c stack-conservative-generational-whippet-%: stack-conservative-generational-whippet-%.o stack-conservative-generational-whippet-%-gc.o gc-stack.o gc-options.o gc-platform.o gc-ephemeron-%.o $(CC) $(LDFLAGS) -o $@ $^ -heap-conservative-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=1 -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 -include $*-embedder.h -o $@ -c whippet.c -heap-conservative-generational-whippet-%.o: whippet.c %.c - $(COMPILE) -DGC_GENERATIONAL=1 -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 -include whippet-attrs.h -o $@ -c $*.c +heap-conservative-generational-whippet-%-gc.o: src/whippet.c benchmarks/%-embedder.h + $(COMPILE) -DGC_GENERATIONAL=1 -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 -include benchmarks/$*-embedder.h -o $@ -c src/whippet.c +heap-conservative-generational-whippet-%.o: src/whippet.c benchmarks/%.c + $(COMPILE) -DGC_GENERATIONAL=1 -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 -include api/whippet-attrs.h -o $@ -c benchmarks/$*.c heap-conservative-generational-whippet-%: heap-conservative-generational-whippet-%.o heap-conservative-generational-whippet-%-gc.o gc-stack.o gc-options.o gc-platform.o gc-ephemeron-%.o $(CC) $(LDFLAGS) -o $@ $^ -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=1 -DGC_GENERATIONAL=1 -DGC_PRECISE_ROOTS=1 -include $*-embedder.h -o $@ -c whippet.c -parallel-generational-whippet-%.o: whippet.c %.c - $(COMPILE) -DGC_PARALLEL=1 -DGC_GENERATIONAL=1 -DGC_PRECISE_ROOTS=1 -include whippet-attrs.h -o $@ -c $*.c +parallel-generational-whippet-%-gc.o: src/whippet.c benchmarks/%-embedder.h + $(COMPILE) -DGC_PARALLEL=1 -DGC_GENERATIONAL=1 -DGC_PRECISE_ROOTS=1 -include benchmarks/$*-embedder.h -o $@ -c src/whippet.c +parallel-generational-whippet-%.o: src/whippet.c benchmarks/%.c + $(COMPILE) -DGC_PARALLEL=1 -DGC_GENERATIONAL=1 -DGC_PRECISE_ROOTS=1 -include api/whippet-attrs.h -o $@ -c benchmarks/$*.c parallel-generational-whippet-%: parallel-generational-whippet-%.o parallel-generational-whippet-%-gc.o gc-stack.o gc-options.o gc-platform.o gc-ephemeron-%.o $(CC) $(LDFLAGS) -o $@ $^ -stack-conservative-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=1 -DGC_GENERATIONAL=1 -DGC_CONSERVATIVE_ROOTS=1 -include $*-embedder.h -o $@ -c whippet.c -stack-conservative-parallel-generational-whippet-%.o: whippet.c %.c - $(COMPILE) -DGC_PARALLEL=1 -DGC_GENERATIONAL=1 -DGC_CONSERVATIVE_ROOTS=1 -include whippet-attrs.h -o $@ -c $*.c +stack-conservative-parallel-generational-whippet-%-gc.o: src/whippet.c benchmarks/%-embedder.h + $(COMPILE) -DGC_PARALLEL=1 -DGC_GENERATIONAL=1 -DGC_CONSERVATIVE_ROOTS=1 -include benchmarks/$*-embedder.h -o $@ -c src/whippet.c +stack-conservative-parallel-generational-whippet-%.o: src/whippet.c benchmarks/%.c + $(COMPILE) -DGC_PARALLEL=1 -DGC_GENERATIONAL=1 -DGC_CONSERVATIVE_ROOTS=1 -include api/whippet-attrs.h -o $@ -c benchmarks/$*.c stack-conservative-parallel-generational-whippet-%: stack-conservative-parallel-generational-whippet-%.o stack-conservative-parallel-generational-whippet-%-gc.o gc-stack.o gc-options.o gc-platform.o gc-ephemeron-%.o $(CC) $(LDFLAGS) -o $@ $^ -heap-conservative-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=1 -DGC_GENERATIONAL=1 -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 -include $*-embedder.h -o $@ -c whippet.c -heap-conservative-parallel-generational-whippet-%.o: whippet.c %.c - $(COMPILE) -DGC_PARALLEL=1 -DGC_GENERATIONAL=1 -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 -include whippet-attrs.h -o $@ -c $*.c +heap-conservative-parallel-generational-whippet-%-gc.o: src/whippet.c benchmarks/%-embedder.h + $(COMPILE) -DGC_PARALLEL=1 -DGC_GENERATIONAL=1 -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 -include benchmarks/$*-embedder.h -o $@ -c src/whippet.c +heap-conservative-parallel-generational-whippet-%.o: src/whippet.c benchmarks/%.c + $(COMPILE) -DGC_PARALLEL=1 -DGC_GENERATIONAL=1 -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 -include api/whippet-attrs.h -o $@ -c benchmarks/$*.c heap-conservative-parallel-generational-whippet-%: heap-conservative-parallel-generational-whippet-%.o heap-conservative-parallel-generational-whippet-%-gc.o gc-stack.o gc-options.o gc-platform.o gc-ephemeron-%.o $(CC) $(LDFLAGS) -o $@ $^ diff --git a/README.md b/README.md index a4dcdbd21..e1ac66150 100644 --- a/README.md +++ b/README.md @@ -4,139 +4,37 @@ This repository is for development of Whippet, a new garbage collector implementation, eventually for use in [Guile Scheme](https://gnu.org/s/guile). -## Design +Whippet is an embed-only C library, designed to be copied into a +program's source tree. It exposes an abstract C API for managed memory +allocation, and provides a number of implementations of that API. -Whippet is mainly a mark-region collector, like -[Immix](http://users.cecs.anu.edu.au/~steveb/pubs/papers/immix-pldi-2008.pdf). -See also the lovely detailed [Rust -implementation](http://users.cecs.anu.edu.au/~steveb/pubs/papers/rust-ismm-2016.pdf). +One of the implementations is also called "whippet", and is the +motivation for creating this library. For a detailed introduction, see +[Whippet: Towards a new local +maximum](https://wingolog.org/archives/2023/02/07/whippet-towards-a-new-local-maximum), +a talk given at FOSDEM 2023. -To a first approximation, Whippet is a whole-heap Immix collector with a -large object space on the side. See the Immix paper for full details, -but basically Immix divides the heap into 32kB blocks, and then divides -those blocks into 128B lines. An Immix allocation never spans blocks; -allocations larger than 8kB go into a separate large object space. -Mutators request blocks from the global store and allocate into those -blocks using bump-pointer allocation. When all blocks are consumed, -Immix stops the world and traces the object graph, marking objects but -also the lines that objects are on. After marking, blocks contain some -lines with live objects and others that are completely free. Spans of -free lines are called holes. When a mutator gets a recycled block from -the global block store, it allocates into those holes. Also, sometimes -Immix can choose to evacuate rather than mark. Bump-pointer-into-holes -allocation is quite compatible with conservative roots, so it's an -interesting option for Guile, which has a lot of legacy C API users. +## Documentation -The essential difference of Whippet from Immix stems from a simple -observation: Immix needs a side table of line mark bytes and also a mark -bit or bits in each object (or in a side table). But if instead you -choose to store mark bytes instead of bits (for concurrency reasons) in -a side table, with one mark byte per granule (unit of allocation, -perhaps 16 bytes), then you effectively have a line mark table where the -granule size is the line size. You can bump-pointer allocate into holes -in the mark byte table. + * [Design](./doc/design.md): What's the general idea? + * [Manual](./doc/manual.md): How do you get your program to use + Whippet? What is the API? + * [Guile](./doc/guile.md): Some notes on a potential rebase of Guile on + top of Whippet. -You might think this is a bad tradeoff, and perhaps it is: I don't know -yet. If your granule size is two pointers, then one mark byte per -granule is 6.25% overhead on 64-bit, or 12.5% on 32-bit. Especially on -32-bit, it's a lot! On the other hand, instead of the worst case of one -survivor object wasting a line (or two, in the case of conservative line -marking), granule-size-is-line-size instead wastes nothing. Also, you -don't need GC bits in the object itself, and you can use the mark byte -array to record the object end, so that finding holes in a block can -just read the mark table and can avoid looking at object memory. +## Source repository structure -Other ideas in Whippet: - - * Minimize stop-the-world phase via parallel marking and punting all - sweeping to mutators - - * Enable mutator parallelism via lock-free block acquisition and lazy - statistics collation - - * Allocate block space using aligned 4 MB slabs, with embedded metadata - to allow metadata bytes, slab headers, and block metadata to be - located via address arithmetic - - * Facilitate conservative collection via mark byte array, oracle for - "does this address start an object" - - * Enable in-place generational collection via card table with one entry - per 256B or so - - * Enable concurrent marking by having three mark bit states (dead, - survivor, marked) that rotate at each collection, and sweeping a - block clears metadata for dead objects; but concurrent marking and - associated SATB barrier not yet implemented - -## What's there - -This repository is a workspace for Whippet implementation. As such, it -has files implementing Whippet itself. It also has some benchmarks to -use in optimizing Whippet: - - - [`mt-gcbench.c`](./mt-gcbench.c): The multi-threaded [GCBench - benchmark](https://hboehm.info/gc/gc_bench.html). An old but - standard benchmark that allocates different sizes of binary trees. - As parameters it takes a heap multiplier and a number of mutator - threads. We analytically compute the peak amount of live data and - then size the GC heap as a multiplier of that size. It has a peak - heap consumption of 10 MB or so per mutator thread: not very large. - At a 2x heap multiplier, it causes about 30 collections for the - whippet collector, and runs somewhere around 200-400 milliseconds in - single-threaded mode, on the machines I have in 2022. For low thread - counts, the GCBench benchmark is small; but then again many Guile - processes also are quite short-lived, so perhaps it is useful to - ensure that small heaps remain lightweight. - - To stress Whippet's handling of fragmentation, we modified this - benchmark to intersperse pseudorandomly-sized holes between tree - nodes. - - - [`quads.c`](./quads.c): A synthetic benchmark that allocates quad - trees. The mutator begins by allocating one long-lived tree of depth - N, and then allocates 13% of the heap in depth-3 trees, 20 times, - simulating a fixed working set and otherwise an allocation-heavy - workload. By observing the times to allocate 13% of the heap in - garbage we can infer mutator overheads, and also note the variance - for the cycles in which GC hits. - -The repository has two other collector implementations, to appropriately -situate Whippet's performance in context: - - - `bdw.h`: The external BDW-GC conservative parallel stop-the-world - mark-sweep segregated-fits collector with lazy sweeping. - - `semi.h`: Semispace copying collector. - - `whippet.h`: The whippet collector. Two different marking - implementations: single-threaded and parallel. Generational and - non-generational variants, also. - -## Guile - -If the Whippet collector works out, it could replace Guile's garbage -collector. Guile currently uses BDW-GC. Guile has a widely used C API -and implements part of its run-time in C. For this reason it may be -infeasible to require precise enumeration of GC roots -- we may need to -allow GC roots to be conservatively identified from data sections and -from stacks. Such conservative roots would be pinned, but other objects -can be moved by the collector if it chooses to do so. We assume that -object references within a heap object can be precisely identified. -(However, Guile currently uses BDW-GC in its default configuration, -which scans for references conservatively even on the heap.) - -The existing C API allows direct access to mutable object fields, -without the mediation of read or write barriers. Therefore it may be -impossible to switch to collector strategies that need barriers, such as -generational or concurrent collectors. However, we shouldn't write off -this possibility entirely; an ideal replacement for Guile's GC will -offer the possibility of migration to other GC designs without imposing -new requirements on C API users in the initial phase. - -In this regard, the Whippet experiment also has the goal of identifying -a smallish GC abstraction in Guile, so that we might consider evolving -GC implementation in the future without too much pain. If we switch -away from BDW-GC, we should be able to evaluate that it's a win for a -large majority of use cases. + * [api/](./api/): The user-facing API. Also, the "embedder API"; see + the [manual](./doc/manual.md) for more. + * [doc/](./doc/): Documentation, such as it is. + * [src/](./src/): The actual GC implementation. The specific + implementations of the Whippet API are [`semi.c`](./src/semi.c), a + semi-space collector; [`bdw.c`](./src/bdw.c), the third-party + [BDW-GC](https://github.com/ivmai/bdwgc) conservative parallel + stop-the-world mark-sweep segregated-fits collector with lazy + sweeping; and [`whippet.c`](./src/whippet.c), the whippet collector. + * [benchmarks/](./benchmarks/): Benchmarks. A work in progress. + * [test/](./test/): A dusty attic of minimal testing. ## To do @@ -166,14 +64,8 @@ size. It would be nice if whippet-gc turns out to have this property. ## License -gcbench.c, MT_GCBench.c, and MT_GCBench2.c are from -https://hboehm.info/gc/gc_bench/ and have a somewhat unclear license. I -have modified GCBench significantly so that I can slot in different GC -implementations. The GC implementations themselves are available under -a MIT-style license, the text of which follows: - ``` -Copyright (c) 2022 Andy Wingo +Copyright (c) 2022-2023 Andy Wingo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -194,3 +86,6 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` + +Note that some benchmarks have other licenses; see +[`benchmarks/README.md`](./benchmarks/README.md) for more. diff --git a/bdw-attrs.h b/api/bdw-attrs.h similarity index 100% rename from bdw-attrs.h rename to api/bdw-attrs.h diff --git a/gc-api.h b/api/gc-api.h similarity index 100% rename from gc-api.h rename to api/gc-api.h diff --git a/gc-assert.h b/api/gc-assert.h similarity index 100% rename from gc-assert.h rename to api/gc-assert.h diff --git a/gc-attrs.h b/api/gc-attrs.h similarity index 100% rename from gc-attrs.h rename to api/gc-attrs.h diff --git a/gc-config.h b/api/gc-config.h similarity index 100% rename from gc-config.h rename to api/gc-config.h diff --git a/gc-conservative-ref.h b/api/gc-conservative-ref.h similarity index 100% rename from gc-conservative-ref.h rename to api/gc-conservative-ref.h diff --git a/gc-edge.h b/api/gc-edge.h similarity index 100% rename from gc-edge.h rename to api/gc-edge.h diff --git a/gc-embedder-api.h b/api/gc-embedder-api.h similarity index 100% rename from gc-embedder-api.h rename to api/gc-embedder-api.h diff --git a/gc-ephemeron.h b/api/gc-ephemeron.h similarity index 100% rename from gc-ephemeron.h rename to api/gc-ephemeron.h diff --git a/gc-forwarding.h b/api/gc-forwarding.h similarity index 100% rename from gc-forwarding.h rename to api/gc-forwarding.h diff --git a/gc-inline.h b/api/gc-inline.h similarity index 100% rename from gc-inline.h rename to api/gc-inline.h diff --git a/gc-options.h b/api/gc-options.h similarity index 100% rename from gc-options.h rename to api/gc-options.h diff --git a/gc-ref.h b/api/gc-ref.h similarity index 100% rename from gc-ref.h rename to api/gc-ref.h diff --git a/gc-visibility.h b/api/gc-visibility.h similarity index 100% rename from gc-visibility.h rename to api/gc-visibility.h diff --git a/semi-attrs.h b/api/semi-attrs.h similarity index 100% rename from semi-attrs.h rename to api/semi-attrs.h diff --git a/whippet-attrs.h b/api/whippet-attrs.h similarity index 100% rename from whippet-attrs.h rename to api/whippet-attrs.h diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 000000000..1a9f1ac87 --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,35 @@ +# Benchmarks + + - [`mt-gcbench.c`](./mt-gcbench.c): The multi-threaded [GCBench + benchmark](https://hboehm.info/gc/gc_bench.html). An old but + standard benchmark that allocates different sizes of binary trees. + As parameters it takes a heap multiplier and a number of mutator + threads. We analytically compute the peak amount of live data and + then size the GC heap as a multiplier of that size. It has a peak + heap consumption of 10 MB or so per mutator thread: not very large. + At a 2x heap multiplier, it causes about 30 collections for the + whippet collector, and runs somewhere around 200-400 milliseconds in + single-threaded mode, on the machines I have in 2022. For low thread + counts, the GCBench benchmark is small; but then again many Guile + processes also are quite short-lived, so perhaps it is useful to + ensure that small heaps remain lightweight. + + To stress Whippet's handling of fragmentation, we modified this + benchmark to intersperse pseudorandomly-sized holes between tree + nodes. + + - [`quads.c`](./quads.c): A synthetic benchmark that allocates quad + trees. The mutator begins by allocating one long-lived tree of depth + N, and then allocates 13% of the heap in depth-3 trees, 20 times, + simulating a fixed working set and otherwise an allocation-heavy + workload. By observing the times to allocate 13% of the heap in + garbage we can infer mutator overheads, and also note the variance + for the cycles in which GC hits. + +## License + +mt-gcbench.c was originally from https://hboehm.info/gc/gc_bench/, which +has a somewhat unclear license. I have modified GCBench significantly +so that I can slot in different GC implementations. Other files are +distributed under the Whippet license; see the top-level +[README.md](../README.md) for more. diff --git a/ephemerons-embedder.h b/benchmarks/ephemerons-embedder.h similarity index 100% rename from ephemerons-embedder.h rename to benchmarks/ephemerons-embedder.h diff --git a/ephemerons-types.h b/benchmarks/ephemerons-types.h similarity index 100% rename from ephemerons-types.h rename to benchmarks/ephemerons-types.h diff --git a/ephemerons.c b/benchmarks/ephemerons.c similarity index 100% rename from ephemerons.c rename to benchmarks/ephemerons.c diff --git a/heap-objects.h b/benchmarks/heap-objects.h similarity index 100% rename from heap-objects.h rename to benchmarks/heap-objects.h diff --git a/mt-gcbench-embedder.h b/benchmarks/mt-gcbench-embedder.h similarity index 100% rename from mt-gcbench-embedder.h rename to benchmarks/mt-gcbench-embedder.h diff --git a/mt-gcbench-types.h b/benchmarks/mt-gcbench-types.h similarity index 100% rename from mt-gcbench-types.h rename to benchmarks/mt-gcbench-types.h diff --git a/mt-gcbench.c b/benchmarks/mt-gcbench.c similarity index 100% rename from mt-gcbench.c rename to benchmarks/mt-gcbench.c diff --git a/quads-embedder.h b/benchmarks/quads-embedder.h similarity index 100% rename from quads-embedder.h rename to benchmarks/quads-embedder.h diff --git a/quads-types.h b/benchmarks/quads-types.h similarity index 100% rename from quads-types.h rename to benchmarks/quads-types.h diff --git a/quads.c b/benchmarks/quads.c similarity index 100% rename from quads.c rename to benchmarks/quads.c diff --git a/simple-allocator.h b/benchmarks/simple-allocator.h similarity index 100% rename from simple-allocator.h rename to benchmarks/simple-allocator.h diff --git a/simple-gc-embedder.h b/benchmarks/simple-gc-embedder.h similarity index 100% rename from simple-gc-embedder.h rename to benchmarks/simple-gc-embedder.h diff --git a/simple-roots-api.h b/benchmarks/simple-roots-api.h similarity index 100% rename from simple-roots-api.h rename to benchmarks/simple-roots-api.h diff --git a/simple-roots-types.h b/benchmarks/simple-roots-types.h similarity index 100% rename from simple-roots-types.h rename to benchmarks/simple-roots-types.h diff --git a/simple-tagging-scheme.h b/benchmarks/simple-tagging-scheme.h similarity index 100% rename from simple-tagging-scheme.h rename to benchmarks/simple-tagging-scheme.h diff --git a/doc/design.md b/doc/design.md new file mode 100644 index 000000000..1a1c69bee --- /dev/null +++ b/doc/design.md @@ -0,0 +1,64 @@ +# Design + +Whippet is mainly a mark-region collector, like +[Immix](http://users.cecs.anu.edu.au/~steveb/pubs/papers/immix-pldi-2008.pdf). +See also the lovely detailed [Rust +implementation](http://users.cecs.anu.edu.au/~steveb/pubs/papers/rust-ismm-2016.pdf). + +To a first approximation, Whippet is a whole-heap Immix collector with a +large object space on the side. See the Immix paper for full details, +but basically Immix divides the heap into 32kB blocks, and then divides +those blocks into 128B lines. An Immix allocation never spans blocks; +allocations larger than 8kB go into a separate large object space. +Mutators request blocks from the global store and allocate into those +blocks using bump-pointer allocation. When all blocks are consumed, +Immix stops the world and traces the object graph, marking objects but +also the lines that objects are on. After marking, blocks contain some +lines with live objects and others that are completely free. Spans of +free lines are called holes. When a mutator gets a recycled block from +the global block store, it allocates into those holes. Also, sometimes +Immix can choose to evacuate rather than mark. Bump-pointer-into-holes +allocation is quite compatible with conservative roots, so it's an +interesting option for Guile, which has a lot of legacy C API users. + +The essential difference of Whippet from Immix stems from a simple +observation: Immix needs a side table of line mark bytes and also a mark +bit or bits in each object (or in a side table). But if instead you +choose to store mark bytes instead of bits (for concurrency reasons) in +a side table, with one mark byte per granule (unit of allocation, +perhaps 16 bytes), then you effectively have a line mark table where the +granule size is the line size. You can bump-pointer allocate into holes +in the mark byte table. + +You might think this is a bad tradeoff, and perhaps it is: I don't know +yet. If your granule size is two pointers, then one mark byte per +granule is 6.25% overhead on 64-bit, or 12.5% on 32-bit. Especially on +32-bit, it's a lot! On the other hand, instead of the worst case of one +survivor object wasting a line (or two, in the case of conservative line +marking), granule-size-is-line-size instead wastes nothing. Also, you +don't need GC bits in the object itself, and you can use the mark byte +array to record the object end, so that finding holes in a block can +just read the mark table and can avoid looking at object memory. + +Other ideas in Whippet: + + * Minimize stop-the-world phase via parallel marking and punting all + sweeping to mutators + + * Enable mutator parallelism via lock-free block acquisition and lazy + statistics collation + + * Allocate block space using aligned 4 MB slabs, with embedded metadata + to allow metadata bytes, slab headers, and block metadata to be + located via address arithmetic + + * Facilitate conservative collection via mark byte array, oracle for + "does this address start an object" + + * Enable in-place generational collection via card table with one entry + per 256B or so + + * Enable concurrent marking by having three mark bit states (dead, + survivor, marked) that rotate at each collection, and sweeping a + block clears metadata for dead objects; but concurrent marking and + associated SATB barrier not yet implemented diff --git a/doc/guile.md b/doc/guile.md new file mode 100644 index 000000000..05bc17e15 --- /dev/null +++ b/doc/guile.md @@ -0,0 +1,26 @@ +# Whippet and Guile + +If the Whippet collector works out, it could replace Guile's garbage +collector. Guile currently uses BDW-GC. Guile has a widely used C API +and implements part of its run-time in C. For this reason it may be +infeasible to require precise enumeration of GC roots -- we may need to +allow GC roots to be conservatively identified from data sections and +from stacks. Such conservative roots would be pinned, but other objects +can be moved by the collector if it chooses to do so. We assume that +object references within a heap object can be precisely identified. +(However, Guile currently uses BDW-GC in its default configuration, +which scans for references conservatively even on the heap.) + +The existing C API allows direct access to mutable object fields, +without the mediation of read or write barriers. Therefore it may be +impossible to switch to collector strategies that need barriers, such as +generational or concurrent collectors. However, we shouldn't write off +this possibility entirely; an ideal replacement for Guile's GC will +offer the possibility of migration to other GC designs without imposing +new requirements on C API users in the initial phase. + +In this regard, the Whippet experiment also has the goal of identifying +a smallish GC abstraction in Guile, so that we might consider evolving +GC implementation in the future without too much pain. If we switch +away from BDW-GC, we should be able to evaluate that it's a win for a +large majority of use cases. diff --git a/USER-GUIDE.md b/doc/manual.md similarity index 97% rename from USER-GUIDE.md rename to doc/manual.md index 434a6f6cb..41ff83d91 100644 --- a/USER-GUIDE.md +++ b/doc/manual.md @@ -51,7 +51,7 @@ itself. This is the *embedder API*, and this document describes what Whippet requires from a program. A program should provide a header file implementing the API in -[`gc-embedder-api.h`](./gc-embedder-api.h). This header should only be +[`gc-embedder-api.h`](../api/gc-embedder-api.h). This header should only be included when compiling Whippet itself; it is not part of the API that Whippet exposes to the program. @@ -83,7 +83,7 @@ Most kinds of GC-managed object are defined by the program, but the GC itself has support for a specific object kind: ephemerons. If the program allocates ephemerons, it should trace them in the `gc_trace_object` function by calling `gc_trace_ephemeron` from -[`gc-ephemerons.h`](./gc-ephemerons.h). +[`gc-ephemerons.h`](../api/gc-ephemerons.h). ### Remembered-set bits @@ -91,7 +91,7 @@ When built to support generational garbage collection, Whippet requires that all "large" or potentially large objects have a flag bit reserved for use of the garbage collector. A large object is one whose size exceeds the `gc_allocator_large_threshold()` (see -[`gc-attrs.h`](./gc-attrs.h)), which is a collector-specific value. +[`gc-attrs.h`](../api/gc-attrs.h)), which is a collector-specific value. Currently the only generational collector is the in-place Whippet collector, whose large object threshold is 4096 bytes. The `gc_object_set_remembered`, `gc_object_is_remembered_nonatomic`, and @@ -116,7 +116,7 @@ The atomic API is gnarly. It is used by parallel collectors, in which multiple collector threads can race to evacuate an object. There is a state machine associated with the `gc_atomic_forward` -structure from [`gc-forwarding.h`](./gc-forwarding.h); the embedder API +structure from [`gc-forwarding.h`](../api/gc-forwarding.h); the embedder API implements the state changes. The collector calls `gc_atomic_forward_begin` on an object to begin a forwarding attempt, and the resulting `gc_atomic_forward` can be in the `NOT_FORWARDED`, @@ -379,7 +379,7 @@ program? No, because your program isn't written yet? Well this section is for you: we describe the user-facing API of Whippet, where "user" in this case denotes the embedding program. -What is the API, you ask? It is in [`gc-api.h`](./gc-api.h). +What is the API, you ask? It is in [`gc-api.h`](../api/gc-api.h). ### Heaps and mutators @@ -442,7 +442,7 @@ defined for all collectors: processors, with a maximum of 8. You can set these options via `gc_option_set_int` and so on; see -[`gc-options.h`](./gc-options.h). Or, you can parse options from +[`gc-options.h`](../api/gc-options.h). Or, you can parse options from strings: `heap-size-policy`, `heap-size`, `maximum-heap-size`, and so on. Use `gc_option_from_string` to determine if a string is really an option. Use `gc_option_parse_and_set` to parse a value for an option. @@ -519,7 +519,7 @@ Whippet supports ephemerons, first-class objects that weakly associate keys with values. If the an ephemeron's key ever becomes unreachable, the ephemeron becomes dead and loses its value. -The user-facing API is in [`gc-ephemeron.h`](./gc-ephemeron.h). To +The user-facing API is in [`gc-ephemeron.h`](../api/gc-ephemeron.h). To allocate an ephemeron, call `gc_allocate_ephemeron`, then initialize its key and value via `gc_ephemeron_init`. Get the key and value via `gc_ephemeron_key` and `gc_ephemeron_value`, respectively. diff --git a/gc.h b/gc.h deleted file mode 100644 index 1de10afdd..000000000 --- a/gc.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef GC_H_ -#define GC_H_ - -#include "gc-api.h" - -#if defined(GC_BDW) -#include "bdw.h" -#elif defined(GC_SEMI) -#include "semi.h" -#elif defined(GC_WHIPPET) -#define GC_PARALLEL_TRACE 0 -#define GC_GENERATIONAL 0 -#include "whippet.h" -#elif defined(GC_PARALLEL_WHIPPET) -#define GC_PARALLEL_TRACE 1 -#define GC_GENERATIONAL 0 -#include "whippet.h" -#elif defined(GC_GENERATIONAL_WHIPPET) -#define GC_PARALLEL_TRACE 0 -#define GC_GENERATIONAL 1 -#include "whippet.h" -#elif defined(GC_PARALLEL_GENERATIONAL_WHIPPET) -#define GC_PARALLEL_TRACE 1 -#define GC_GENERATIONAL 1 -#include "whippet.h" -#else -#error unknown gc -#endif - -#endif // GC_H_ diff --git a/address-hash.h b/src/address-hash.h similarity index 100% rename from address-hash.h rename to src/address-hash.h diff --git a/address-map.h b/src/address-map.h similarity index 100% rename from address-map.h rename to src/address-map.h diff --git a/address-set.h b/src/address-set.h similarity index 100% rename from address-set.h rename to src/address-set.h diff --git a/assert.h b/src/assert.h similarity index 100% rename from assert.h rename to src/assert.h diff --git a/bdw.c b/src/bdw.c similarity index 100% rename from bdw.c rename to src/bdw.c diff --git a/debug.h b/src/debug.h similarity index 100% rename from debug.h rename to src/debug.h diff --git a/gc-align.h b/src/gc-align.h similarity index 100% rename from gc-align.h rename to src/gc-align.h diff --git a/gc-ephemeron-internal.h b/src/gc-ephemeron-internal.h similarity index 100% rename from gc-ephemeron-internal.h rename to src/gc-ephemeron-internal.h diff --git a/gc-ephemeron.c b/src/gc-ephemeron.c similarity index 100% rename from gc-ephemeron.c rename to src/gc-ephemeron.c diff --git a/gc-internal.h b/src/gc-internal.h similarity index 100% rename from gc-internal.h rename to src/gc-internal.h diff --git a/gc-options-internal.h b/src/gc-options-internal.h similarity index 100% rename from gc-options-internal.h rename to src/gc-options-internal.h diff --git a/gc-options.c b/src/gc-options.c similarity index 100% rename from gc-options.c rename to src/gc-options.c diff --git a/gc-platform-gnu-linux.c b/src/gc-platform-gnu-linux.c similarity index 100% rename from gc-platform-gnu-linux.c rename to src/gc-platform-gnu-linux.c diff --git a/gc-platform.h b/src/gc-platform.h similarity index 100% rename from gc-platform.h rename to src/gc-platform.h diff --git a/gc-stack.c b/src/gc-stack.c similarity index 100% rename from gc-stack.c rename to src/gc-stack.c diff --git a/gc-stack.h b/src/gc-stack.h similarity index 100% rename from gc-stack.h rename to src/gc-stack.h diff --git a/gc-trace.h b/src/gc-trace.h similarity index 100% rename from gc-trace.h rename to src/gc-trace.h diff --git a/large-object-space.h b/src/large-object-space.h similarity index 100% rename from large-object-space.h rename to src/large-object-space.h diff --git a/parallel-tracer.h b/src/parallel-tracer.h similarity index 100% rename from parallel-tracer.h rename to src/parallel-tracer.h diff --git a/semi.c b/src/semi.c similarity index 100% rename from semi.c rename to src/semi.c diff --git a/serial-tracer.h b/src/serial-tracer.h similarity index 100% rename from serial-tracer.h rename to src/serial-tracer.h diff --git a/spin.h b/src/spin.h similarity index 100% rename from spin.h rename to src/spin.h diff --git a/whippet.c b/src/whippet.c similarity index 100% rename from whippet.c rename to src/whippet.c diff --git a/test-address-map.c b/test/test-address-map.c similarity index 100% rename from test-address-map.c rename to test/test-address-map.c diff --git a/test-address-set.c b/test/test-address-set.c similarity index 100% rename from test-address-set.c rename to test/test-address-set.c