├── .gitignore ├── HACKING.md ├── Makefile ├── README.md ├── bin └── whiffle ├── build.mk ├── doc └── design.md ├── examples ├── cpstak.scm ├── earley.scm ├── ephemerons.scm ├── eval-fib.scm ├── gcbench.scm ├── nboyer.scm ├── peano-fib.scm ├── peval.scm ├── quads.scm └── splay.scm ├── guix.scm ├── include └── whiffle │ ├── types.h │ └── vm.h ├── module └── whiffle │ ├── compile.scm │ ├── features.scm │ ├── input.scm │ ├── paths.scm │ ├── primitives.scm │ └── run.scm ├── pre-inst-env ├── runtime ├── prelude.scm └── whiffle-gc.h ├── test ├── basic.scm ├── benchmarks.scm └── examples.scm └── whippet ├── .gitignore ├── Makefile ├── README.md ├── api ├── bdw-attrs.h ├── gc-allocation-kind.h ├── gc-api.h ├── gc-assert.h ├── gc-attrs.h ├── gc-basic-stats.h ├── gc-collection-kind.h ├── gc-config.h ├── gc-conservative-ref.h ├── gc-edge.h ├── gc-embedder-api.h ├── gc-ephemeron.h ├── gc-event-listener-chain.h ├── gc-event-listener.h ├── gc-finalizer.h ├── gc-forwarding.h ├── gc-histogram.h ├── gc-inline.h ├── gc-lttng.h ├── gc-null-event-listener.h ├── gc-options.h ├── gc-ref.h ├── gc-stack-addr.h ├── gc-tracepoint.h ├── gc-visibility.h ├── mmc-attrs.h ├── pcc-attrs.h └── semi-attrs.h ├── benchmarks ├── README.md ├── ephemerons-embedder.h ├── ephemerons-types.h ├── ephemerons.c ├── finalizers-embedder.h ├── finalizers-types.h ├── finalizers.c ├── heap-objects.h ├── mt-gcbench-embedder.h ├── mt-gcbench-types.h ├── mt-gcbench.c ├── quads-embedder.h ├── quads-types.h ├── quads.c ├── simple-allocator.h ├── simple-gc-embedder.h ├── simple-roots-api.h ├── simple-roots-types.h └── simple-tagging-scheme.h ├── ctf_to_json.py ├── doc ├── README.md ├── collector-bdw.md ├── collector-mmc.md ├── collector-pcc.md ├── collector-semi.md ├── collectors.md ├── guile.md ├── manual.md ├── perfetto-minor-gc.png └── tracepoints.md ├── embed.am ├── embed.mk ├── manifest.scm ├── src ├── adaptive-heap-sizer.h ├── address-hash.h ├── address-map.h ├── address-set.h ├── assert.h ├── background-thread.h ├── bdw.c ├── copy-space.h ├── debug.h ├── embedder-api-impl.h ├── extents.h ├── field-set.h ├── freelist.h ├── gc-align.h ├── gc-ephemeron-internal.h ├── gc-ephemeron.c ├── gc-finalizer-internal.h ├── gc-finalizer.c ├── gc-internal.h ├── gc-lock.h ├── gc-options-internal.h ├── gc-options.c ├── gc-platform-gnu-linux.c ├── gc-platform.h ├── gc-stack.c ├── gc-stack.h ├── gc-trace.h ├── gc-tracepoint.c ├── growable-heap-sizer.h ├── heap-sizer.h ├── large-object-space.h ├── local-worklist.h ├── mmc.c ├── nofl-space.h ├── parallel-tracer.h ├── pcc.c ├── root-worklist.h ├── root.h ├── semi.c ├── serial-tracer.h ├── shared-worklist.h ├── simple-worklist.h ├── spin.h ├── splay-tree.h ├── swar.h └── tracer.h ├── test ├── test-address-map.c ├── test-address-set.c └── test-splay-tree.c └── whippet.m4 /.gitignore: -------------------------------------------------------------------------------- 1 | *.go 2 | -------------------------------------------------------------------------------- /HACKING.md: -------------------------------------------------------------------------------- 1 | # Meta-notes 2 | 3 | ## How to merge whippet 4 | 5 | Whiffle uses [whippet](https://github.com/wingo/whippet-gc/) as the 6 | garbage collector. 7 | 8 | Whippet is an include-only library, meant to be "vendored" into a 9 | project's source tree. This is partly because it needs compile-time 10 | specialization against the object representation of a specific program. 11 | Also, this allows compile-time choice of GC algorithm, with inlined fast 12 | paths. Anyway the way Whiffle incorporates whippet is via git [subtree 13 | merges](https://docs.github.com/en/get-started/using-git/about-git-subtree-merges). 14 | 15 | To update Whiffle's copy of whippet, first ensure that you have the 16 | whippet remote: 17 | 18 | ``` 19 | git remote add whippet https://github.com/wingo/whippet-gc 20 | git fetch whippet 21 | ``` 22 | 23 | Then to update: 24 | 25 | ``` 26 | git pull -s subtree whippet main 27 | ``` 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GUILE_MODULES = $(wildcard module/whiffle/*.scm) 2 | GUILE_OBJS = $(GUILE_MODULES:%.scm=%.go) 3 | 4 | TESTS = $(wildcard test/*.scm) 5 | TEST_TARGETS = $(TESTS:%.scm=%.check) 6 | 7 | GUILE = guile 8 | GUILD = guild 9 | GUILD_CFLAGS = -O2 -W2 10 | 11 | all: $(GUILE_OBJS) 12 | 13 | $(GUILE_OBJS): %.go: %.scm 14 | ./pre-inst-env $(GUILD) compile $(GUILD_CFLAGS) -o $@ $< 15 | 16 | clean: 17 | rm -f $(GUILE_OBJS) 18 | 19 | check: $(TEST_TARGETS) 20 | 21 | $(TEST_TARGETS): %.check: %.scm 22 | ./pre-inst-env $(GUILE) $< 23 | 24 | .PHONY: $(TEST_TARGETS) all check clean 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Whiffle Scheme 2 | 3 | Oh my god another Scheme. Don't worry, this one is useless! 4 | 5 | ## What it is 6 | 7 | A simple Scheme compiler, made as a workbench for experimenting with 8 | garbage collectors. See the [design document](./doc/design.md), for more. 9 | 10 | ## Using Whiffle 11 | 12 | Whiffle itself has three dependencies: [Guile 13 | 3.0.10](https://gnu.org/s/guile), `make`, and a C compiler. As of late 14 | October 2023, Guile 3.0.10 is not released yet, so you need a build from 15 | git. Additionally if you want to support the 16 | [BDW-GC](https://github.com/ivmai/bdwgc) collector implementation, you 17 | will need bdw-gc and pkg-config. 18 | 19 | The easiest way to get the dependencies is via 20 | [Guix](https://guix.gnu.org/): 21 | 22 | ``` 23 | $ guix shell 24 | ``` 25 | 26 | This command will put you in a shell that has everything you need. 27 | Then, probably you want to compile the Whiffle compiler: 28 | 29 | ``` 30 | $ make 31 | [...] 32 | ``` 33 | 34 | You don't have to do this, but things run faster this way. 35 | 36 | If you don't want to enter a Guix shell, prefix the commands below with 37 | `guix shell -Df guix.scm --`, like this: 38 | 39 | ``` 40 | $ guix shell -Df guix.scm -- make 41 | ``` 42 | 43 | Whiffle embeds a copy of [Whippet](https://github.com/wingo/whippet), a 44 | portable garbage collector implementation. (Testing Whippet is the 45 | purpose of Whiffle.) The Whippet GC currently only works on Linux 46 | systems, but this will be fixed to include MacOS and Windows soonish. 47 | Other OS implementations are welcome but they should go through the 48 | Whippet project first. 49 | 50 | So one example is to compile an expression to C, then run the compiled 51 | file: 52 | 53 | ``` 54 | $ ./pre-inst-env whiffle -e '42' 55 | ``` 56 | 57 | The `pre-inst-env` is for running Whiffle from within the source tree, 58 | which is the only supported way to run it, currently. 59 | 60 | But if you ran that, OK, actually we should print the result: 61 | 62 | ``` 63 | $ ./pre-inst-env whiffle -e '(writeln 42)' 64 | ``` 65 | 66 | You may prefer instead to generate a binary instead of running it 67 | directly: 68 | 69 | ``` 70 | $ ./pre-inst-env whiffle -o foo -e '(write 42)' 71 | $ ./foo 72 | 42 73 | ``` 74 | 75 | The resulting binary has some standard command-line options: 76 | 77 | ``` 78 | $ ./foo --help 79 | usage: ./foo [--print-stats] [--gc-options OPTIONS] ARG... 80 | ``` 81 | 82 | For example, we can print some statistics: 83 | 84 | ``` 85 | $ ./foo --print-stats 86 | 42 87 | Completed 0 major collections (0 minor). 88 | 0.083 ms total time (0.000 stopped). 89 | Heap size is 6.291 MB (max 6.291 MB); peak live data 0.000 MB. 90 | ``` 91 | 92 | Whiffle's GC library, [Whippet](https://github.com/wingo/whippet), 93 | includes a number of concrete garbage collectors. The choice of which 94 | collector to use is made at build-time: 95 | 96 | ``` 97 | $ ./pre-inst-env whiffle -o foo -e '(write 42)' --gc=help 98 | available GC implementations: 99 | semi serial copying 100 | pcc parallel copying 101 | bdw third-party BDW-GC parallel mark-sweep 102 | mmc serial immix 103 | generational-mmc mmc + in-place generations 104 | parallel-mmc mmc + parallel tracing 105 | stack-conservative-mmc mmc + conservative stack root finding 106 | heap-conservative-mmc stack-conservative-mmc + conservative heap edges 107 | stack-conservative-parallel-mmc 108 | heap-conservative-parallel-mmc 109 | stack-conservative-generational-mmc 110 | heap-conservative-generational-mmc 111 | parallel-generational-mmc 112 | stack-conservative-parallel-generational-mmc 113 | heap-conservative-parallel-generational-mmc 114 | combinations of the above 115 | ``` 116 | 117 | The default collector is `semi`. The collectors are also parameterized 118 | by run-time 119 | [options](https://github.com/wingo/whippet/blob/main/doc/manual.md#options). 120 | To set options, pass the `--gc-options` argument: 121 | 122 | ``` 123 | $ ./foo --print-stats --gc-options heap-size=100m 124 | warning: parallelism unimplemented in semispace copying collector 125 | 42 126 | Completed 0 major collections (0 minor). 127 | 0.050 ms total time (0.000 stopped). 128 | Heap size is 104.858 MB (max 104.858 MB); peak live data 0.000 MB. 129 | ``` 130 | For example 131 | 132 | One of the options is `parallelism`, which defaults to the number of 133 | cores available. But `semi` only supports 1 core, currently; it will 134 | warn if the `parallelism` option is not 1. (We have been omitting the 135 | warning from the output above, except in this latest example.) So if 136 | you want to avoid the warning you can pass `parallelism=1` as an option: 137 | 138 | ``` 139 | $ ./foo --print-stats --gc-options heap-size=100m,parallelism=1 140 | 42 141 | Completed 0 major collections (0 minor). 142 | 0.065 ms total time (0.000 stopped). 143 | Heap size is 104.858 MB (max 104.858 MB); peak live data 0.000 MB. 144 | ``` 145 | 146 | Whiffle can compile files instead of expressions: 147 | 148 | ``` 149 | $ ./pre-inst-env whiffle examples/peano-fib.scm 25 150 | warning: parallelism unimplemented in semispace copying collector 151 | 121393 152 | ``` 153 | 154 | Here we see that we got the semi parallelism warning. When we use 155 | whiffle to evaluate a file or expression directly instead of reifying a 156 | binary, we can pass some command-line arguments to set relevant GC 157 | options: `--parallelism` for a limit to the number of GC threads, 158 | `--heap-size` for heap size, and `--heap-size-policy` to request a 159 | fixed, growable, or adaptive heap size policy: 160 | 161 | ``` 162 | $ ./pre-inst-env whiffle --parallelism=1 examples/peano-fib.scm 25 163 | 121393 164 | ``` 165 | 166 | Again, pass `-o` to write a binary instead of running the compiled file 167 | directly. 168 | 169 | Maybe you want to choose a different GC? Pass `--gc`: 170 | 171 | ``` 172 | $ ./pre-inst-env whiffle --gc=mmc examples/peano-fib.scm 25 173 | 121393 174 | ``` 175 | 176 | Also with the `peano-fib.scm` example, we see that we are passing an 177 | additional argument, `25`. If you pass an argument to whiffle, or to a 178 | compiled binary, Whiffle will check that the program evaluates to a 179 | procedure, then applies that procedure to the arguments. 180 | 181 | See `whiffle --help` for more. 182 | -------------------------------------------------------------------------------- /build.mk: -------------------------------------------------------------------------------- 1 | here:=$(dir $(lastword $(MAKEFILE_LIST))) 2 | WHIFFLE=$(here) 3 | all: out 4 | 5 | GC_BASE=$(here)whippet/ 6 | GC_EMBEDDER_H=$(WHIFFLE)runtime/whiffle-gc.h 7 | GC_EMBEDDER_CPPFLAGS=$(WHIFFLE_CPPFLAGS) 8 | include $(GC_BASE)embed.mk 9 | 10 | WHIFFLE_CPPFLAGS=-I $(WHIFFLE)include 11 | WHIFFLE_CFLAGS=-foptimize-sibling-calls 12 | 13 | out.o: out.c 14 | $(GC_COMPILE) $(WHIFFLE_CPPFLAGS) $(WHIFFLE_CFLAGS) -c $< 15 | out: out.o $(GC_OBJS) 16 | $(GC_LINK) $^ $(GC_LIBS) 17 | 18 | clean: 19 | $(GC_V)rm -f out out.o $(GC_OBJS) 20 | 21 | # Clear some of the default rules. 22 | .SUFFIXES: 23 | .SECONDARY: 24 | %.c:; 25 | Makefile:; 26 | -------------------------------------------------------------------------------- /doc/design.md: -------------------------------------------------------------------------------- 1 | # Whiffle design document 2 | 3 | ## Problem statement 4 | 5 | The [Whippet garbage collector 6 | library](https://github.com/wingo/whippet) needs some microbenchmarks. 7 | This turns out to be surprisingly tricky. 8 | 9 | Some GC microbenchmarks can written in C, but C is an annoying language 10 | for writing garbage-collected programs if you want precise root-finding 11 | to be an option. Really what a GC library needs is microbenchmarks 12 | written in a language with automatically-managed memory. But, such 13 | languages tend to have large run-times with variable performance 14 | characteristics; it would be nice to have something smaller. And, it 15 | needs to link well with C. 16 | 17 | Whiffle is that something. It compiles Scheme programs to native 18 | binaries, outputting C as an intermediate stage. The C is 19 | straightforward and self-contained, just including a few files from 20 | Whiffle's source. 21 | 22 | ## Structure 23 | 24 | Whiffle only supports a subset of Scheme: enough to write the 25 | microbenchmarks. 26 | 27 | When writing Scheme, you want a baseline level of abstraction: macros, 28 | inlining of trivial functions, basic constant folding and unrolling, and 29 | so on: the [macro writer's bill of 30 | rights](https://www.youtube.com/watch?v=LIEX3tUliHw), basically. 31 | Whiffle has this by re-using the front-end of 32 | [Guile](https://gnu.org/s/guile/). That's easy to do, given that the 33 | Whiffle compiler itself is written in Guile's dialect of Scheme. 34 | 35 | As far as the back-end goes, Whiffle is a [baseline 36 | compiler](https://wingolog.org/archives/2020/06/03/a-baseline-compiler-for-guile). 37 | It compiles by walking the abstract syntax tree of a program, emitting 38 | code as it goes. 39 | 40 | The C code that Whiffle emits currently uses the C stack for control 41 | (function calls) and a side stack for values (locals and temporaries). 42 | 43 | ## Stack roots 44 | 45 | To the garbage collector, a Whiffle thread's VM state is basically just 46 | a stack pointer that grows down. There are no other ABI registers. 47 | Each function has a compile-time-determined maximum number of 48 | temporaries and locals, and will ensure there is enough space on 49 | function entry, bumping the stack pointer accordingly. Operations load 50 | operands from and store results to stack slots, without moving the stack 51 | pointer. Only single-valued function returns are supported. 52 | 53 | If an operation can allocate, and allocation would take the slow path to 54 | call into the collector, the stack is trimmed so that the stack is 55 | packed, with no trailing garbage words. In this way we don't have to do 56 | any book-keeping as to what stack slot is live. Using a side stack does 57 | inhibit some C compiler optimizations though, relative to the alternate 58 | strategy of using C local variables; the compiler doesn't know that 59 | bumping the stack pointer is an `alloca`. 60 | 61 | ## Limitations 62 | 63 | Using the C stack for control means no delimited continuations, at least 64 | for now. A proper solution that would allow for delimited continuations 65 | would be something like the `tailify` pass used by 66 | [Hoot](https://gitlab.com/spritely/guile-hoot/). Maybe someday we'd do 67 | that, if we actually care about completeness. But for now, Whiffle is 68 | just a workbench for making GC benchmarks. 69 | 70 | A garbage collector library needs macrobenchmarks in addition to 71 | microbenchmarks; these will come to Whippet when it is integrated into 72 | [Guile Scheme](https://gnu.org/s/guile). But until then, Whiffle is 73 | here. 74 | 75 | Right now the set of primitives supported by Whiffle is verrrrrrry 76 | limited. 77 | 78 | The error-handling is terrible: Whiffle generally just aborts. But, it 79 | should be safe: it shouldn't be possible to access, say, a pair as if it 80 | were a vector. 81 | 82 | -------------------------------------------------------------------------------- /examples/cpstak.scm: -------------------------------------------------------------------------------- 1 | (define (cpstak x y z) 2 | 3 | (define (tak x y z k) 4 | (if (not (< y x)) 5 | (k z) 6 | (tak (- x 1) 7 | y 8 | z 9 | (lambda (v1) 10 | (tak (- y 1) 11 | z 12 | x 13 | (lambda (v2) 14 | (tak (- z 1) 15 | x 16 | y 17 | (lambda (v3) 18 | (tak v1 v2 v3 k))))))))) 19 | 20 | (<< "starting") 21 | (let ((v (tak x y z (lambda (a) a)))) 22 | (<< "finished") 23 | v)) 24 | 25 | (lambda (nthreads x y z output) 26 | (let ((retv (parallel nthreads (lambda (i) (cpstak x y z))))) 27 | (let lp ((i 0)) 28 | (when (< i nthreads) 29 | (unless (eq? (vector-ref retv i) output) 30 | (error "unexpected output" i (vector-ref retv i) output)))))) 31 | -------------------------------------------------------------------------------- /examples/ephemerons.scm: -------------------------------------------------------------------------------- 1 | (define-syntax-rule (assert! expr) 2 | (or expr (error "check failed" 'expr))) 3 | (define-syntax-rule (assert-eq! a b) 4 | (let ((a' a) (b' b)) 5 | (unless (eq? a' b') 6 | (error "not equal: " 'a "=" a' ", " 'b "=" b')))) 7 | 8 | (define-syntax list 9 | (syntax-rules () 10 | ((_) '()) 11 | ((_ head . tail) (cons head (list . tail))))) 12 | 13 | (define (pk* vals) 14 | (<< "pk: " vals) 15 | (let lp ((vals vals)) 16 | (if (null? (cdr vals)) 17 | (car vals) 18 | (lp (cdr vals))))) 19 | (define-syntax-rule (pk expr ...) 20 | (pk* (list expr ...))) 21 | 22 | (define (ephemeron-chain-length chain key-box) 23 | (let lp ((head (ephemeron-table-ref chain 0)) 24 | (key (box-ref key-box)) 25 | (len 0)) 26 | (cond 27 | ((not head) len) 28 | (else 29 | (assert-eq! key (ephemeron-key head)) 30 | (let ((value (assert! (ephemeron-value head)))) 31 | (lp (ephemeron-next head) 32 | (box-ref value) 33 | (1+ len))))))) 34 | 35 | (define (make-ephemeron-chain length head-key-box) 36 | (let ((table (make-ephemeron-table 1))) 37 | (define (make-key x) (cons x x)) 38 | (let lp ((head #f) (i 0)) 39 | (if (< i length) 40 | (let* ((tail-box (box (box-ref head-key-box))) 41 | (next-head-key (make-key i)) 42 | (next-head (make-ephemeron next-head-key tail-box))) 43 | (ephemeron-table-push! table 0 next-head) 44 | (box-set! head-key-box next-head-key) 45 | (lp next-head (1+ i))) 46 | table)))) 47 | 48 | (define-syntax-rule (when-precise-gc expr ...) 49 | (cond-expand 50 | (precise-gc (begin expr ...)) 51 | (else #t))) 52 | 53 | (define (run-test i chain-length) 54 | (<< i ": Allocating ephemeron list " chain-length " nodes long.\n") 55 | (let* ((start (current-microseconds)) 56 | (head-key-box (box #f)) 57 | (chain (make-ephemeron-chain chain-length head-key-box))) 58 | (assert-eq! (ephemeron-chain-length chain head-key-box) 59 | chain-length) 60 | (gc-collect) 61 | (assert-eq! (ephemeron-chain-length chain head-key-box) chain-length) 62 | (when-precise-gc 63 | (box-set! head-key-box #f) 64 | ;; Assume that gc-collect forces major collections, otherwise the 65 | ;; key may still be live. 66 | (gc-collect) 67 | (assert-eq! (ephemeron-chain-length chain head-key-box) 0)) 68 | (print-elapsed "thread" start))) 69 | 70 | (lambda (nthreads chain-length) 71 | (parallel nthreads 72 | (lambda (i) 73 | (run-test i chain-length)))) 74 | -------------------------------------------------------------------------------- /examples/eval-fib.scm: -------------------------------------------------------------------------------- 1 | (define (compile expr cenv) 2 | (define (lookup var cenv k) 3 | (let lp ((depth 0) (cenv cenv)) 4 | (match cenv 5 | (() (error "unbound var" var)) 6 | ((rib . cenv) 7 | (match (list-index rib var) 8 | (#f (lp (1+ depth) cenv)) 9 | (offset 10 | (k depth offset))))))) 11 | (define (valid-bindings? bindings) 12 | (match bindings 13 | (() #t) 14 | ((((? symbol?) _) . bindings) (valid-bindings? bindings)) 15 | (_ #f))) 16 | (define (binding-var binding) (car binding)) 17 | (define (binding-val binding) (car (cdr binding))) 18 | (match expr 19 | ((? symbol? var) 20 | (lookup var cenv 21 | (lambda (depth offset) 22 | (lambda (env) 23 | (let lp ((depth depth) (env env)) 24 | (if (zero? depth) 25 | (vector-ref (car env) offset) 26 | (lp (1- depth) (cdr env)))))))) 27 | 28 | (('set! var val) 29 | (let ((val (compile val cenv))) 30 | (lookup var cenv 31 | (lambda (depth offset) 32 | (lambda (env) 33 | (let lp ((depth depth) (env env)) 34 | (if (zero? depth) 35 | (vector-set! (car env) offset (val env)) 36 | (lp (1- depth) (cdr env))))))))) 37 | 38 | (('if test consequent alternate) 39 | (let ((test (compile test cenv)) 40 | (consequent (compile consequent cenv)) 41 | (alternate (compile alternate cenv))) 42 | (lambda (env) 43 | (if (test env) 44 | (consequent env) 45 | (alternate env))))) 46 | 47 | (('begin expr) (compile expr cenv)) 48 | (('begin head . tail) 49 | (let ((head (compile head cenv)) 50 | (tail (compile `(begin . ,tail) cenv))) 51 | (lambda (env) (head env) (tail env)))) 52 | 53 | (('lambda args . body) 54 | (let ((body (compile `(begin . ,body) (cons args cenv)))) 55 | (define-syntax-rule (args-case pat ...) 56 | (match args 57 | (pat (lambda (env) 58 | (lambda pat (body (cons (vector . pat) env))))) 59 | ...)) 60 | (args-case () (a) (a b) (a b c) (a b c d)))) 61 | 62 | (('let (? symbol? lp) (? valid-bindings? bindings) . body) 63 | (let ((vars (map binding-var bindings)) 64 | (vals (map binding-val bindings))) 65 | (compile `(letrec ((,lp (lambda ,vars . ,body))) (,lp . ,vals)) cenv))) 66 | (('let (? valid-bindings? bindings) . body) 67 | (let ((vars (map binding-var bindings)) 68 | (vals (map binding-val bindings))) 69 | (compile `((lambda ,vars . ,body) . ,vals) cenv))) 70 | 71 | (('letrec (? valid-bindings? bindings) . body) 72 | (let ((vars (map binding-var bindings)) 73 | (vals (map binding-val bindings))) 74 | (compile `(let ,(map (lambda (var) `(,var #f)) vars) 75 | . ,(let lp ((vars vars) (vals vals) (out body)) 76 | (match vars 77 | (() out) 78 | ((var . vars) 79 | (match vals 80 | ((val . vals) 81 | (lp vars vals (cons `(set! ,var ,val) out)))))))) 82 | cenv))) 83 | 84 | (('quote x) 85 | (lambda (env) x)) 86 | ((or (? exact-integer?) (? string?) #t #f) 87 | (lambda (env) expr)) 88 | 89 | ((proc . args) 90 | (let ((proc (compile proc cenv)) 91 | (args (map (lambda (arg) (compile arg cenv)) args))) 92 | (define-syntax-rule (args-case pat ...) 93 | (match args 94 | (pat (lambda (env) 95 | (define-syntax-rule (call x y (... ...)) 96 | ((x env) (y env) (... ...))) 97 | (call proc . pat))) 98 | ...)) 99 | (args-case () (a) (a b) (a b c) (a b c d)))))) 100 | 101 | (define-syntax-rule (define-globals global-names global-values g ...) 102 | (begin 103 | (define global-names '(g ...)) 104 | (define global-values (vector g ...)))) 105 | 106 | (define-globals global-names global-values 107 | pair? cons car cdr set-car! set-cdr! 108 | 109 | exact-integer? 110 | + - * < <= = > >= 1+ 1- zero? negative? 111 | quotient remainder 112 | 113 | char? char->integer integer->char 114 | 115 | eq? 116 | symbol? 117 | 118 | vector? make-vector vector-length vector-ref vector-set! vector->list 119 | 120 | null? 121 | not 122 | 123 | map for-each iota list? length) 124 | 125 | (define (eval expr) 126 | (let ((cenv (cons global-names '())) 127 | (env (cons global-values '()))) 128 | ((compile expr cenv) env))) 129 | 130 | (define fib 131 | (eval '(letrec ((fib (lambda (n) 132 | (if (< n 2) 133 | 1 134 | (+ (fib (- n 1)) (fib (- n 2))))))) 135 | fib))) 136 | 137 | (lambda (nthreads arg) 138 | (writeln (parallel nthreads (lambda (i) (<< "here") (fib arg))))) 139 | -------------------------------------------------------------------------------- /examples/gcbench.scm: -------------------------------------------------------------------------------- 1 | (define min-tree-depth 4) 2 | (define max-tree-depth 16) 3 | (define long-lived-bytevector-size #e4e6) 4 | (define long-lived-tree-depth 16) 5 | 6 | (define (make-node left right i j) (vector left right i j)) 7 | (define (make-empty-node) (make-node #f #f 0 0)) 8 | (define (node-left node) (vector-ref node 0)) 9 | (define (node-right node) (vector-ref node 1)) 10 | (define (node-i node) (vector-ref node 2)) 11 | (define (node-j node) (vector-ref node 3)) 12 | (define (set-left! node left) (vector-set! node 0 left)) 13 | (define (set-right! node right) (vector-set! node 1 right)) 14 | (define (set-j! node j) (vector-set! node 3 j)) 15 | 16 | (define (make-power-law-distribution) 17 | (define counter 0) 18 | ;; A power-law distribution. Each integer was selected by starting at 19 | ;; 0, taking a random number in [0,1), and then accepting the integer 20 | ;; if the random number was less than 0.15, or trying again with the 21 | ;; next integer otherwise. Useful for modelling allocation sizes or 22 | ;; number of garbage objects to allocate between live allocations. 23 | (define values 24 | #vu8(1 15 3 12 2 8 4 0 18 7 9 8 15 2 36 5 25 | 1 9 6 11 9 19 2 0 0 3 9 6 3 2 1 1 26 | 6 1 8 4 2 0 5 3 7 0 0 3 0 4 1 7 27 | 1 8 2 2 2 14 0 7 8 0 2 1 4 12 7 5 28 | 0 3 4 13 10 2 3 7 0 8 0 23 0 16 1 1 29 | 6 28 1 18 0 3 6 5 8 6 14 5 2 5 0 11 30 | 0 18 4 16 1 4 3 13 3 23 7 4 10 5 3 13 31 | 0 14 5 5 2 5 0 16 2 0 1 1 0 0 4 2 32 | 7 7 0 5 7 2 1 24 27 3 7 1 0 8 1 4 33 | 0 3 0 7 7 3 9 2 9 2 5 10 1 1 12 6 34 | 2 9 5 0 4 6 0 7 2 1 5 4 1 0 1 15 35 | 4 0 15 4 0 0 32 18 2 2 1 7 8 3 11 1 36 | 2 7 11 1 9 1 2 6 11 17 1 2 5 1 14 3 37 | 6 1 1 15 3 1 0 6 10 8 1 3 2 7 0 1 38 | 0 11 3 3 5 8 2 0 0 7 12 2 5 20 3 7 39 | 4 4 5 22 1 5 2 7 15 2 4 6 11 8 12 1)) 40 | 41 | (lambda () 42 | (let* ((i counter) 43 | (value (bytevector-u8-ref values i)) 44 | (i* (1+ i))) 45 | (set! counter (if (eq? i* (bytevector-length values)) 0 i*)) 46 | value))) 47 | 48 | (define (validate-tree! node depth) 49 | (cond-expand 50 | (check-heap-consistency 51 | (unless (eq? (node-i node) 0) (error "bad i")) 52 | (unless (eq? (node-j node) depth) (error "bad j")) 53 | (cond 54 | ((zero? depth) 55 | (when (node-left node) (error "unexpected left")) 56 | (when (node-right node) (error "unexpected right"))) 57 | (else 58 | (validate-tree! (node-left node) (1- depth)) 59 | (validate-tree! (node-right node) (1- depth))))) 60 | (else 61 | #t))) 62 | 63 | (define (tree-size depth) 64 | (if (zero? depth) 65 | 1 66 | (+ 1 (* 2 (tree-size (1- depth)))))) 67 | (define (compute-num-iters depth) 68 | (quotient (* 2 (tree-size (+ max-tree-depth 2))) 69 | (tree-size depth))) 70 | 71 | (define (run-test i fragmentation) 72 | (define garbage-size (make-power-law-distribution)) 73 | (define (allocate-garbage!) 74 | (let ((size (* fragmentation (garbage-size)))) 75 | (unless (zero? size) (make-vector (1- size) #f)))) 76 | (define (make-tree depth) 77 | (and (<= 0 depth) 78 | (let () 79 | (define left (make-tree (1- depth))) 80 | (define right (make-tree (1- depth))) 81 | (allocate-garbage!) 82 | (make-node left right 0 depth)))) 83 | (define (populate-node! depth node) 84 | (when (< 0 depth) 85 | (allocate-garbage!) 86 | (define left (make-empty-node)) 87 | (allocate-garbage!) 88 | (define right (make-empty-node)) 89 | (set-left! node left) 90 | (set-right! node right) 91 | (set-j! node depth) 92 | (populate-node! (1- depth) left) 93 | (populate-node! (1- depth) right))) 94 | 95 | (define (time-construction depth) 96 | (define iterations (compute-num-iters depth)) 97 | 98 | (<< i ": creating " iterations " trees of depth " depth) 99 | 100 | (let ((start (current-microseconds))) 101 | (let lp ((iterations iterations)) 102 | (unless (zero? iterations) 103 | (let ((tmp (make-empty-node))) 104 | (populate-node! depth tmp) 105 | (validate-tree! tmp depth) 106 | (lp (1- iterations))))) 107 | (print-elapsed "top-down construction" start)) 108 | 109 | (let ((start (current-microseconds))) 110 | (let lp ((iterations iterations)) 111 | (unless (zero? iterations) 112 | (let ((tmp (make-tree depth))) 113 | (validate-tree! tmp depth) 114 | (lp (1- iterations))))) 115 | (print-elapsed "bottom-up construction" start))) 116 | 117 | (<< i ": creating long-lived binary tree of depth " long-lived-tree-depth) 118 | 119 | (define long-lived-tree (make-tree long-lived-tree-depth)) 120 | (define long-lived-bytevector (make-bytevector long-lived-bytevector-size 0)) 121 | (let lp ((i 0) (v 0)) 122 | (when (< i long-lived-bytevector-size) 123 | (bytevector-u8-set! long-lived-bytevector i v) 124 | (lp (1+ i) (if (eq? v #xff) 0 (1+ v))))) 125 | (let lp ((i min-tree-depth)) 126 | (when (<= i max-tree-depth) 127 | (time-construction i) 128 | (lp (+ i 2)))) 129 | (validate-tree! long-lived-tree long-lived-tree-depth) 130 | ;; These references keep long-lived-tree and long-lived-bytevector 131 | ;; alive. 132 | (unless (eq? (node-i long-lived-tree) 0) 133 | (error "long-lived tree has unexpected value")) 134 | (unless (eq? (bytevector-u8-ref long-lived-bytevector 1000) 135 | (remainder 1000 256)) 136 | (error "long-lived bytevector has unexpected contents")) 137 | #t) 138 | 139 | (lambda (nthreads fragmentation) 140 | (parallel nthreads (lambda (i) (run-test i fragmentation)))) 141 | -------------------------------------------------------------------------------- /examples/peano-fib.scm: -------------------------------------------------------------------------------- 1 | (lambda (n) 2 | (define (make-peano n) 3 | (if (zero? n) 4 | '() 5 | (cons 0 (make-peano (1- n))))) 6 | (define (peano-value n) 7 | (if (null? n) 8 | 0 9 | (1+ (peano-value (cdr n))))) 10 | (let () 11 | (define (zero? n) (null? n)) 12 | (define zero '()) 13 | (define (1- n) (cdr n)) 14 | (define (1+ n) (cons 0 n)) 15 | (define (+ x y) 16 | (if (zero? x) 17 | y 18 | (1+ (+ (1- x) y)))) 19 | (define (- x y) 20 | (if (zero? y) 21 | x 22 | (- (1- x) (1- y)))) 23 | (define (< x y) 24 | (cond 25 | ((zero? x) (not (zero? y))) 26 | ((zero? y) #f) 27 | (else (< (1- x) (1- y))))) 28 | 29 | (define (fib n) 30 | (if (< n (1+ (1+ zero))) 31 | (1+ zero) 32 | (+ (fib (1- n)) (fib (1- (1- n)))))) 33 | 34 | (writeln (peano-value (fib (make-peano n)))))) 35 | -------------------------------------------------------------------------------- /examples/quads.scm: -------------------------------------------------------------------------------- 1 | (define-syntax-rule (assert! expr) 2 | (unless expr 3 | (error "check failed" 'expr))) 4 | 5 | (define (make-quad a b c d) (vector a b c d)) 6 | (define (quad-a quad) (vector-ref quad 0)) 7 | (define (quad-b quad) (vector-ref quad 1)) 8 | (define (quad-c quad) (vector-ref quad 2)) 9 | (define (quad-d quad) (vector-ref quad 3)) 10 | 11 | ;; Build tree bottom-up 12 | (define (make-tree depth) 13 | (and (<= 0 depth) 14 | (make-quad (make-tree (1- depth)) 15 | (make-tree (1- depth)) 16 | (make-tree (1- depth)) 17 | (make-tree (1- depth))))) 18 | 19 | (define (validate-tree tree depth) 20 | (cond 21 | ((negative? depth) (assert! (not tree))) 22 | (else 23 | (assert! tree) 24 | (validate-tree (quad-a tree) (1- depth)) 25 | (validate-tree (quad-b tree) (1- depth)) 26 | (validate-tree (quad-c tree) (1- depth)) 27 | (validate-tree (quad-d tree) (1- depth))))) 28 | 29 | (define (tree-size depth) 30 | (if (zero? depth) 31 | 1 32 | (+ 1 (* (tree-size (1- depth)) 4)))) 33 | 34 | (define (run-test i depth) 35 | (<< i ": Making quad tree of depth " depth " (" (tree-size depth) " nodes).") 36 | (let* ((start (current-microseconds)) 37 | (long-lived-tree (make-tree depth))) 38 | (print-elapsed "construction" start) 39 | (validate-tree long-lived-tree depth) 40 | (<< i "Allocating garbage tree of depth " (1- depth) 41 | " (" (tree-size (1- depth)) " nodes), 60 times," 42 | " validating live tree each time.") 43 | (let ((alloc-start (current-microseconds))) 44 | (let lp ((n 0)) 45 | (when (< n 60) 46 | (make-tree (1- depth)) 47 | (validate-tree long-lived-tree depth) 48 | (lp (1+ n)))) 49 | (print-elapsed "allocation loop" alloc-start)) 50 | (print-elapsed "quads test" start))) 51 | 52 | (lambda (nthreads depth) 53 | (parallel nthreads 54 | (lambda (i) 55 | (run-test i depth)))) 56 | -------------------------------------------------------------------------------- /guix.scm: -------------------------------------------------------------------------------- 1 | ;;; Copyright © 2023, 2025 Andy Wingo 2 | ;;; 3 | ;;; Whippet is free software; you can redistribute it and/or modify it 4 | ;;; under the terms of the GNU Lesser General Public License as 5 | ;;; published by the Free Software Foundation; either version 3 of the 6 | ;;; License, or (at your option) any later version. 7 | ;;; 8 | ;;; Whippet is distributed in the hope that it will be useful, but 9 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of 10 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | ;;; Lesser General Public License for more details. 12 | ;;; 13 | ;;; You should have received a copy of the GNU Lesser General Public 14 | ;;; License along with Whippet. If not, see 15 | ;;; . 16 | 17 | ;; This file defines a Guix package for Whiffle. It can be used to 18 | ;; spawn an interactive Whiffle development environment: 19 | ;; 20 | ;; guix shell 21 | ;; 22 | ;; Or it can be used to build Whiffle from a checkout in an isolated 23 | ;; environment: 24 | ;; 25 | ;; guix build -f guix.scm 26 | ;; 27 | ;; Likewise, you may cross-compile it: 28 | ;; 29 | ;; guix build -f guix.scm --target=x86_64-w64-mingw32 30 | ;; 31 | ;; … or perform a native build for another architecture, assuming 32 | ;; either offloading or transparent QEMU emulation is set up: 33 | ;; 34 | ;; guix build -f guix.scm -s riscv64-linux 35 | 36 | (define-module (whiffle-package) 37 | #:use-module (ice-9 match) 38 | #:use-module (guix) 39 | #:use-module (guix git-download) 40 | #:use-module (guix build-system gnu) 41 | #:use-module ((guix licenses) #:prefix license:) 42 | #:use-module (gnu packages base) 43 | #:use-module (gnu packages bdw-gc) 44 | #:use-module (gnu packages commencement) 45 | #:use-module (gnu packages guile) 46 | #:use-module (gnu packages instrumentation) 47 | #:use-module (gnu packages pkg-config) 48 | #:use-module (gnu packages version-control)) 49 | 50 | (define-public whiffle 51 | (let ((vcs-file? (or (git-predicate 52 | (string-append (current-source-directory) 53 | "/../..")) 54 | (const #t)))) 55 | (package 56 | (name "whiffle") 57 | (version "0.0.1-git") 58 | (source (local-file "../.." "whiffle-checkout" 59 | #:recursive? #t 60 | #:select? vcs-file?)) 61 | (build-system gnu-build-system) 62 | (arguments 63 | (list #:phases 64 | #~(modify-phases %standard-phases 65 | (delete 'configure)))) 66 | (native-inputs 67 | (list guile-next)) 68 | (propagated-inputs 69 | (list gcc-toolchain gnu-make libgc pkg-config lttng-ust lttng-tools)) 70 | (outputs '("out")) 71 | (synopsis "Scheme implementation intended to test the Whippet garbage collector") 72 | (description 73 | "Whiffle is a very simple compile-to-C Scheme implementation, intended to be a 74 | testbed for development of the Whippet garbage collector.") 75 | (home-page "https://github.com/wingo/whiffle/") 76 | (license license:lgpl3+)))) 77 | 78 | whiffle 79 | -------------------------------------------------------------------------------- /module/whiffle/features.scm: -------------------------------------------------------------------------------- 1 | ;;; Lightweight Scheme compiler directly to C. 2 | ;;; Copyright (C) 2023 Andy Wingo. 3 | 4 | ;;; This library is free software; you can redistribute it and/or modify it 5 | ;;; under the terms of the GNU Lesser General Public License as published by 6 | ;;; the Free Software Foundation; either version 3 of the License, or (at 7 | ;;; your option) any later version. 8 | ;;; 9 | ;;; This library is distributed in the hope that it will be useful, but 10 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of 11 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 12 | ;;; General Public License for more details. 13 | ;;; 14 | ;;; You should have received a copy of the GNU Lesser General Public License 15 | ;;; along with this program. If not, see . 16 | 17 | (define-module (whiffle features) 18 | #:export (whiffle-cond-expand 19 | check-heap-consistency-feature 20 | precise-gc-feature)) 21 | 22 | (define check-heap-consistency-feature (make-parameter #f)) 23 | (define precise-gc-feature (make-parameter #f)) 24 | 25 | (define-syntax whiffle-cond-expand 26 | (lambda (x) 27 | (define (condition-matches? condition) 28 | (syntax-case condition (and or not) 29 | ((and c ...) 30 | (and-map condition-matches? #'(c ...))) 31 | ((or c ...) 32 | (or-map condition-matches? #'(c ...))) 33 | ((not c) 34 | (if (condition-matches? #'c) #f #t)) 35 | (c 36 | (identifier? #'c) 37 | (memq (syntax->datum #'c) (whiffle-features))))) 38 | 39 | (define (match clauses alternate) 40 | (syntax-case clauses () 41 | (((condition form ...) . rest) 42 | (if (condition-matches? #'condition) 43 | #'(begin form ...) 44 | (match #'rest alternate))) 45 | (() (alternate)))) 46 | 47 | (syntax-case x (else) 48 | ((_ clause ... (else form ...)) 49 | (match #'(clause ...) 50 | (lambda () 51 | #'(begin form ...)))) 52 | ((_ clause ...) 53 | (match #'(clause ...) 54 | (lambda () 55 | (syntax-violation 'cond-expand "unfulfilled cond-expand" x))))))) 56 | 57 | (define (whiffle-features) 58 | (define (add-feature-if test feature features) 59 | (if test (cons feature features) features)) 60 | (add-feature-if 61 | (precise-gc-feature) 'precise-gc 62 | (add-feature-if 63 | (check-heap-consistency-feature) 'check-heap-consistency 64 | '()))) 65 | -------------------------------------------------------------------------------- /module/whiffle/input.scm: -------------------------------------------------------------------------------- 1 | ;;; Lightweight Scheme compiler directly to C. 2 | ;;; Copyright (C) 2023, 2024 Andy Wingo. 3 | 4 | ;;; Derived from (language tree-il compile-bytecode) in Guile, which is: 5 | ;;; Copyright (C) 2020, 2021 Free Software Foundation, Inc. 6 | 7 | ;;; This library is free software; you can redistribute it and/or modify it 8 | ;;; under the terms of the GNU Lesser General Public License as published by 9 | ;;; the Free Software Foundation; either version 3 of the License, or (at 10 | ;;; your option) any later version. 11 | ;;; 12 | ;;; This library is distributed in the hope that it will be useful, but 13 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 15 | ;;; General Public License for more details. 16 | ;;; 17 | ;;; You should have received a copy of the GNU Lesser General Public License 18 | ;;; along with this program. If not, see . 19 | 20 | (define-module (whiffle input) 21 | #:use-module (whiffle paths) 22 | #:use-module (ice-9 match) 23 | #:use-module (language tree-il primitives) 24 | #:export (expand read-and-expand)) 25 | 26 | (save-module-excursion 27 | (lambda () 28 | (set-current-module (resolve-module '(whiffle primitives))) 29 | (add-interesting-primitive! 'call-c-primitive) 30 | (add-interesting-primitive! 'call-c-primitive/result) 31 | (add-interesting-primitive! 'call-c-primitive/thread) 32 | (add-interesting-primitive! 'call-c-primitive/alloc) 33 | (add-interesting-primitive! 'call-c-primitive/pred) 34 | (add-interesting-primitive! 'call-c-primitive/write))) 35 | 36 | (define (make-fresh-whiffle-module) 37 | (let ((mod (make-fresh-user-module))) 38 | (purify-module! mod) 39 | (module-use! mod (resolve-interface '(whiffle primitives))) 40 | mod)) 41 | 42 | (define (expand exp) 43 | (let ((exp #`(let () 44 | (include #,(whiffle-prelude.scm)) 45 | (let () 46 | #,exp)))) 47 | (save-module-excursion 48 | (lambda () 49 | (define mod (make-fresh-whiffle-module)) 50 | (set-current-module mod) 51 | (resolve-primitives (macroexpand exp) mod))))) 52 | 53 | (define (read* port) 54 | (match (let lp () 55 | (match (read-syntax port) 56 | ((? eof-object?) '()) 57 | (expr (cons expr (lp))))) 58 | (() (error "file is empty" (port-filename port))) 59 | (body 60 | #`(begin . #,body)))) 61 | 62 | (define (read-and-expand port) 63 | (expand (read* port))) 64 | 65 | -------------------------------------------------------------------------------- /module/whiffle/paths.scm: -------------------------------------------------------------------------------- 1 | ;;; Lightweight Scheme compiler directly to C. 2 | ;;; Copyright (C) 2023 Andy Wingo. 3 | 4 | ;;; Derived from (language tree-il compile-bytecode) in Guile, which is: 5 | ;;; Copyright (C) 2020, 2021 Free Software Foundation, Inc. 6 | 7 | ;;; This library is free software; you can redistribute it and/or modify it 8 | ;;; under the terms of the GNU Lesser General Public License as published by 9 | ;;; the Free Software Foundation; either version 3 of the License, or (at 10 | ;;; your option) any later version. 11 | ;;; 12 | ;;; This library is distributed in the hope that it will be useful, but 13 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 15 | ;;; General Public License for more details. 16 | ;;; 17 | ;;; You should have received a copy of the GNU Lesser General Public License 18 | ;;; along with this program. If not, see . 19 | 20 | (define-module (whiffle paths) 21 | #:use-module (ice-9 match) 22 | #:export (whiffle-filename 23 | whiffle-build.mk 24 | whiffle-prelude.scm 25 | temp-filename)) 26 | 27 | (define (resolve filename) 28 | (cond 29 | ((search-path %load-path filename) 30 | => (lambda (filename) 31 | (dirname (dirname (dirname filename))))) 32 | (else 33 | (error "don't know current file name!" filename)))) 34 | 35 | (define whiffledir 36 | (delay (resolve (assq-ref (or (current-source-location) '()) 'filename)))) 37 | 38 | (define tmpdir 39 | (or (getenv "TMPDIR") "/tmp")) 40 | 41 | (define (whiffle-filename . parts) 42 | (let lp ((result (force whiffledir)) (parts parts)) 43 | (match parts 44 | (() result) 45 | ((part . parts) (lp (in-vicinity result part) parts))))) 46 | 47 | (define (whiffle-build.mk) 48 | (whiffle-filename "build.mk")) 49 | 50 | (define (whiffle-prelude.scm) 51 | (whiffle-filename "runtime" "prelude.scm")) 52 | 53 | (define (temp-filename filename) 54 | (in-vicinity tmpdir filename)) 55 | -------------------------------------------------------------------------------- /module/whiffle/primitives.scm: -------------------------------------------------------------------------------- 1 | ;;; Lightweight Scheme compiler directly to C. 2 | ;;; Copyright (C) 2023, 2024 Andy Wingo. 3 | 4 | ;;; This library is free software; you can redistribute it and/or modify it 5 | ;;; under the terms of the GNU Lesser General Public License as published by 6 | ;;; the Free Software Foundation; either version 3 of the License, or (at 7 | ;;; your option) any later version. 8 | ;;; 9 | ;;; This library is distributed in the hope that it will be useful, but 10 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of 11 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 12 | ;;; General Public License for more details. 13 | ;;; 14 | ;;; You should have received a copy of the GNU Lesser General Public License 15 | ;;; along with this program. If not, see . 16 | 17 | (define-module (whiffle primitives) 18 | #:pure 19 | #:use-module ((guile) 20 | #:select 21 | (include-from-path 22 | define-syntax-rule 23 | syntax-case syntax quasisyntax unsyntax unsyntax-splicing 24 | with-syntax identifier-syntax identifier? 25 | lambda* define* 26 | 27 | cons* 28 | eval-when 29 | error)) 30 | #:use-module ((srfi srfi-11) 31 | #:select 32 | (let-values let*-values)) 33 | #:use-module ((rnrs bytevectors) 34 | #:select 35 | (bytevector? 36 | bytevector-length bytevector-u8-ref bytevector-u8-set!)) 37 | #:use-module ((guile) 38 | #:select 39 | (_ 40 | ... => else 41 | lambda 42 | define define-values let let* letrec letrec* 43 | or and 44 | begin 45 | if cond case when unless 46 | do 47 | set! 48 | quote quasiquote unquote unquote-splicing 49 | include 50 | define-syntax let-syntax letrec-syntax 51 | syntax-rules syntax-error 52 | cond-expand 53 | 54 | pair? 55 | cons 56 | car 57 | cdr 58 | set-car! 59 | set-cdr! 60 | 61 | exact-integer? 62 | + 63 | - 64 | * 65 | < 66 | <= 67 | = 68 | > 69 | >= 70 | quotient 71 | remainder 72 | 73 | char->integer 74 | integer->char 75 | char? 76 | 77 | eq? 78 | 79 | string? 80 | 81 | symbol? 82 | symbol->string 83 | string->symbol 84 | 85 | vector? 86 | make-vector 87 | vector 88 | vector-length 89 | vector-ref 90 | vector-set! 91 | 92 | variable? 93 | make-variable 94 | variable-ref 95 | variable-set!)) 96 | #:use-module ((whiffle features) 97 | #:select (whiffle-cond-expand)) 98 | #:re-export 99 | (_ 100 | ... => else 101 | lambda 102 | define define-values let let* letrec letrec* let-values let*-values 103 | or and 104 | begin 105 | if cond case when unless 106 | do 107 | set! 108 | quote quasiquote unquote unquote-splicing 109 | include 110 | define-syntax let-syntax letrec-syntax 111 | syntax-rules syntax-error 112 | (whiffle-cond-expand . cond-expand) 113 | include-from-path 114 | define-syntax-rule 115 | syntax-case syntax quasisyntax unsyntax unsyntax-splicing 116 | with-syntax identifier-syntax identifier? 117 | lambda* define* 118 | 119 | ;; The rest of the primitives can only appear in primcalls, so we 120 | ;; expose them as %foo instead of foo, relying on the prelude to wrap 121 | ;; them in lambdas to ensure they are always called with the right 122 | ;; number of arguments, even when used as a value. 123 | 124 | (pair? . %pair?) 125 | (cons . %cons) 126 | (car . %car) 127 | (cdr . %cdr) 128 | (set-car! . %set-car!) 129 | (set-cdr! . %set-cdr!) 130 | 131 | (exact-integer? . %exact-integer?) 132 | (* . %*) 133 | (+ . %+) 134 | (- . %-) 135 | (< . %<) 136 | (<= . %<=) 137 | (= . %=) 138 | (> . %>) 139 | (>= . %>=) 140 | (quotient . %quotient) 141 | (remainder . %remainder) 142 | 143 | (char? . %char?) 144 | (char->integer . %char->integer) 145 | (integer->char . %integer->char) 146 | 147 | (eq? . %eq?) 148 | 149 | (string? . %string?) 150 | 151 | (symbol? . %symbol?) 152 | (symbol->string . %symbol->string) 153 | (string->symbol . %string->symbol) 154 | 155 | (vector? . %vector?) 156 | (make-vector . %make-vector) 157 | (vector . %vector) 158 | (vector-length . %vector-length) 159 | (vector-ref . %vector-ref) 160 | (vector-set! . %vector-set!) 161 | 162 | (variable? . %box?) 163 | (make-variable . %box) 164 | (variable-ref . %box-ref) 165 | (variable-set! . %box-set!) 166 | 167 | (bytevector? . %bytevector?) 168 | (bytevector-length . %bytevector-length) 169 | (bytevector-u8-ref . %bytevector-u8-ref) 170 | (bytevector-u8-set! . %bytevector-u8-set!)) 171 | #:export (call-c-primitive 172 | call-c-primitive/result 173 | call-c-primitive/thread 174 | call-c-primitive/alloc 175 | call-c-primitive/pred 176 | call-c-primitive/write) 177 | ;; Mark as non-declarative, as we should not have inlinable exports. 178 | #:declarative? #f) 179 | 180 | (define (call-c-primitive prim . args) (error "target-only primitive")) 181 | (define (call-c-primitive/result prim . args) (error "target-only primitive")) 182 | (define (call-c-primitive/thread prim . args) (error "target-only primitive")) 183 | (define (call-c-primitive/alloc prim . args) (error "target-only primitive")) 184 | (define (call-c-primitive/pred prim . args) (error "target-only primitive")) 185 | (define (call-c-primitive/write prim . args) (error "target-only primitive")) 186 | -------------------------------------------------------------------------------- /pre-inst-env: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | top_srcdir=$(cd $(dirname $0) && pwd) 4 | 5 | export GUILE_LOAD_COMPILED_PATH="$top_srcdir/module${GUILE_LOAD_COMPILED_PATH:+:}$GUILE_LOAD_COMPILED_PATH" 6 | export GUILE_LOAD_PATH="$top_srcdir/module${GUILE_LOAD_PATH:+:}$GUILE_LOAD_PATH" 7 | export PATH="$top_srcdir/bin${PATH:+:}$PATH" 8 | 9 | exec "$@" 10 | -------------------------------------------------------------------------------- /test/basic.scm: -------------------------------------------------------------------------------- 1 | ;;; Lightweight Scheme compiler directly to C. 2 | ;;; Copyright (C) 2023 Andy Wingo. 3 | 4 | ;;; This library is free software; you can redistribute it and/or modify it 5 | ;;; under the terms of the GNU Lesser General Public License as published by 6 | ;;; the Free Software Foundation; either version 3 of the License, or (at 7 | ;;; your option) any later version. 8 | ;;; 9 | ;;; This library is distributed in the hope that it will be useful, but 10 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of 11 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 12 | ;;; General Public License for more details. 13 | ;;; 14 | ;;; You should have received a copy of the GNU Lesser General Public License 15 | ;;; along with this program. If not, see . 16 | 17 | (use-modules (whiffle run) 18 | (ice-9 match)) 19 | 20 | (define (check-equal expected actual) 21 | (unless (equal? expected actual) 22 | (error (format #f "expected ~s, got ~s" expected actual)))) 23 | 24 | (define (read-values port) 25 | (let ((datum (read port))) 26 | (if (eof-object? datum) 27 | '() 28 | (cons datum 29 | (read-values port))))) 30 | 31 | (define (parse-output str) 32 | (call-with-input-string str read-values)) 33 | 34 | (define (check-expr expr) 35 | (format #t "checking: ~s:" expr) 36 | (force-output) 37 | (check-equal (call-with-values (lambda () (primitive-eval expr)) 38 | list) 39 | (parse-output 40 | (run #:expr `(write ',expr) 41 | #:echo-error? #t 42 | #:parallelism 1))) 43 | (format #t " ok.\n")) 44 | 45 | (define (check-exprs exprs) 46 | (for-each check-expr exprs)) 47 | 48 | (check-exprs 49 | '(42)) 50 | 51 | (format #t "All tests passed.\n") 52 | (exit 0) 53 | -------------------------------------------------------------------------------- /test/examples.scm: -------------------------------------------------------------------------------- 1 | ;;; Lightweight Scheme compiler directly to C. 2 | ;;; Copyright (C) 2023 Andy Wingo. 3 | 4 | ;;; This library is free software; you can redistribute it and/or modify it 5 | ;;; under the terms of the GNU Lesser General Public License as published by 6 | ;;; the Free Software Foundation; either version 3 of the License, or (at 7 | ;;; your option) any later version. 8 | ;;; 9 | ;;; This library is distributed in the hope that it will be useful, but 10 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of 11 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 12 | ;;; General Public License for more details. 13 | ;;; 14 | ;;; You should have received a copy of the GNU Lesser General Public License 15 | ;;; along with this program. If not, see . 16 | 17 | (use-modules (whiffle run) 18 | (whiffle paths) 19 | (ice-9 match)) 20 | 21 | (define (check-equal expected actual) 22 | (unless (equal? expected actual) 23 | (error (format #f "expected ~s, got ~s" expected actual)))) 24 | 25 | (define (wrap-whiffle-expr-for-guile expr) 26 | `(let () 27 | ;; Here define any whiffle primitives that aren't defined in a 28 | ;; normal Guile environment. 29 | (define (writeln x) (write x) (newline)) 30 | (define (displayln x) (display x) (newline)) 31 | (let () 32 | ,expr))) 33 | 34 | (define (load-in-fresh-module filename) 35 | (save-module-excursion 36 | (lambda () 37 | (set-current-module (make-fresh-user-module)) 38 | (primitive-eval (wrap-whiffle-expr-for-guile `(include ,filename)))))) 39 | 40 | (define (read-values port) 41 | (let ((datum (read port))) 42 | (if (eof-object? datum) 43 | '() 44 | (cons datum 45 | (read-values port))))) 46 | 47 | (define (parse-output str) 48 | (call-with-input-string str read-values)) 49 | 50 | (define (check-run example . args) 51 | (format #t "checking: ~s~{ ~s~}:" example args) 52 | (force-output) 53 | (let ((filename (whiffle-filename "examples" example))) 54 | (check-equal (parse-output 55 | (with-output-to-string 56 | (lambda () (apply (load-in-fresh-module filename) args)))) 57 | (parse-output 58 | (run #:input (open-input-file filename) 59 | #:args args 60 | #:echo-error? #t 61 | #:parallelism 1)))) 62 | (format #t " ok.\n")) 63 | 64 | (check-run "peano-fib.scm" 25) 65 | 66 | (format #t "All tests passed.\n") 67 | (exit 0) 68 | -------------------------------------------------------------------------------- /whippet/.gitignore: -------------------------------------------------------------------------------- 1 | /*.o 2 | /*.bdw 3 | /*.semi 4 | /*.mmc 5 | /*.generational-mmc 6 | /*.parallel-mmc 7 | /*.parallel-generational-mmc 8 | /*.stack-conservative-mmc 9 | /*.stack-conservative-generational-mmc 10 | /*.stack-conservative-parallel-mmc 11 | /*.stack-conservative-parallel-generational-mmc 12 | /*.heap-conservative-mmc 13 | /*.heap-conservative-generational-mmc 14 | /*.heap-conservative-parallel-mmc 15 | /*.heap-conservative-parallel-generational-mmc 16 | /.deps/ 17 | -------------------------------------------------------------------------------- /whippet/Makefile: -------------------------------------------------------------------------------- 1 | TESTS = quads mt-gcbench ephemerons finalizers 2 | COLLECTORS = \ 3 | bdw \ 4 | semi \ 5 | \ 6 | pcc \ 7 | generational-pcc \ 8 | \ 9 | mmc \ 10 | stack-conservative-mmc \ 11 | heap-conservative-mmc \ 12 | \ 13 | parallel-mmc \ 14 | stack-conservative-parallel-mmc \ 15 | heap-conservative-parallel-mmc \ 16 | \ 17 | generational-mmc \ 18 | stack-conservative-generational-mmc \ 19 | heap-conservative-generational-mmc \ 20 | \ 21 | parallel-generational-mmc \ 22 | stack-conservative-parallel-generational-mmc \ 23 | heap-conservative-parallel-generational-mmc 24 | 25 | DEFAULT_BUILD := opt 26 | 27 | BUILD_CFLAGS_opt = -O2 -g 28 | BUILD_CFLAGS_optdebug = -Og -g 29 | BUILD_CFLAGS_debug = -O0 -g 30 | 31 | BUILD_CPPFLAGS_opt = -DNDEBUG 32 | BUILD_CPPFLAGS_optdebug = -DGC_DEBUG=1 33 | BUILD_CPPFLAGS_debug = -DGC_DEBUG=1 34 | 35 | BUILD_CFLAGS = $(BUILD_CFLAGS_$(or $(BUILD),$(DEFAULT_BUILD))) 36 | BUILD_CPPFLAGS = $(BUILD_CPPFLAGS_$(or $(BUILD),$(DEFAULT_BUILD))) 37 | 38 | USE_LTTNG_0 := 39 | USE_LTTNG_1 := 1 40 | USE_LTTNG := $(shell pkg-config --exists lttng-ust && echo 1 || echo 0) 41 | LTTNG_CPPFLAGS := $(if $(USE_LTTNG_$(USE_LTTNG)), $(shell pkg-config --cflags lttng-ust),) 42 | LTTNG_LIBS := $(if $(USE_LTTNG_$(USE_LTTNG)), $(shell pkg-config --libs lttng-ust),) 43 | TRACEPOINT_CPPFLAGS = $(if $(USE_LTTNG_$(USE_LTTNG)),$(LTTNG_CPPFLAGS) -DGC_TRACEPOINT_LTTNG=1,) 44 | TRACEPOINT_LIBS = $(LTTNG_LIBS) 45 | 46 | CC = gcc 47 | CFLAGS = -Wall -flto -fno-strict-aliasing -fvisibility=hidden -Wno-unused $(BUILD_CFLAGS) 48 | CPPFLAGS = -Iapi $(TRACEPOINT_CPPFLAGS) $(BUILD_CPPFLAGS) 49 | LDFLAGS = -lpthread -flto=auto $(TRACEPOINT_LIBS) 50 | DEPFLAGS = -MMD -MP -MF $(@:obj/%.o=.deps/%.d) 51 | COMPILE = $(CC) $(CFLAGS) $(CPPFLAGS) $(DEPFLAGS) -o $@ 52 | LINK = $(CC) $(LDFLAGS) -o $@ 53 | PLATFORM = gnu-linux 54 | 55 | ALL_TESTS = $(foreach COLLECTOR,$(COLLECTORS),$(addsuffix .$(COLLECTOR),$(TESTS))) 56 | 57 | all: $(ALL_TESTS:%=bin/%) 58 | .deps obj bin: ; mkdir -p $@ 59 | 60 | include $(wildcard .deps/*) 61 | 62 | obj/gc-platform.o: src/gc-platform-$(PLATFORM).c | .deps obj 63 | $(COMPILE) -c $< 64 | obj/gc-stack.o: src/gc-stack.c | .deps obj 65 | $(COMPILE) -c $< 66 | obj/gc-options.o: src/gc-options.c | .deps obj 67 | $(COMPILE) -c $< 68 | obj/gc-tracepoint.o: src/gc-tracepoint.c | .deps obj 69 | $(COMPILE) -c $< 70 | obj/%.gc-ephemeron.o: src/gc-ephemeron.c | .deps obj 71 | $(COMPILE) -DGC_EMBEDDER=\"../benchmarks/$*-embedder.h\" -c $< 72 | obj/%.gc-finalizer.o: src/gc-finalizer.c | .deps obj 73 | $(COMPILE) -DGC_EMBEDDER=\"../benchmarks/$*-embedder.h\" -c $< 74 | 75 | GC_STEM_bdw = bdw 76 | GC_CPPFLAGS_bdw = -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 77 | GC_IMPL_CFLAGS_bdw = `pkg-config --cflags bdw-gc` 78 | GC_LIBS_bdw = `pkg-config --libs bdw-gc` 79 | 80 | GC_STEM_semi = semi 81 | GC_CPPFLAGS_semi = -DGC_PRECISE_ROOTS=1 82 | GC_LIBS_semi = -lm 83 | 84 | GC_STEM_pcc = pcc 85 | GC_CPPFLAGS_pcc = -DGC_PRECISE_ROOTS=1 -DGC_PARALLEL=1 86 | GC_LIBS_pcc = -lm 87 | 88 | GC_STEM_generational_pcc = $(GC_STEM_pcc) 89 | GC_CPPFLAGS_generational_pcc = $(GC_CPPFLAGS_pcc) -DGC_GENERATIONAL=1 90 | GC_LIBS_generational_pcc = $(GC_LIBS_pcc) 91 | 92 | define mmc_variant 93 | GC_STEM_$(1) = mmc 94 | GC_CPPFLAGS_$(1) = $(2) 95 | GC_LIBS_$(1) = -lm 96 | endef 97 | 98 | define generational_mmc_variants 99 | $(call mmc_variant,$(1)mmc,$(2)) 100 | $(call mmc_variant,$(1)generational_mmc,$(2) -DGC_GENERATIONAL=1) 101 | endef 102 | 103 | define parallel_mmc_variants 104 | $(call generational_mmc_variants,$(1),$(2)) 105 | $(call generational_mmc_variants,$(1)parallel_,$(2) -DGC_PARALLEL=1) 106 | endef 107 | 108 | define trace_mmc_variants 109 | $(call parallel_mmc_variants,,-DGC_PRECISE_ROOTS=1) 110 | $(call parallel_mmc_variants,stack_conservative_,-DGC_CONSERVATIVE_ROOTS=1) 111 | $(call parallel_mmc_variants,heap_conservative_,-DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1) 112 | endef 113 | 114 | $(eval $(call trace_mmc_variants)) 115 | 116 | # $(1) is the benchmark, $(2) is the collector configuration 117 | make_gc_var = $$($(1)$(subst -,_,$(2))) 118 | gc_impl = $(call make_gc_var,GC_STEM_,$(1)).c 119 | gc_attrs = $(call make_gc_var,GC_STEM_,$(1))-attrs.h 120 | gc_cppflags = $(call make_gc_var,GC_CPPFLAGS_,$(1)) 121 | gc_cppflags += -DGC_ATTRS=\"../api/$(call gc_attrs,$(1))\" 122 | gc_impl_cflags = $(call make_gc_var,GC_IMPL_CFLAGS_,$(1)) 123 | gc_libs = $(call make_gc_var,GC_LIBS_,$(1)) 124 | define benchmark_template 125 | obj/$(1).$(2).gc.o: src/$(call gc_impl,$(2)) | .deps obj 126 | $$(COMPILE) $(call gc_cppflags,$(2)) $(call gc_impl_cflags,$(2)) -DGC_EMBEDDER=\"../benchmarks/$(1)-embedder.h\" -c $$< 127 | obj/$(1).$(2).o: benchmarks/$(1).c | .deps obj 128 | $$(COMPILE) $(call gc_cppflags,$(2)) -c $$< 129 | bin/$(1).$(2): obj/$(1).$(2).gc.o obj/$(1).$(2).o obj/gc-stack.o obj/gc-options.o obj/gc-platform.o obj/gc-tracepoint.o obj/$(1).gc-ephemeron.o obj/$(1).gc-finalizer.o | bin 130 | $$(LINK) $$^ $(call gc_libs,$(2)) 131 | endef 132 | 133 | $(foreach BENCHMARK,$(TESTS),\ 134 | $(foreach COLLECTOR,$(COLLECTORS),\ 135 | $(eval $(call benchmark_template,$(BENCHMARK),$(COLLECTOR))))) 136 | 137 | .PRECIOUS: $(ALL_TESTS) $(OBJS) 138 | 139 | clean: 140 | rm -f $(ALL_TESTS) 141 | rm -rf .deps obj bin 142 | 143 | # Clear some of the default rules. 144 | .SUFFIXES: 145 | .SECONDARY: 146 | %.c:; 147 | Makefile:; 148 | -------------------------------------------------------------------------------- /whippet/README.md: -------------------------------------------------------------------------------- 1 | # Whippet Garbage Collector 2 | 3 | This repository is for development of Whippet, a new garbage collector 4 | implementation, eventually for use in [Guile 5 | Scheme](https://gnu.org/s/guile). 6 | 7 | Whippet is an embed-only C library, designed to be copied into a 8 | program's source tree. It exposes an abstract C API for managed memory 9 | allocation, and provides a number of implementations of that API. 10 | 11 | ## Documentation 12 | 13 | See the [documentation](./doc/README.md). 14 | 15 | ## Features 16 | 17 | - Per-object pinning (with `mmc` collectors) 18 | - Finalization (supporting resuscitation) 19 | - Ephemerons (except on `bdw`, which has a polyfill) 20 | - Conservative roots (optionally with `mmc` or always with `bdw`) 21 | - Precise roots (optionally with `mmc` or always with `semi` / `pcc`) 22 | - Precise embedder-parameterized heap tracing (except with `bdw`) 23 | - Conservative heap tracing (optionally with `mmc`, always with `bdw`) 24 | - Parallel tracing (except `semi`) 25 | - Parallel mutators (except `semi`) 26 | - Inline allocation / write barrier fast paths (supporting JIT) 27 | - One unified API with no-overhead abstraction: switch collectors when 28 | you like 29 | - Three policies for sizing heaps: fixed, proportional to live size, and 30 | [MemBalancer](http://marisa.moe/balancer.html) 31 | 32 | ## Source repository structure 33 | 34 | * [api/](./api/): The user-facing API. Also, the "embedder API"; see 35 | the [manual](./doc/manual.md) for more. 36 | * [doc/](./doc/): Documentation, such as it is. 37 | * [src/](./src/): The actual GC implementation, containing a number of 38 | collector implementations. The embedder chooses which collector to 39 | use at compile-time. See the [documentation](./doc/collectors.md) 40 | for more on the different collectors (`semi`, `bdw`, `pcc`, and the 41 | different flavors of `mmc`). 42 | * [benchmarks/](./benchmarks/): Benchmarks. A work in progress. 43 | * [test/](./test/): A dusty attic of minimal testing. 44 | 45 | ## Status and roadmap 46 | 47 | As of January 2025, Whippet is good to go! Of course there will surely 48 | be new features to build as Whippet gets integrated it into language 49 | run-times, but the basics are there. 50 | 51 | The next phase on the roadmap is support for tracing, and 52 | some performance noodling. 53 | 54 | Once that is done, the big task is integrating Whippet into the [Guile 55 | Scheme](https://gnu.org/s/guile) language run-time, replacing BDW-GC. 56 | Fingers crossed! 57 | 58 | ## About the name 59 | 60 | It sounds better than WIP (work-in-progress) garbage collector, doesn't 61 | it? Also apparently a whippet is a kind of dog that is fast for its 62 | size. It would be nice if the Whippet collectors turn out to have this 63 | property. 64 | 65 | ## License 66 | 67 | ``` 68 | Copyright (c) 2022-2024 Andy Wingo 69 | 70 | Permission is hereby granted, free of charge, to any person obtaining a 71 | copy of this software and associated documentation files (the 72 | "Software"), to deal in the Software without restriction, including 73 | without limitation the rights to use, copy, modify, merge, publish, 74 | distribute, sublicense, and/or sell copies of the Software, and to 75 | permit persons to whom the Software is furnished to do so, subject to 76 | the following conditions: 77 | 78 | The above copyright notice and this permission notice shall be included 79 | in all copies or substantial portions of the Software. 80 | 81 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 82 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 83 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 84 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 85 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 86 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 87 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 88 | ``` 89 | 90 | Note that some benchmarks have other licenses; see 91 | [`benchmarks/README.md`](./benchmarks/README.md) for more. 92 | -------------------------------------------------------------------------------- /whippet/api/bdw-attrs.h: -------------------------------------------------------------------------------- 1 | #ifndef BDW_ATTRS_H 2 | #define BDW_ATTRS_H 3 | 4 | #include "gc-attrs.h" 5 | #include "gc-assert.h" 6 | 7 | static inline enum gc_allocator_kind gc_allocator_kind(void) { 8 | return GC_ALLOCATOR_INLINE_FREELIST; 9 | } 10 | static inline size_t gc_allocator_small_granule_size(void) { 11 | return 2 * sizeof(void *); 12 | } 13 | static inline size_t gc_allocator_large_threshold(void) { 14 | return 256; 15 | } 16 | 17 | static inline size_t gc_allocator_allocation_pointer_offset(void) { 18 | GC_CRASH(); 19 | } 20 | static inline size_t gc_allocator_allocation_limit_offset(void) { 21 | GC_CRASH(); 22 | } 23 | 24 | static inline size_t gc_allocator_freelist_offset(size_t size, 25 | enum gc_allocation_kind kind) { 26 | GC_ASSERT(size); 27 | size_t base; 28 | switch (kind) { 29 | case GC_ALLOCATION_TAGGED: 30 | case GC_ALLOCATION_UNTAGGED_CONSERVATIVE: 31 | base = 0; 32 | break; 33 | case GC_ALLOCATION_UNTAGGED_POINTERLESS: 34 | case GC_ALLOCATION_TAGGED_POINTERLESS: 35 | base = (sizeof(void*) * gc_allocator_large_threshold() / 36 | gc_allocator_small_granule_size()); 37 | break; 38 | } 39 | size_t bucket = (size - 1) / gc_allocator_small_granule_size(); 40 | return base + sizeof(void*) * bucket; 41 | } 42 | 43 | static inline size_t gc_allocator_alloc_table_alignment(void) { 44 | return 0; 45 | } 46 | static inline uint8_t gc_allocator_alloc_table_begin_pattern(enum gc_allocation_kind) { 47 | GC_CRASH(); 48 | } 49 | static inline uint8_t gc_allocator_alloc_table_end_pattern(void) { 50 | GC_CRASH(); 51 | } 52 | 53 | static inline enum gc_old_generation_check_kind gc_old_generation_check_kind(size_t) { 54 | return GC_OLD_GENERATION_CHECK_NONE; 55 | } 56 | static inline uint8_t gc_old_generation_check_alloc_table_tag_mask(void) { 57 | GC_CRASH(); 58 | } 59 | static inline uint8_t gc_old_generation_check_alloc_table_young_tag(void) { 60 | GC_CRASH(); 61 | } 62 | 63 | static inline enum gc_write_barrier_kind gc_write_barrier_kind(size_t) { 64 | return GC_WRITE_BARRIER_NONE; 65 | } 66 | static inline size_t gc_write_barrier_field_table_alignment(void) { 67 | GC_CRASH(); 68 | } 69 | static inline ptrdiff_t gc_write_barrier_field_table_offset(void) { 70 | GC_CRASH(); 71 | } 72 | static inline size_t gc_write_barrier_field_fields_per_byte(void) { 73 | GC_CRASH(); 74 | } 75 | static inline uint8_t gc_write_barrier_field_first_bit_pattern(void) { 76 | GC_CRASH(); 77 | } 78 | 79 | static inline enum gc_safepoint_mechanism gc_safepoint_mechanism(void) { 80 | return GC_SAFEPOINT_MECHANISM_SIGNAL; 81 | } 82 | 83 | static inline enum gc_cooperative_safepoint_kind gc_cooperative_safepoint_kind(void) { 84 | return GC_COOPERATIVE_SAFEPOINT_NONE; 85 | } 86 | 87 | static inline int gc_can_pin_objects(void) { 88 | return 1; 89 | } 90 | 91 | #endif // BDW_ATTRS_H 92 | -------------------------------------------------------------------------------- /whippet/api/gc-allocation-kind.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_ALLOCATION_KIND_H 2 | #define GC_ALLOCATION_KIND_H 3 | 4 | enum gc_allocation_kind { 5 | // An object whose type can be inspected at run-time based on its contents, 6 | // and whose fields be traced via the gc_trace_object procedure. 7 | GC_ALLOCATION_TAGGED, 8 | // Like GC_ALLOCATION_TAGGED, but not containing any fields that reference 9 | // GC-managed objects. The GC may choose to handle these specially. 10 | GC_ALLOCATION_TAGGED_POINTERLESS, 11 | // A raw allocation whose type cannot be inspected at trace-time, and whose 12 | // fields should be traced conservatively. 13 | GC_ALLOCATION_UNTAGGED_CONSERVATIVE, 14 | // A raw allocation whose type cannot be inspected at trace-time, but 15 | // containing no fields that reference GC-managed objects. 16 | GC_ALLOCATION_UNTAGGED_POINTERLESS 17 | }; 18 | 19 | #endif // GC_ALLOCATION_KIND_H 20 | -------------------------------------------------------------------------------- /whippet/api/gc-assert.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_ASSERT_H 2 | #define GC_ASSERT_H 3 | 4 | #include "gc-config.h" 5 | 6 | #define GC_UNLIKELY(e) __builtin_expect(e, 0) 7 | #define GC_LIKELY(e) __builtin_expect(e, 1) 8 | 9 | #define GC_CRASH() __builtin_trap() 10 | 11 | #if GC_DEBUG 12 | #define GC_ASSERT(x) do { if (GC_UNLIKELY(!(x))) GC_CRASH(); } while (0) 13 | #define GC_UNREACHABLE() GC_CRASH() 14 | #else 15 | #define GC_ASSERT(x) do { } while (0) 16 | #define GC_UNREACHABLE() __builtin_unreachable() 17 | #endif 18 | 19 | #define GC_ASSERT_EQ(a, b) GC_ASSERT((a) == (b)) 20 | 21 | #endif // GC_ASSERT_H 22 | -------------------------------------------------------------------------------- /whippet/api/gc-attrs.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_ATTRS_H 2 | #define GC_ATTRS_H 3 | 4 | #include "gc-inline.h" 5 | #include "gc-allocation-kind.h" 6 | 7 | #include 8 | #include 9 | 10 | enum gc_allocator_kind { 11 | GC_ALLOCATOR_INLINE_BUMP_POINTER, 12 | GC_ALLOCATOR_INLINE_FREELIST, 13 | GC_ALLOCATOR_INLINE_NONE 14 | }; 15 | 16 | static inline enum gc_allocator_kind gc_allocator_kind(void) GC_ALWAYS_INLINE; 17 | static inline size_t gc_allocator_large_threshold(void) GC_ALWAYS_INLINE; 18 | static inline size_t gc_allocator_small_granule_size(void) GC_ALWAYS_INLINE; 19 | 20 | static inline size_t gc_allocator_allocation_pointer_offset(void) GC_ALWAYS_INLINE; 21 | static inline size_t gc_allocator_allocation_limit_offset(void) GC_ALWAYS_INLINE; 22 | 23 | static inline size_t gc_allocator_freelist_offset(size_t size, 24 | enum gc_allocation_kind kind) GC_ALWAYS_INLINE; 25 | 26 | static inline size_t gc_allocator_alloc_table_alignment(void) GC_ALWAYS_INLINE; 27 | static inline uint8_t gc_allocator_alloc_table_begin_pattern(enum gc_allocation_kind kind) GC_ALWAYS_INLINE; 28 | static inline uint8_t gc_allocator_alloc_table_end_pattern(void) GC_ALWAYS_INLINE; 29 | 30 | enum gc_old_generation_check_kind { 31 | GC_OLD_GENERATION_CHECK_NONE, 32 | GC_OLD_GENERATION_CHECK_ALLOC_TABLE, 33 | GC_OLD_GENERATION_CHECK_SMALL_OBJECT_NURSERY, 34 | GC_OLD_GENERATION_CHECK_SLOW 35 | }; 36 | 37 | static inline enum gc_old_generation_check_kind gc_old_generation_check_kind(size_t obj_size) GC_ALWAYS_INLINE; 38 | 39 | static inline uint8_t gc_old_generation_check_alloc_table_tag_mask(void) GC_ALWAYS_INLINE; 40 | static inline uint8_t gc_old_generation_check_alloc_table_young_tag(void) GC_ALWAYS_INLINE; 41 | 42 | enum gc_write_barrier_kind { 43 | GC_WRITE_BARRIER_NONE, 44 | GC_WRITE_BARRIER_FIELD, 45 | GC_WRITE_BARRIER_SLOW 46 | }; 47 | 48 | static inline enum gc_write_barrier_kind gc_write_barrier_kind(size_t obj_size) GC_ALWAYS_INLINE; 49 | static inline size_t gc_write_barrier_field_table_alignment(void) GC_ALWAYS_INLINE; 50 | static inline ptrdiff_t gc_write_barrier_field_table_offset(void) GC_ALWAYS_INLINE; 51 | static inline size_t gc_write_barrier_field_fields_per_byte(void) GC_ALWAYS_INLINE; 52 | static inline uint8_t gc_write_barrier_field_first_bit_pattern(void) GC_ALWAYS_INLINE; 53 | 54 | enum gc_safepoint_mechanism { 55 | GC_SAFEPOINT_MECHANISM_COOPERATIVE, 56 | GC_SAFEPOINT_MECHANISM_SIGNAL, 57 | }; 58 | static inline enum gc_safepoint_mechanism gc_safepoint_mechanism(void) GC_ALWAYS_INLINE; 59 | 60 | enum gc_cooperative_safepoint_kind { 61 | GC_COOPERATIVE_SAFEPOINT_NONE, 62 | GC_COOPERATIVE_SAFEPOINT_MUTATOR_FLAG, 63 | GC_COOPERATIVE_SAFEPOINT_HEAP_FLAG, 64 | }; 65 | static inline enum gc_cooperative_safepoint_kind gc_cooperative_safepoint_kind(void) GC_ALWAYS_INLINE; 66 | 67 | static inline int gc_can_pin_objects(void) GC_ALWAYS_INLINE; 68 | 69 | #ifndef GC_IMPL 70 | #ifdef GC_ATTRS 71 | #include GC_ATTRS 72 | #else 73 | #error Fix build system to pass -DGC_ATTRS="path/to/foo-attrs.h" for selected GC 74 | #endif // GC_ATTRS 75 | #endif // GC_IMPL 76 | 77 | #endif // GC_ATTRS_H 78 | -------------------------------------------------------------------------------- /whippet/api/gc-collection-kind.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_COLLECTION_KIND_H 2 | #define GC_COLLECTION_KIND_H 3 | 4 | enum gc_collection_kind { 5 | GC_COLLECTION_ANY, 6 | GC_COLLECTION_MINOR, 7 | GC_COLLECTION_MAJOR, 8 | GC_COLLECTION_COMPACTING, 9 | }; 10 | 11 | #endif // GC_COLLECTION_KIND_H 12 | -------------------------------------------------------------------------------- /whippet/api/gc-config.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_CONFIG_H 2 | #define GC_CONFIG_H 3 | 4 | #ifndef GC_DEBUG 5 | #define GC_DEBUG 0 6 | #endif 7 | 8 | #ifndef GC_HAS_IMMEDIATES 9 | #define GC_HAS_IMMEDIATES 1 10 | #endif 11 | 12 | #ifndef GC_PARALLEL 13 | #define GC_PARALLEL 0 14 | #endif 15 | 16 | #ifndef GC_GENERATIONAL 17 | #define GC_GENERATIONAL 0 18 | #endif 19 | 20 | // Though you normally wouldn't configure things this way, it's possible 21 | // to have both precise and conservative roots. However we have to 22 | // either have precise or conservative tracing; not a mix. 23 | 24 | #ifndef GC_PRECISE_ROOTS 25 | #define GC_PRECISE_ROOTS 0 26 | #endif 27 | 28 | #ifndef GC_CONSERVATIVE_ROOTS 29 | #define GC_CONSERVATIVE_ROOTS 0 30 | #endif 31 | 32 | #ifndef GC_CONSERVATIVE_TRACE 33 | #define GC_CONSERVATIVE_TRACE 0 34 | #endif 35 | 36 | #ifndef GC_CONCURRENT_TRACE 37 | #define GC_CONCURRENT_TRACE 0 38 | #endif 39 | 40 | #endif // GC_CONFIG_H 41 | -------------------------------------------------------------------------------- /whippet/api/gc-conservative-ref.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_CONSERVATIVE_REF_H 2 | #define GC_CONSERVATIVE_REF_H 3 | 4 | #include 5 | 6 | struct gc_conservative_ref { 7 | uintptr_t value; 8 | }; 9 | 10 | static inline struct gc_conservative_ref gc_conservative_ref(uintptr_t value) { 11 | return (struct gc_conservative_ref){value}; 12 | } 13 | static inline uintptr_t gc_conservative_ref_value(struct gc_conservative_ref ref) { 14 | return ref.value; 15 | } 16 | 17 | #endif // GC_CONSERVATIVE_REF_H 18 | -------------------------------------------------------------------------------- /whippet/api/gc-edge.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_EDGE_H 2 | #define GC_EDGE_H 3 | 4 | #include "gc-ref.h" 5 | 6 | struct gc_edge { 7 | struct gc_ref *dst; 8 | }; 9 | 10 | static inline struct gc_edge gc_edge(void* addr) { 11 | return (struct gc_edge){addr}; 12 | } 13 | static inline struct gc_ref gc_edge_ref(struct gc_edge edge) { 14 | return *edge.dst; 15 | } 16 | static inline struct gc_ref* gc_edge_loc(struct gc_edge edge) { 17 | return edge.dst; 18 | } 19 | static inline uintptr_t gc_edge_address(struct gc_edge edge) { 20 | return (uintptr_t)gc_edge_loc(edge); 21 | } 22 | static inline void gc_edge_update(struct gc_edge edge, struct gc_ref ref) { 23 | *edge.dst = ref; 24 | } 25 | 26 | #endif // GC_EDGE_H 27 | -------------------------------------------------------------------------------- /whippet/api/gc-embedder-api.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_EMBEDDER_API_H 2 | #define GC_EMBEDDER_API_H 3 | 4 | #include 5 | 6 | #include "gc-config.h" 7 | #include "gc-edge.h" 8 | #include "gc-inline.h" 9 | #include "gc-forwarding.h" 10 | 11 | #ifndef GC_EMBEDDER_API 12 | #define GC_EMBEDDER_API static 13 | #endif 14 | 15 | struct gc_mutator_roots; 16 | struct gc_heap_roots; 17 | struct gc_atomic_forward; 18 | struct gc_heap; 19 | struct gc_extern_space; 20 | 21 | GC_EMBEDDER_API inline int gc_is_valid_conservative_ref_displacement(uintptr_t displacement); 22 | GC_EMBEDDER_API inline size_t gc_finalizer_priority_count(void); 23 | 24 | GC_EMBEDDER_API inline int gc_extern_space_visit(struct gc_extern_space *space, 25 | struct gc_edge edge, 26 | struct gc_ref ref) GC_ALWAYS_INLINE; 27 | GC_EMBEDDER_API inline void gc_extern_space_start_gc(struct gc_extern_space *space, 28 | int is_minor_gc); 29 | GC_EMBEDDER_API inline void gc_extern_space_finish_gc(struct gc_extern_space *space, 30 | int is_minor_gc); 31 | 32 | GC_EMBEDDER_API inline void gc_trace_object(struct gc_ref ref, 33 | void (*visit)(struct gc_edge edge, 34 | struct gc_heap *heap, 35 | void *visit_data), 36 | struct gc_heap *heap, 37 | void *trace_data, 38 | size_t *size) GC_ALWAYS_INLINE; 39 | 40 | GC_EMBEDDER_API inline void gc_trace_mutator_roots(struct gc_mutator_roots *roots, 41 | void (*trace_edge)(struct gc_edge edge, 42 | struct gc_heap *heap, 43 | void *trace_data), 44 | struct gc_heap *heap, 45 | void *trace_data); 46 | GC_EMBEDDER_API inline void gc_trace_heap_roots(struct gc_heap_roots *roots, 47 | void (*trace_edge)(struct gc_edge edge, 48 | struct gc_heap *heap, 49 | void *trace_data), 50 | struct gc_heap *heap, 51 | void *trace_data); 52 | 53 | GC_EMBEDDER_API inline uintptr_t gc_object_forwarded_nonatomic(struct gc_ref ref); 54 | GC_EMBEDDER_API inline void gc_object_forward_nonatomic(struct gc_ref ref, 55 | struct gc_ref new_ref); 56 | 57 | GC_EMBEDDER_API inline struct gc_atomic_forward gc_atomic_forward_begin(struct gc_ref ref); 58 | GC_EMBEDDER_API inline void gc_atomic_forward_acquire(struct gc_atomic_forward *); 59 | GC_EMBEDDER_API inline int gc_atomic_forward_retry_busy(struct gc_atomic_forward *); 60 | GC_EMBEDDER_API inline void gc_atomic_forward_abort(struct gc_atomic_forward *); 61 | GC_EMBEDDER_API inline size_t gc_atomic_forward_object_size(struct gc_atomic_forward *); 62 | GC_EMBEDDER_API inline void gc_atomic_forward_commit(struct gc_atomic_forward *, 63 | struct gc_ref new_ref); 64 | GC_EMBEDDER_API inline uintptr_t gc_atomic_forward_address(struct gc_atomic_forward *); 65 | 66 | 67 | #endif // GC_EMBEDDER_API_H 68 | -------------------------------------------------------------------------------- /whippet/api/gc-ephemeron.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_EPHEMERON_H_ 2 | #define GC_EPHEMERON_H_ 3 | 4 | #include "gc-edge.h" 5 | #include "gc-ref.h" 6 | #include "gc-visibility.h" 7 | 8 | // Ephemerons establish an association between a "key" object and a 9 | // "value" object. If the ephemeron and the key are live, then the 10 | // value is live, and can be retrieved from the ephemeron. Ephemerons 11 | // can be chained together, which allows them to function as links in a 12 | // buckets-and-chains hash table. 13 | // 14 | // This file defines the user-facing API for ephemerons. 15 | 16 | struct gc_heap; 17 | struct gc_mutator; 18 | struct gc_ephemeron; 19 | 20 | GC_API_ size_t gc_ephemeron_size(void); 21 | GC_API_ struct gc_ephemeron* gc_allocate_ephemeron(struct gc_mutator *mut); 22 | GC_API_ void gc_ephemeron_init(struct gc_mutator *mut, 23 | struct gc_ephemeron *ephemeron, 24 | struct gc_ref key, struct gc_ref value); 25 | 26 | GC_API_ struct gc_ref gc_ephemeron_key(struct gc_ephemeron *ephemeron); 27 | GC_API_ struct gc_ref gc_ephemeron_value(struct gc_ephemeron *ephemeron); 28 | 29 | GC_API_ struct gc_ephemeron* gc_ephemeron_chain_head(struct gc_ephemeron **loc); 30 | GC_API_ void gc_ephemeron_chain_push(struct gc_ephemeron **loc, 31 | struct gc_ephemeron *ephemeron); 32 | GC_API_ struct gc_ephemeron* gc_ephemeron_chain_next(struct gc_ephemeron *ephemeron); 33 | GC_API_ void gc_ephemeron_mark_dead(struct gc_ephemeron *ephemeron); 34 | 35 | GC_API_ void gc_trace_ephemeron(struct gc_ephemeron *ephemeron, 36 | void (*visit)(struct gc_edge edge, 37 | struct gc_heap *heap, 38 | void *visit_data), 39 | struct gc_heap *heap, 40 | void *trace_data); 41 | 42 | #endif // GC_EPHEMERON_H_ 43 | -------------------------------------------------------------------------------- /whippet/api/gc-event-listener.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_EVENT_LISTENER_H 2 | #define GC_EVENT_LISTENER_H 3 | 4 | #include "gc-collection-kind.h" 5 | 6 | struct gc_event_listener { 7 | void (*init)(void *data, size_t heap_size); 8 | void (*requesting_stop)(void *data); 9 | void (*waiting_for_stop)(void *data); 10 | void (*mutators_stopped)(void *data); 11 | void (*prepare_gc)(void *data, enum gc_collection_kind kind); 12 | void (*roots_traced)(void *data); 13 | void (*heap_traced)(void *data); 14 | void (*ephemerons_traced)(void *data); 15 | void (*finalizers_traced)(void *data); 16 | void (*restarting_mutators)(void *data); 17 | 18 | void* (*mutator_added)(void *data); 19 | void (*mutator_cause_gc)(void *mutator_data); 20 | void (*mutator_stopping)(void *mutator_data); 21 | void (*mutator_stopped)(void *mutator_data); 22 | void (*mutator_restarted)(void *mutator_data); 23 | void (*mutator_removed)(void *mutator_data); 24 | 25 | void (*heap_resized)(void *data, size_t size); 26 | void (*live_data_size)(void *data, size_t size); 27 | }; 28 | 29 | #endif // GC_EVENT_LISTENER_H 30 | -------------------------------------------------------------------------------- /whippet/api/gc-finalizer.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_FINALIZER_H_ 2 | #define GC_FINALIZER_H_ 3 | 4 | #include "gc-edge.h" 5 | #include "gc-ref.h" 6 | #include "gc-visibility.h" 7 | 8 | // A finalizer allows the embedder to be notified when an object becomes 9 | // unreachable. 10 | // 11 | // A finalizer has a priority. When the heap is created, the embedder 12 | // should declare how many priorities there are. Lower-numbered 13 | // priorities take precedence; if an object has a priority-0 finalizer 14 | // outstanding, that will prevent any finalizer at level 1 (or 2, ...) 15 | // from firing until no priority-0 finalizer remains. 16 | // 17 | // Call gc_attach_finalizer to attach a finalizer to an object. 18 | // 19 | // A finalizer also references an associated GC-managed closure object. 20 | // A finalizer's reference to the closure object is strong: if a 21 | // finalizer's closure closure references its finalizable object, 22 | // directly or indirectly, the finalizer will never fire. 23 | // 24 | // When an object with a finalizer becomes unreachable, it is added to a 25 | // queue. The embedder can call gc_pop_finalizable to get the next 26 | // finalizable object and its associated closure. At that point the 27 | // embedder can do anything with the object, including keeping it alive. 28 | // Ephemeron associations will still be present while the finalizable 29 | // object is live. Note however that any objects referenced by the 30 | // finalizable object may themselves be already finalized; finalizers 31 | // are enqueued for objects when they become unreachable, which can 32 | // concern whole subgraphs of objects at once. 33 | // 34 | // The usual way for an embedder to know when the queue of finalizable 35 | // object is non-empty is to call gc_set_finalizer_callback to 36 | // provide a function that will be invoked when there are pending 37 | // finalizers. 38 | // 39 | // Arranging to call gc_pop_finalizable and doing something with the 40 | // finalizable object and closure is the responsibility of the embedder. 41 | // The embedder's finalization action can end up invoking arbitrary 42 | // code, so unless the embedder imposes some kind of restriction on what 43 | // finalizers can do, generally speaking finalizers should be run in a 44 | // dedicated thread instead of recursively from within whatever mutator 45 | // thread caused GC. Setting up such a thread is the responsibility of 46 | // the mutator. gc_pop_finalizable is thread-safe, allowing multiple 47 | // finalization threads if that is appropriate. 48 | // 49 | // gc_allocate_finalizer returns a finalizer, which is a fresh 50 | // GC-managed heap object. The mutator should then directly attach it 51 | // to an object using gc_finalizer_attach. When the finalizer is fired, 52 | // it becomes available to the mutator via gc_pop_finalizable. 53 | 54 | struct gc_heap; 55 | struct gc_mutator; 56 | struct gc_finalizer; 57 | 58 | GC_API_ size_t gc_finalizer_size(void); 59 | GC_API_ struct gc_finalizer* gc_allocate_finalizer(struct gc_mutator *mut); 60 | GC_API_ void gc_finalizer_attach(struct gc_mutator *mut, 61 | struct gc_finalizer *finalizer, 62 | unsigned priority, 63 | struct gc_ref object, struct gc_ref closure); 64 | 65 | GC_API_ struct gc_ref gc_finalizer_object(struct gc_finalizer *finalizer); 66 | GC_API_ struct gc_ref gc_finalizer_closure(struct gc_finalizer *finalizer); 67 | 68 | GC_API_ struct gc_finalizer* gc_pop_finalizable(struct gc_mutator *mut); 69 | 70 | typedef void (*gc_finalizer_callback)(struct gc_heap *heap, size_t count); 71 | GC_API_ void gc_set_finalizer_callback(struct gc_heap *heap, 72 | gc_finalizer_callback callback); 73 | 74 | GC_API_ void gc_trace_finalizer(struct gc_finalizer *finalizer, 75 | void (*visit)(struct gc_edge edge, 76 | struct gc_heap *heap, 77 | void *visit_data), 78 | struct gc_heap *heap, 79 | void *trace_data); 80 | 81 | #endif // GC_FINALIZER_H_ 82 | -------------------------------------------------------------------------------- /whippet/api/gc-forwarding.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_FORWARDING_H 2 | #define GC_FORWARDING_H 3 | 4 | #include 5 | #include "gc-ref.h" 6 | 7 | enum gc_forwarding_state { 8 | GC_FORWARDING_STATE_FORWARDED, 9 | GC_FORWARDING_STATE_BUSY, 10 | GC_FORWARDING_STATE_ACQUIRED, 11 | GC_FORWARDING_STATE_NOT_FORWARDED 12 | }; 13 | 14 | struct gc_atomic_forward { 15 | struct gc_ref ref; 16 | uintptr_t data; 17 | enum gc_forwarding_state state; 18 | }; 19 | 20 | #endif // GC_FORWARDING_H 21 | -------------------------------------------------------------------------------- /whippet/api/gc-histogram.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_HISTOGRAM_H 2 | #define GC_HISTOGRAM_H 3 | 4 | #include "gc-assert.h" 5 | 6 | #include 7 | 8 | static inline size_t gc_histogram_bucket(uint64_t max_value_bits, 9 | uint64_t precision, 10 | uint64_t val) { 11 | uint64_t major = val < (1ULL << precision) 12 | ? 0ULL 13 | : 64ULL - __builtin_clzl(val) - precision; 14 | uint64_t minor = val < (1 << precision) 15 | ? val 16 | : (val >> (major - 1ULL)) & ((1ULL << precision) - 1ULL); 17 | uint64_t idx = (major << precision) | minor; 18 | if (idx >= (max_value_bits << precision)) 19 | idx = max_value_bits << precision; 20 | return idx; 21 | } 22 | 23 | static inline uint64_t gc_histogram_bucket_min_val(uint64_t precision, 24 | size_t idx) { 25 | uint64_t major = idx >> precision; 26 | uint64_t minor = idx & ((1ULL << precision) - 1ULL); 27 | uint64_t min_val = major 28 | ? ((1ULL << precision) | minor) << (major - 1ULL) 29 | : minor; 30 | return min_val; 31 | } 32 | 33 | #define GC_DEFINE_HISTOGRAM(name, max_value_bits, precision) \ 34 | struct name { uint32_t buckets[((max_value_bits) << (precision)) + 1]; }; \ 35 | static inline size_t name##_size(void) { \ 36 | return ((max_value_bits) << (precision)) + 1; \ 37 | } \ 38 | static inline uint64_t name##_bucket_min_val(size_t idx) { \ 39 | GC_ASSERT(idx < name##_size()); \ 40 | return gc_histogram_bucket_min_val((precision), idx); \ 41 | } \ 42 | static inline struct name make_##name(void) { \ 43 | return (struct name) { { 0, }}; \ 44 | } \ 45 | static inline void name##_record(struct name *h, uint64_t val) { \ 46 | h->buckets[gc_histogram_bucket((max_value_bits), (precision), val)]++; \ 47 | } \ 48 | static inline uint64_t name##_ref(struct name *h, size_t idx) { \ 49 | GC_ASSERT(idx < name##_size()); \ 50 | return h->buckets[idx]; \ 51 | } \ 52 | static inline uint64_t name##_min(struct name *h) { \ 53 | for (size_t bucket = 0; bucket < name##_size(); bucket++) \ 54 | if (h->buckets[bucket]) return name##_bucket_min_val(bucket); \ 55 | return -1; \ 56 | } \ 57 | static inline uint64_t name##_max(struct name *h) { \ 58 | if (h->buckets[name##_size()-1]) return -1LL; \ 59 | for (ssize_t bucket = name##_size() - 1; bucket >= 0; bucket--) \ 60 | if (h->buckets[bucket]) return name##_bucket_min_val(bucket+1); \ 61 | return 0; \ 62 | } \ 63 | static inline uint64_t name##_count(struct name *h) { \ 64 | uint64_t sum = 0; \ 65 | for (size_t bucket = 0; bucket < name##_size(); bucket++) \ 66 | sum += h->buckets[bucket]; \ 67 | return sum; \ 68 | } \ 69 | static inline uint64_t name##_percentile(struct name *h, double p) { \ 70 | uint64_t n = name##_count(h) * p; \ 71 | uint64_t sum = 0; \ 72 | for (size_t bucket = 0; bucket + 1 < name##_size(); bucket++) { \ 73 | sum += h->buckets[bucket]; \ 74 | if (sum >= n) return name##_bucket_min_val(bucket+1); \ 75 | } \ 76 | return -1ULL; \ 77 | } \ 78 | static inline uint64_t name##_median(struct name *h) { \ 79 | return name##_percentile(h, 0.5); \ 80 | } 81 | 82 | #endif // GC_HISTOGRAM_H 83 | -------------------------------------------------------------------------------- /whippet/api/gc-inline.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_INLINE_H_ 2 | #define GC_INLINE_H_ 3 | 4 | #define GC_ALWAYS_INLINE __attribute__((always_inline)) 5 | #define GC_NEVER_INLINE __attribute__((noinline)) 6 | 7 | #endif // GC_INLINE_H_ 8 | -------------------------------------------------------------------------------- /whippet/api/gc-lttng.h: -------------------------------------------------------------------------------- 1 | #define LTTNG_UST_TRACEPOINT_PROVIDER whippet 2 | 3 | #undef LTTNG_UST_TRACEPOINT_INCLUDE 4 | #define LTTNG_UST_TRACEPOINT_INCLUDE "gc-lttng.h" 5 | 6 | #if !defined(_TP_H) || defined(LTTNG_UST_TRACEPOINT_HEADER_MULTI_READ) 7 | #define _TP_H 8 | 9 | #include 10 | 11 | LTTNG_UST_TRACEPOINT_ENUM( 12 | whippet, gc_kind, 13 | LTTNG_UST_TP_ENUM_VALUES 14 | (lttng_ust_field_enum_value("MINOR", 1) 15 | lttng_ust_field_enum_value("MAJOR", 2) 16 | lttng_ust_field_enum_value("COMPACTING", 3))) 17 | 18 | LTTNG_UST_TRACEPOINT_EVENT_CLASS( 19 | whippet, tracepoint, 20 | LTTNG_UST_TP_ARGS(), 21 | LTTNG_UST_TP_FIELDS()) 22 | 23 | LTTNG_UST_TRACEPOINT_EVENT_CLASS( 24 | whippet, size_tracepoint, 25 | LTTNG_UST_TP_ARGS(size_t, size), 26 | LTTNG_UST_TP_FIELDS(lttng_ust_field_integer(size_t, size, size))) 27 | 28 | 29 | /* The tracepoint instances */ 30 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 31 | whippet, size_tracepoint, whippet, init, 32 | LTTNG_UST_TP_ARGS(size_t, size)) 33 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 34 | whippet, size_tracepoint, whippet, heap_resized, 35 | LTTNG_UST_TP_ARGS(size_t, size)) 36 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 37 | whippet, size_tracepoint, whippet, live_data_size, 38 | LTTNG_UST_TP_ARGS(size_t, size)) 39 | 40 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 41 | whippet, tracepoint, whippet, requesting_stop, LTTNG_UST_TP_ARGS()) 42 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 43 | whippet, tracepoint, whippet, waiting_for_stop, LTTNG_UST_TP_ARGS()) 44 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 45 | whippet, tracepoint, whippet, mutators_stopped, LTTNG_UST_TP_ARGS()) 46 | LTTNG_UST_TRACEPOINT_EVENT( 47 | whippet, prepare_gc, 48 | LTTNG_UST_TP_ARGS(int, gc_kind), 49 | LTTNG_UST_TP_FIELDS( 50 | lttng_ust_field_enum(whippet, gc_kind, int, gc_kind, gc_kind))) 51 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 52 | whippet, tracepoint, whippet, roots_traced, LTTNG_UST_TP_ARGS()) 53 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 54 | whippet, tracepoint, whippet, heap_traced, LTTNG_UST_TP_ARGS()) 55 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 56 | whippet, tracepoint, whippet, ephemerons_traced, LTTNG_UST_TP_ARGS()) 57 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 58 | whippet, tracepoint, whippet, finalizers_traced, LTTNG_UST_TP_ARGS()) 59 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 60 | whippet, tracepoint, whippet, restarting_mutators, LTTNG_UST_TP_ARGS()) 61 | 62 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 63 | whippet, tracepoint, whippet, mutator_added, LTTNG_UST_TP_ARGS()) 64 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 65 | whippet, tracepoint, whippet, mutator_cause_gc, LTTNG_UST_TP_ARGS()) 66 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 67 | whippet, tracepoint, whippet, mutator_stopping, LTTNG_UST_TP_ARGS()) 68 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 69 | whippet, tracepoint, whippet, mutator_stopped, LTTNG_UST_TP_ARGS()) 70 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 71 | whippet, tracepoint, whippet, mutator_restarted, LTTNG_UST_TP_ARGS()) 72 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 73 | whippet, tracepoint, whippet, mutator_removed, LTTNG_UST_TP_ARGS()) 74 | 75 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 76 | whippet, tracepoint, whippet, trace_unpark_all, LTTNG_UST_TP_ARGS()) 77 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 78 | whippet, tracepoint, whippet, trace_share, LTTNG_UST_TP_ARGS()) 79 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 80 | whippet, tracepoint, whippet, trace_check_termination_begin, LTTNG_UST_TP_ARGS()) 81 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 82 | whippet, tracepoint, whippet, trace_check_termination_end, LTTNG_UST_TP_ARGS()) 83 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 84 | whippet, tracepoint, whippet, trace_steal, LTTNG_UST_TP_ARGS()) 85 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 86 | whippet, tracepoint, whippet, trace_roots_begin, LTTNG_UST_TP_ARGS()) 87 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 88 | whippet, tracepoint, whippet, trace_roots_end, LTTNG_UST_TP_ARGS()) 89 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 90 | whippet, tracepoint, whippet, trace_objects_begin, LTTNG_UST_TP_ARGS()) 91 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 92 | whippet, tracepoint, whippet, trace_objects_end, LTTNG_UST_TP_ARGS()) 93 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 94 | whippet, tracepoint, whippet, trace_worker_begin, LTTNG_UST_TP_ARGS()) 95 | LTTNG_UST_TRACEPOINT_EVENT_INSTANCE( 96 | whippet, tracepoint, whippet, trace_worker_end, LTTNG_UST_TP_ARGS()) 97 | 98 | #endif /* _TP_H */ 99 | 100 | #include 101 | -------------------------------------------------------------------------------- /whippet/api/gc-null-event-listener.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_NULL_EVENT_LISTENER_H 2 | #define GC_NULL_EVENT_LISTENER_H 3 | 4 | #include "gc-event-listener.h" 5 | 6 | static inline void gc_null_event_listener_init(void *data, size_t size) {} 7 | static inline void gc_null_event_listener_requesting_stop(void *data) {} 8 | static inline void gc_null_event_listener_waiting_for_stop(void *data) {} 9 | static inline void gc_null_event_listener_mutators_stopped(void *data) {} 10 | static inline void gc_null_event_listener_prepare_gc(void *data, 11 | enum gc_collection_kind) {} 12 | static inline void gc_null_event_listener_roots_traced(void *data) {} 13 | static inline void gc_null_event_listener_heap_traced(void *data) {} 14 | static inline void gc_null_event_listener_ephemerons_traced(void *data) {} 15 | static inline void gc_null_event_listener_finalizers_traced(void *data) {} 16 | static inline void gc_null_event_listener_restarting_mutators(void *data) {} 17 | 18 | static inline void* gc_null_event_listener_mutator_added(void *data) {} 19 | static inline void gc_null_event_listener_mutator_cause_gc(void *mutator_data) {} 20 | static inline void gc_null_event_listener_mutator_stopping(void *mutator_data) {} 21 | static inline void gc_null_event_listener_mutator_stopped(void *mutator_data) {} 22 | static inline void gc_null_event_listener_mutator_restarted(void *mutator_data) {} 23 | static inline void gc_null_event_listener_mutator_removed(void *mutator_data) {} 24 | 25 | static inline void gc_null_event_listener_heap_resized(void *, size_t) {} 26 | static inline void gc_null_event_listener_live_data_size(void *, size_t) {} 27 | 28 | #define GC_NULL_EVENT_LISTENER \ 29 | ((struct gc_event_listener) { \ 30 | gc_null_event_listener_init, \ 31 | gc_null_event_listener_requesting_stop, \ 32 | gc_null_event_listener_waiting_for_stop, \ 33 | gc_null_event_listener_mutators_stopped, \ 34 | gc_null_event_listener_prepare_gc, \ 35 | gc_null_event_listener_roots_traced, \ 36 | gc_null_event_listener_heap_traced, \ 37 | gc_null_event_listener_ephemerons_traced, \ 38 | gc_null_event_listener_finalizers_traced, \ 39 | gc_null_event_listener_restarting_mutators, \ 40 | gc_null_event_listener_mutator_added, \ 41 | gc_null_event_listener_mutator_cause_gc, \ 42 | gc_null_event_listener_mutator_stopping, \ 43 | gc_null_event_listener_mutator_stopped, \ 44 | gc_null_event_listener_mutator_restarted, \ 45 | gc_null_event_listener_mutator_removed, \ 46 | gc_null_event_listener_heap_resized, \ 47 | gc_null_event_listener_live_data_size, \ 48 | }) 49 | 50 | #endif // GC_NULL_EVENT_LISTENER_H_ 51 | -------------------------------------------------------------------------------- /whippet/api/gc-options.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_OPTIONS_H 2 | #define GC_OPTIONS_H 3 | 4 | #include "gc-visibility.h" 5 | 6 | enum gc_heap_size_policy { 7 | GC_HEAP_SIZE_FIXED, 8 | GC_HEAP_SIZE_GROWABLE, 9 | GC_HEAP_SIZE_ADAPTIVE, 10 | }; 11 | 12 | enum { 13 | GC_OPTION_HEAP_SIZE_POLICY, 14 | GC_OPTION_HEAP_SIZE, 15 | GC_OPTION_MAXIMUM_HEAP_SIZE, 16 | GC_OPTION_HEAP_SIZE_MULTIPLIER, 17 | GC_OPTION_HEAP_EXPANSIVENESS, 18 | GC_OPTION_PARALLELISM 19 | }; 20 | 21 | struct gc_options; 22 | 23 | GC_API_ int gc_option_from_string(const char *str); 24 | 25 | GC_API_ struct gc_options* gc_allocate_options(void); 26 | 27 | GC_API_ int gc_options_set_int(struct gc_options *options, int option, 28 | int value); 29 | GC_API_ int gc_options_set_size(struct gc_options *options, int option, 30 | size_t value); 31 | GC_API_ int gc_options_set_double(struct gc_options *options, int option, 32 | double value); 33 | 34 | GC_API_ int gc_options_parse_and_set(struct gc_options *options, 35 | int option, const char *value); 36 | GC_API_ int gc_options_parse_and_set_many(struct gc_options *options, 37 | const char *str); 38 | 39 | #endif // GC_OPTIONS_H 40 | -------------------------------------------------------------------------------- /whippet/api/gc-ref.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_REF_H 2 | #define GC_REF_H 3 | 4 | #include "gc-assert.h" 5 | #include "gc-config.h" 6 | 7 | #include 8 | 9 | struct gc_ref { 10 | uintptr_t value; 11 | }; 12 | 13 | static inline struct gc_ref gc_ref(uintptr_t value) { 14 | return (struct gc_ref){value}; 15 | } 16 | static inline uintptr_t gc_ref_value(struct gc_ref ref) { 17 | return ref.value; 18 | } 19 | 20 | static inline struct gc_ref gc_ref_null(void) { 21 | return gc_ref(0); 22 | } 23 | static inline int gc_ref_is_null(struct gc_ref ref) { 24 | return ref.value == 0; 25 | } 26 | static inline int gc_ref_is_immediate(struct gc_ref ref) { 27 | GC_ASSERT(!gc_ref_is_null(ref)); 28 | return GC_HAS_IMMEDIATES && (ref.value & (sizeof(void*) - 1)); 29 | } 30 | static inline struct gc_ref gc_ref_immediate(uintptr_t val) { 31 | GC_ASSERT(val & (sizeof(void*) - 1)); 32 | GC_ASSERT(GC_HAS_IMMEDIATES); 33 | return gc_ref(val); 34 | } 35 | static inline int gc_ref_is_heap_object(struct gc_ref ref) { 36 | return !gc_ref_is_immediate(ref); 37 | } 38 | static inline struct gc_ref gc_ref_from_heap_object_or_null(void *obj) { 39 | return gc_ref((uintptr_t) obj); 40 | } 41 | static inline struct gc_ref gc_ref_from_heap_object(void *obj) { 42 | GC_ASSERT(obj); 43 | return gc_ref_from_heap_object_or_null(obj); 44 | } 45 | static inline void* gc_ref_heap_object(struct gc_ref ref) { 46 | GC_ASSERT(gc_ref_is_heap_object(ref)); 47 | return (void *) gc_ref_value(ref); 48 | } 49 | 50 | #endif // GC_REF_H 51 | -------------------------------------------------------------------------------- /whippet/api/gc-stack-addr.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_STACK_ADDR_H 2 | #define GC_STACK_ADDR_H 3 | 4 | #include "gc-assert.h" 5 | #include "gc-visibility.h" 6 | 7 | struct gc_stack_addr { 8 | uintptr_t addr; 9 | }; 10 | 11 | static inline struct gc_stack_addr gc_empty_stack_addr (void) { 12 | return (struct gc_stack_addr){ 0 }; 13 | }; 14 | 15 | static inline int gc_stack_addr_is_empty (struct gc_stack_addr addr) { 16 | return addr.addr == 0; 17 | }; 18 | 19 | static inline char* gc_stack_addr_as_pointer (struct gc_stack_addr addr) { 20 | GC_ASSERT(!gc_stack_addr_is_empty(addr)); 21 | return (char*)addr.addr; 22 | }; 23 | 24 | static inline struct gc_stack_addr gc_stack_addr (uintptr_t addr) { 25 | GC_ASSERT(addr); 26 | return (struct gc_stack_addr){ addr }; 27 | }; 28 | 29 | GC_API_ void* gc_call_with_stack_addr(void* (*f)(struct gc_stack_addr, 30 | void *), 31 | void *data) GC_NEVER_INLINE; 32 | 33 | GC_API_ int gc_stack_addr_is_colder(struct gc_stack_addr a, 34 | struct gc_stack_addr b); 35 | 36 | #endif // GC_STACK_ADDR_H 37 | -------------------------------------------------------------------------------- /whippet/api/gc-tracepoint.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_TRACEPOINT_H 2 | #define GC_TRACEPOINT_H 3 | 4 | #ifdef GC_TRACEPOINT_LTTNG 5 | 6 | #include "gc-lttng.h" 7 | 8 | #define GC_TRACEPOINT(...) \ 9 | lttng_ust_tracepoint(whippet, __VA_ARGS__) 10 | 11 | #else // GC_TRACEPOINT_LTTNG 12 | 13 | #define GC_TRACEPOINT(...) do {} while (0) 14 | 15 | #endif // GC_TRACEPOINT_LTTNG 16 | 17 | #endif // GC_TRACEPOINT_H 18 | -------------------------------------------------------------------------------- /whippet/api/gc-visibility.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_VISIBILITY_H_ 2 | #define GC_VISIBILITY_H_ 3 | 4 | #define GC_INTERNAL __attribute__((visibility("hidden"))) 5 | #define GC_PUBLIC __attribute__((visibility("default"))) 6 | 7 | // FIXME: Conflict with bdw-gc GC_API. Switch prefix? 8 | #ifndef GC_API_ 9 | #define GC_API_ GC_INTERNAL 10 | #endif 11 | 12 | #endif // GC_VISIBILITY_H 13 | -------------------------------------------------------------------------------- /whippet/api/mmc-attrs.h: -------------------------------------------------------------------------------- 1 | #ifndef MMC_ATTRS_H 2 | #define MMC_ATTRS_H 3 | 4 | #include "gc-config.h" 5 | #include "gc-assert.h" 6 | #include "gc-attrs.h" 7 | 8 | static inline enum gc_allocator_kind gc_allocator_kind(void) { 9 | return GC_ALLOCATOR_INLINE_BUMP_POINTER; 10 | } 11 | static inline size_t gc_allocator_small_granule_size(void) { 12 | return 16; 13 | } 14 | static inline size_t gc_allocator_large_threshold(void) { 15 | return 8192; 16 | } 17 | 18 | static inline size_t gc_allocator_allocation_pointer_offset(void) { 19 | return sizeof(uintptr_t) * 0; 20 | } 21 | static inline size_t gc_allocator_allocation_limit_offset(void) { 22 | return sizeof(uintptr_t) * 1; 23 | } 24 | 25 | static inline size_t gc_allocator_freelist_offset(size_t size, 26 | enum gc_allocation_kind kind) { 27 | GC_CRASH(); 28 | } 29 | 30 | static inline size_t gc_allocator_alloc_table_alignment(void) { 31 | return 4 * 1024 * 1024; 32 | } 33 | static inline uint8_t gc_allocator_alloc_table_begin_pattern(enum gc_allocation_kind kind) { 34 | uint8_t young = 1; 35 | uint8_t trace_precisely = 0; 36 | uint8_t trace_none = 8; 37 | uint8_t trace_conservatively = 16; 38 | uint8_t pinned = 16; 39 | if (GC_CONSERVATIVE_TRACE) { 40 | switch (kind) { 41 | case GC_ALLOCATION_TAGGED: 42 | case GC_ALLOCATION_UNTAGGED_CONSERVATIVE: 43 | return young | trace_conservatively; 44 | case GC_ALLOCATION_TAGGED_POINTERLESS: 45 | return young | trace_none; 46 | case GC_ALLOCATION_UNTAGGED_POINTERLESS: 47 | return young | trace_none; 48 | default: 49 | GC_CRASH(); 50 | }; 51 | } else { 52 | switch (kind) { 53 | case GC_ALLOCATION_TAGGED: 54 | return young | trace_precisely; 55 | case GC_ALLOCATION_TAGGED_POINTERLESS: 56 | return young | trace_none; 57 | case GC_ALLOCATION_UNTAGGED_POINTERLESS: 58 | return young | trace_none | pinned; 59 | case GC_ALLOCATION_UNTAGGED_CONSERVATIVE: 60 | default: 61 | GC_CRASH(); 62 | }; 63 | } 64 | } 65 | static inline uint8_t gc_allocator_alloc_table_end_pattern(void) { 66 | return 32; 67 | } 68 | 69 | static inline enum gc_old_generation_check_kind gc_old_generation_check_kind(size_t obj_size) { 70 | if (GC_GENERATIONAL) { 71 | if (obj_size <= gc_allocator_large_threshold()) 72 | return GC_OLD_GENERATION_CHECK_ALLOC_TABLE; 73 | return GC_OLD_GENERATION_CHECK_SLOW; 74 | } 75 | return GC_OLD_GENERATION_CHECK_NONE; 76 | } 77 | static inline uint8_t gc_old_generation_check_alloc_table_tag_mask(void) { 78 | return 7; 79 | } 80 | static inline uint8_t gc_old_generation_check_alloc_table_young_tag(void) { 81 | return 1; 82 | } 83 | 84 | static inline enum gc_write_barrier_kind gc_write_barrier_kind(size_t obj_size) { 85 | if (GC_GENERATIONAL) { 86 | if (obj_size <= gc_allocator_large_threshold()) 87 | return GC_WRITE_BARRIER_FIELD; 88 | return GC_WRITE_BARRIER_SLOW; 89 | } 90 | return GC_WRITE_BARRIER_NONE; 91 | } 92 | static inline size_t gc_write_barrier_field_table_alignment(void) { 93 | GC_ASSERT(GC_GENERATIONAL); 94 | return gc_allocator_alloc_table_alignment(); 95 | } 96 | static inline ptrdiff_t gc_write_barrier_field_table_offset(void) { 97 | GC_ASSERT(GC_GENERATIONAL); 98 | return 0; 99 | } 100 | static inline size_t gc_write_barrier_field_fields_per_byte(void) { 101 | GC_ASSERT(GC_GENERATIONAL); 102 | return 2; 103 | } 104 | static inline uint8_t gc_write_barrier_field_first_bit_pattern(void) { 105 | GC_ASSERT(GC_GENERATIONAL); 106 | return 64; // NOFL_METADATA_BYTE_LOGGED_0 107 | } 108 | 109 | static inline enum gc_safepoint_mechanism gc_safepoint_mechanism(void) { 110 | return GC_SAFEPOINT_MECHANISM_COOPERATIVE; 111 | } 112 | 113 | static inline enum gc_cooperative_safepoint_kind gc_cooperative_safepoint_kind(void) { 114 | return GC_COOPERATIVE_SAFEPOINT_HEAP_FLAG; 115 | } 116 | 117 | static inline int gc_can_pin_objects(void) { 118 | return 1; 119 | } 120 | 121 | #endif // MMC_ATTRS_H 122 | -------------------------------------------------------------------------------- /whippet/api/pcc-attrs.h: -------------------------------------------------------------------------------- 1 | #ifndef PCC_ATTRS_H 2 | #define PCC_ATTRS_H 3 | 4 | #include "gc-config.h" 5 | #include "gc-assert.h" 6 | #include "gc-attrs.h" 7 | 8 | static const uintptr_t GC_ALIGNMENT = 8; 9 | static const size_t GC_LARGE_OBJECT_THRESHOLD = 8192; 10 | 11 | static inline enum gc_allocator_kind gc_allocator_kind(void) { 12 | return GC_ALLOCATOR_INLINE_BUMP_POINTER; 13 | } 14 | static inline size_t gc_allocator_small_granule_size(void) { 15 | return GC_ALIGNMENT; 16 | } 17 | static inline size_t gc_allocator_large_threshold(void) { 18 | return GC_LARGE_OBJECT_THRESHOLD; 19 | } 20 | 21 | static inline size_t gc_allocator_allocation_pointer_offset(void) { 22 | return sizeof(uintptr_t) * 0; 23 | } 24 | static inline size_t gc_allocator_allocation_limit_offset(void) { 25 | return sizeof(uintptr_t) * 1; 26 | } 27 | 28 | static inline size_t gc_allocator_freelist_offset(size_t size, enum gc_allocation_kind kind) { 29 | GC_CRASH(); 30 | } 31 | 32 | static inline size_t gc_allocator_alloc_table_alignment(void) { 33 | return 0; 34 | } 35 | static inline uint8_t gc_allocator_alloc_table_begin_pattern(enum gc_allocation_kind kind) { 36 | GC_CRASH(); 37 | } 38 | static inline uint8_t gc_allocator_alloc_table_end_pattern(void) { 39 | GC_CRASH(); 40 | } 41 | 42 | static inline enum gc_old_generation_check_kind gc_old_generation_check_kind(size_t size) { 43 | if (!GC_GENERATIONAL) 44 | return GC_OLD_GENERATION_CHECK_NONE; 45 | if (size <= gc_allocator_large_threshold()) 46 | return GC_OLD_GENERATION_CHECK_SMALL_OBJECT_NURSERY; 47 | return GC_OLD_GENERATION_CHECK_SLOW; 48 | } 49 | static inline uint8_t gc_old_generation_check_alloc_table_tag_mask(void) { 50 | GC_CRASH(); 51 | } 52 | static inline uint8_t gc_old_generation_check_alloc_table_young_tag(void) { 53 | GC_CRASH(); 54 | } 55 | 56 | static inline enum gc_write_barrier_kind gc_write_barrier_kind(size_t obj_size) { 57 | if (!GC_GENERATIONAL) 58 | return GC_WRITE_BARRIER_NONE; 59 | if (obj_size <= gc_allocator_large_threshold()) 60 | return GC_WRITE_BARRIER_FIELD; 61 | return GC_WRITE_BARRIER_SLOW; 62 | } 63 | static inline size_t gc_write_barrier_field_table_alignment(void) { 64 | GC_ASSERT(GC_GENERATIONAL); 65 | return 64 * 1024 * 1024; 66 | } 67 | static inline ptrdiff_t gc_write_barrier_field_table_offset(void) { 68 | GC_ASSERT(GC_GENERATIONAL); 69 | return 128 * 1024; 70 | } 71 | static inline size_t gc_write_barrier_field_fields_per_byte(void) { 72 | GC_ASSERT(GC_GENERATIONAL); 73 | return 8; 74 | } 75 | static inline uint8_t gc_write_barrier_field_first_bit_pattern(void) { 76 | GC_ASSERT(GC_GENERATIONAL); 77 | return 1; 78 | } 79 | 80 | static inline enum gc_safepoint_mechanism gc_safepoint_mechanism(void) { 81 | return GC_SAFEPOINT_MECHANISM_COOPERATIVE; 82 | } 83 | 84 | static inline enum gc_cooperative_safepoint_kind gc_cooperative_safepoint_kind(void) { 85 | return GC_COOPERATIVE_SAFEPOINT_HEAP_FLAG; 86 | } 87 | 88 | static inline int gc_can_pin_objects(void) { 89 | return 0; 90 | } 91 | 92 | #endif // PCC_ATTRS_H 93 | -------------------------------------------------------------------------------- /whippet/api/semi-attrs.h: -------------------------------------------------------------------------------- 1 | #ifndef SEMI_ATTRS_H 2 | #define SEMI_ATTRS_H 3 | 4 | #include "gc-attrs.h" 5 | #include "gc-assert.h" 6 | 7 | static const uintptr_t GC_ALIGNMENT = 8; 8 | static const size_t GC_LARGE_OBJECT_THRESHOLD = 8192; 9 | 10 | static inline enum gc_allocator_kind gc_allocator_kind(void) { 11 | return GC_ALLOCATOR_INLINE_BUMP_POINTER; 12 | } 13 | static inline size_t gc_allocator_small_granule_size(void) { 14 | return GC_ALIGNMENT; 15 | } 16 | static inline size_t gc_allocator_large_threshold(void) { 17 | return GC_LARGE_OBJECT_THRESHOLD; 18 | } 19 | 20 | static inline size_t gc_allocator_allocation_pointer_offset(void) { 21 | return sizeof(uintptr_t) * 0; 22 | } 23 | static inline size_t gc_allocator_allocation_limit_offset(void) { 24 | return sizeof(uintptr_t) * 1; 25 | } 26 | 27 | static inline size_t gc_allocator_freelist_offset(size_t size, 28 | enum gc_allocation_kind kind) { 29 | GC_CRASH(); 30 | } 31 | 32 | static inline size_t gc_allocator_alloc_table_alignment(void) { 33 | return 0; 34 | } 35 | static inline uint8_t gc_allocator_alloc_table_begin_pattern(enum gc_allocation_kind kind) { 36 | GC_CRASH(); 37 | } 38 | static inline uint8_t gc_allocator_alloc_table_end_pattern(void) { 39 | GC_CRASH(); 40 | } 41 | 42 | static inline enum gc_old_generation_check_kind gc_old_generation_check_kind(size_t) { 43 | return GC_OLD_GENERATION_CHECK_NONE; 44 | } 45 | static inline uint8_t gc_old_generation_check_alloc_table_tag_mask(void) { 46 | GC_CRASH(); 47 | } 48 | static inline uint8_t gc_old_generation_check_alloc_table_young_tag(void) { 49 | GC_CRASH(); 50 | } 51 | 52 | static inline enum gc_write_barrier_kind gc_write_barrier_kind(size_t) { 53 | return GC_WRITE_BARRIER_NONE; 54 | } 55 | static inline size_t gc_write_barrier_field_table_alignment(void) { 56 | GC_CRASH(); 57 | } 58 | static inline ptrdiff_t gc_write_barrier_field_table_offset(void) { 59 | GC_CRASH(); 60 | } 61 | static inline size_t gc_write_barrier_field_fields_per_byte(void) { 62 | GC_CRASH(); 63 | } 64 | static inline uint8_t gc_write_barrier_field_first_bit_pattern(void) { 65 | GC_CRASH(); 66 | } 67 | 68 | static inline enum gc_safepoint_mechanism gc_safepoint_mechanism(void) { 69 | return GC_SAFEPOINT_MECHANISM_COOPERATIVE; 70 | } 71 | 72 | static inline enum gc_cooperative_safepoint_kind gc_cooperative_safepoint_kind(void) { 73 | return GC_COOPERATIVE_SAFEPOINT_NONE; 74 | } 75 | 76 | static inline int gc_can_pin_objects(void) { 77 | return 0; 78 | } 79 | 80 | #endif // SEMI_ATTRS_H 81 | -------------------------------------------------------------------------------- /whippet/benchmarks/README.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | - [`mt-gcbench.c`](./mt-gcbench.c): The multi-threaded [GCBench 4 | benchmark](https://hboehm.info/gc/gc_bench.html). An old but 5 | standard benchmark that allocates different sizes of binary trees. 6 | As parameters it takes a heap multiplier and a number of mutator 7 | threads. We analytically compute the peak amount of live data and 8 | then size the GC heap as a multiplier of that size. It has a peak 9 | heap consumption of 10 MB or so per mutator thread: not very large. 10 | At a 2x heap multiplier, it causes about 30 collections for the `mmc` 11 | collector, and runs somewhere around 200-400 milliseconds in 12 | single-threaded mode, on the machines I have in 2022. For low thread 13 | counts, the GCBench benchmark is small; but then again many Guile 14 | processes also are quite short-lived, so perhaps it is useful to 15 | ensure that small heaps remain lightweight. 16 | 17 | To stress `mmc`'s handling of fragmentation, we modified this 18 | benchmark to intersperse pseudorandomly-sized holes between tree 19 | nodes. 20 | 21 | - [`quads.c`](./quads.c): A synthetic benchmark that allocates quad 22 | trees. The mutator begins by allocating one long-lived tree of depth 23 | N, and then allocates 13% of the heap in depth-3 trees, 20 times, 24 | simulating a fixed working set and otherwise an allocation-heavy 25 | workload. By observing the times to allocate 13% of the heap in 26 | garbage we can infer mutator overheads, and also note the variance 27 | for the cycles in which GC hits. 28 | 29 | ## License 30 | 31 | mt-gcbench.c was originally from https://hboehm.info/gc/gc_bench/, which 32 | has a somewhat unclear license. I have modified GCBench significantly 33 | so that I can slot in different GC implementations. Other files are 34 | distributed under the Whippet license; see the top-level 35 | [README.md](../README.md) for more. 36 | -------------------------------------------------------------------------------- /whippet/benchmarks/ephemerons-embedder.h: -------------------------------------------------------------------------------- 1 | #ifndef EPHEMERONS_EMBEDDER_H 2 | #define EPHEMERONS_EMBEDDER_H 3 | 4 | #include 5 | 6 | #include "ephemerons-types.h" 7 | #include "gc-ephemeron.h" 8 | 9 | struct gc_heap; 10 | 11 | #define DEFINE_METHODS(name, Name, NAME) \ 12 | static inline size_t name##_size(Name *obj) GC_ALWAYS_INLINE; \ 13 | static inline void visit_##name##_fields(Name *obj,\ 14 | void (*visit)(struct gc_edge edge, \ 15 | struct gc_heap *heap, \ 16 | void *visit_data), \ 17 | struct gc_heap *heap, \ 18 | void *visit_data) GC_ALWAYS_INLINE; 19 | FOR_EACH_HEAP_OBJECT_KIND(DEFINE_METHODS) 20 | #undef DEFINE_METHODS 21 | 22 | static inline size_t small_object_size(SmallObject *obj) { return sizeof(*obj); } 23 | static inline size_t ephemeron_size(Ephemeron *obj) { return gc_ephemeron_size(); } 24 | static inline size_t box_size(Box *obj) { return sizeof(*obj); } 25 | 26 | static inline void 27 | visit_small_object_fields(SmallObject *obj, 28 | void (*visit)(struct gc_edge edge, struct gc_heap *heap, 29 | void *visit_data), 30 | struct gc_heap *heap, 31 | void *visit_data) {} 32 | 33 | static inline void 34 | visit_ephemeron_fields(Ephemeron *ephemeron, 35 | void (*visit)(struct gc_edge edge, struct gc_heap *heap, 36 | void *visit_data), 37 | 38 | struct gc_heap *heap, 39 | void *visit_data) { 40 | gc_trace_ephemeron((struct gc_ephemeron*)ephemeron, visit, heap, visit_data); 41 | } 42 | 43 | static inline void 44 | visit_box_fields(Box *box, 45 | void (*visit)(struct gc_edge edge, struct gc_heap *heap, 46 | void *visit_data), 47 | struct gc_heap *heap, 48 | void *visit_data) { 49 | visit(gc_edge(&box->obj), heap, visit_data); 50 | } 51 | 52 | #include "simple-gc-embedder.h" 53 | 54 | #endif // EPHEMERONS_EMBEDDER_H 55 | -------------------------------------------------------------------------------- /whippet/benchmarks/ephemerons-types.h: -------------------------------------------------------------------------------- 1 | #ifndef EPHEMERONS_TYPES_H 2 | #define EPHEMERONS_TYPES_H 3 | 4 | #define FOR_EACH_HEAP_OBJECT_KIND(M) \ 5 | M(box, Box, BOX) \ 6 | M(ephemeron, Ephemeron, EPHEMERON) \ 7 | M(small_object, SmallObject, SMALL_OBJECT) 8 | 9 | #include "heap-objects.h" 10 | #include "simple-tagging-scheme.h" 11 | 12 | struct SmallObject { 13 | struct gc_header header; 14 | }; 15 | 16 | struct Box { 17 | struct gc_header header; 18 | void *obj; 19 | }; 20 | 21 | #endif // EPHEMERONS_TYPES_H 22 | -------------------------------------------------------------------------------- /whippet/benchmarks/finalizers-embedder.h: -------------------------------------------------------------------------------- 1 | #ifndef FINALIZERS_EMBEDDER_H 2 | #define FINALIZERS_EMBEDDER_H 3 | 4 | #include 5 | 6 | #include "finalizers-types.h" 7 | #include "gc-finalizer.h" 8 | 9 | struct gc_heap; 10 | 11 | #define DEFINE_METHODS(name, Name, NAME) \ 12 | static inline size_t name##_size(Name *obj) GC_ALWAYS_INLINE; \ 13 | static inline void visit_##name##_fields(Name *obj,\ 14 | void (*visit)(struct gc_edge edge, \ 15 | struct gc_heap *heap, \ 16 | void *visit_data), \ 17 | struct gc_heap *heap, \ 18 | void *visit_data) GC_ALWAYS_INLINE; 19 | FOR_EACH_HEAP_OBJECT_KIND(DEFINE_METHODS) 20 | #undef DEFINE_METHODS 21 | 22 | static inline size_t small_object_size(SmallObject *obj) { return sizeof(*obj); } 23 | static inline size_t finalizer_size(Finalizer *obj) { return gc_finalizer_size(); } 24 | static inline size_t pair_size(Pair *obj) { return sizeof(*obj); } 25 | 26 | static inline void 27 | visit_small_object_fields(SmallObject *obj, 28 | void (*visit)(struct gc_edge edge, struct gc_heap *heap, 29 | void *visit_data), 30 | struct gc_heap *heap, 31 | void *visit_data) {} 32 | 33 | static inline void 34 | visit_finalizer_fields(Finalizer *finalizer, 35 | void (*visit)(struct gc_edge edge, struct gc_heap *heap, 36 | void *visit_data), 37 | 38 | struct gc_heap *heap, 39 | void *visit_data) { 40 | gc_trace_finalizer((struct gc_finalizer*)finalizer, visit, heap, visit_data); 41 | } 42 | 43 | static inline void 44 | visit_pair_fields(Pair *pair, 45 | void (*visit)(struct gc_edge edge, struct gc_heap *heap, 46 | void *visit_data), 47 | struct gc_heap *heap, 48 | void *visit_data) { 49 | visit(gc_edge(&pair->car), heap, visit_data); 50 | visit(gc_edge(&pair->cdr), heap, visit_data); 51 | } 52 | 53 | #include "simple-gc-embedder.h" 54 | 55 | #endif // FINALIZERS_EMBEDDER_H 56 | -------------------------------------------------------------------------------- /whippet/benchmarks/finalizers-types.h: -------------------------------------------------------------------------------- 1 | #ifndef FINALIZERS_TYPES_H 2 | #define FINALIZERS_TYPES_H 3 | 4 | #define FOR_EACH_HEAP_OBJECT_KIND(M) \ 5 | M(pair, Pair, PAIR) \ 6 | M(finalizer, Finalizer, FINALIZER) \ 7 | M(small_object, SmallObject, SMALL_OBJECT) 8 | 9 | #include "heap-objects.h" 10 | #include "simple-tagging-scheme.h" 11 | 12 | struct SmallObject { 13 | struct gc_header header; 14 | }; 15 | 16 | struct Pair { 17 | struct gc_header header; 18 | void *car; 19 | void *cdr; 20 | }; 21 | 22 | #endif // FINALIZERS_TYPES_H 23 | -------------------------------------------------------------------------------- /whippet/benchmarks/heap-objects.h: -------------------------------------------------------------------------------- 1 | #ifndef HEAP_OBJECTS_H 2 | #define HEAP_OBJECTS_H 3 | 4 | #include "gc-inline.h" 5 | #include "gc-edge.h" 6 | 7 | #define DECLARE_NODE_TYPE(name, Name, NAME) \ 8 | struct Name; \ 9 | typedef struct Name Name; 10 | FOR_EACH_HEAP_OBJECT_KIND(DECLARE_NODE_TYPE) 11 | #undef DECLARE_NODE_TYPE 12 | 13 | #define DEFINE_ENUM(name, Name, NAME) ALLOC_KIND_##NAME, 14 | enum alloc_kind { 15 | FOR_EACH_HEAP_OBJECT_KIND(DEFINE_ENUM) 16 | }; 17 | #undef DEFINE_ENUM 18 | 19 | #endif // HEAP_OBJECTS_H 20 | -------------------------------------------------------------------------------- /whippet/benchmarks/mt-gcbench-embedder.h: -------------------------------------------------------------------------------- 1 | #ifndef MT_GCBENCH_EMBEDDER_H 2 | #define MT_GCBENCH_EMBEDDER_H 3 | 4 | #include "gc-config.h" 5 | #include "mt-gcbench-types.h" 6 | 7 | struct gc_heap; 8 | 9 | #define DEFINE_METHODS(name, Name, NAME) \ 10 | static inline size_t name##_size(Name *obj) GC_ALWAYS_INLINE; \ 11 | static inline void visit_##name##_fields(Name *obj,\ 12 | void (*visit)(struct gc_edge edge, \ 13 | struct gc_heap *heap, \ 14 | void *visit_data), \ 15 | struct gc_heap *heap, \ 16 | void *visit_data) GC_ALWAYS_INLINE; 17 | FOR_EACH_HEAP_OBJECT_KIND(DEFINE_METHODS) 18 | #undef DEFINE_METHODS 19 | 20 | static inline size_t node_size(Node *obj) { 21 | return sizeof(Node); 22 | } 23 | static inline size_t double_array_size(DoubleArray *array) { 24 | return sizeof(*array) + array->length * sizeof(double); 25 | } 26 | static inline size_t hole_size(Hole *hole) { 27 | return sizeof(*hole) + hole->length * sizeof(uintptr_t); 28 | } 29 | static inline void 30 | visit_node_fields(Node *node, 31 | void (*visit)(struct gc_edge edge, struct gc_heap *heap, 32 | void *visit_data), 33 | struct gc_heap *heap, void *visit_data) { 34 | visit(gc_edge(&node->left), heap, visit_data); 35 | visit(gc_edge(&node->right), heap, visit_data); 36 | } 37 | static inline void 38 | visit_double_array_fields(DoubleArray *obj, 39 | void (*visit)(struct gc_edge edge, 40 | struct gc_heap *heap, void *visit_data), 41 | struct gc_heap *heap, void *visit_data) { 42 | } 43 | static inline void 44 | visit_hole_fields(Hole *obj, 45 | void (*visit)(struct gc_edge edge, 46 | struct gc_heap *heap, void *visit_data), 47 | struct gc_heap *heap, void *visit_data) { 48 | if (GC_PRECISE_ROOTS) 49 | GC_CRASH(); 50 | } 51 | 52 | #include "simple-gc-embedder.h" 53 | 54 | #endif // MT_GCBENCH_EMBEDDER_H 55 | -------------------------------------------------------------------------------- /whippet/benchmarks/mt-gcbench-types.h: -------------------------------------------------------------------------------- 1 | #ifndef GCBENCH_TYPES_H 2 | #define GCBENCH_TYPES_H 3 | 4 | #include 5 | #include 6 | 7 | #define FOR_EACH_HEAP_OBJECT_KIND(M) \ 8 | M(node, Node, NODE) \ 9 | M(double_array, DoubleArray, DOUBLE_ARRAY) \ 10 | M(hole, Hole, HOLE) 11 | 12 | #include "heap-objects.h" 13 | #include "simple-tagging-scheme.h" 14 | 15 | struct Node { 16 | struct gc_header header; 17 | struct Node *left; 18 | struct Node *right; 19 | int i, j; 20 | }; 21 | 22 | struct DoubleArray { 23 | struct gc_header header; 24 | size_t length; 25 | double values[0]; 26 | }; 27 | 28 | struct Hole { 29 | struct gc_header header; 30 | size_t length; 31 | uintptr_t values[0]; 32 | }; 33 | 34 | #endif // GCBENCH_TYPES_H 35 | -------------------------------------------------------------------------------- /whippet/benchmarks/quads-embedder.h: -------------------------------------------------------------------------------- 1 | #ifndef QUADS_EMBEDDER_H 2 | #define QUADS_EMBEDDER_H 3 | 4 | #include 5 | 6 | #include "quads-types.h" 7 | 8 | struct gc_heap; 9 | 10 | #define DEFINE_METHODS(name, Name, NAME) \ 11 | static inline size_t name##_size(Name *obj) GC_ALWAYS_INLINE; \ 12 | static inline void visit_##name##_fields(Name *obj,\ 13 | void (*visit)(struct gc_edge edge, \ 14 | struct gc_heap *heap, \ 15 | void *visit_data), \ 16 | struct gc_heap *heap, \ 17 | void *visit_data) GC_ALWAYS_INLINE; 18 | FOR_EACH_HEAP_OBJECT_KIND(DEFINE_METHODS) 19 | #undef DEFINE_METHODS 20 | 21 | static inline size_t quad_size(Quad *obj) { 22 | return sizeof(Quad); 23 | } 24 | 25 | static inline void 26 | visit_quad_fields(Quad *quad, 27 | void (*visit)(struct gc_edge edge, struct gc_heap *heap, 28 | void *visit_data), 29 | struct gc_heap *heap, 30 | void *visit_data) { 31 | for (size_t i = 0; i < 4; i++) 32 | visit(gc_edge(&quad->kids[i]), heap, visit_data); 33 | } 34 | 35 | #include "simple-gc-embedder.h" 36 | 37 | #endif // QUADS_EMBEDDER_H 38 | -------------------------------------------------------------------------------- /whippet/benchmarks/quads-types.h: -------------------------------------------------------------------------------- 1 | #ifndef QUADS_TYPES_H 2 | #define QUADS_TYPES_H 3 | 4 | #define FOR_EACH_HEAP_OBJECT_KIND(M) \ 5 | M(quad, Quad, QUAD) 6 | 7 | #include "heap-objects.h" 8 | #include "simple-tagging-scheme.h" 9 | 10 | struct Quad { 11 | struct gc_header header; 12 | struct Quad *kids[4]; 13 | }; 14 | 15 | #endif // QUADS_TYPES_H 16 | -------------------------------------------------------------------------------- /whippet/benchmarks/quads.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "assert.h" 7 | #include "gc-api.h" 8 | #include "gc-basic-stats.h" 9 | #include "simple-roots-api.h" 10 | #include "quads-types.h" 11 | #include "simple-allocator.h" 12 | 13 | typedef HANDLE_TO(Quad) QuadHandle; 14 | 15 | static Quad* allocate_quad(struct gc_mutator *mut) { 16 | // memset to 0 by the collector. 17 | return gc_allocate_with_kind(mut, ALLOC_KIND_QUAD, sizeof (Quad)); 18 | } 19 | 20 | /* Get the current time in microseconds */ 21 | static unsigned long current_time(void) 22 | { 23 | struct timeval t; 24 | if (gettimeofday(&t, NULL) == -1) 25 | return 0; 26 | return t.tv_sec * 1000 * 1000 + t.tv_usec; 27 | } 28 | 29 | struct thread { 30 | struct gc_mutator *mut; 31 | struct gc_mutator_roots roots; 32 | size_t counter; 33 | }; 34 | 35 | // Build tree bottom-up 36 | static Quad* make_tree(struct thread *t, int depth) { 37 | if (depth<=0) { 38 | return allocate_quad(t->mut); 39 | } else { 40 | QuadHandle kids[4] = { { NULL }, }; 41 | for (size_t i = 0; i < 4; i++) { 42 | HANDLE_SET(kids[i], make_tree(t, depth-1)); 43 | PUSH_HANDLE(t, kids[i]); 44 | } 45 | 46 | Quad *result = allocate_quad(t->mut); 47 | for (size_t i = 0; i < 4; i++) 48 | result->kids[i] = HANDLE_REF(kids[i]); 49 | 50 | for (size_t i = 0; i < 4; i++) 51 | POP_HANDLE(t); 52 | 53 | return result; 54 | } 55 | } 56 | 57 | static void validate_tree(Quad *tree, int depth) { 58 | for (size_t i = 0; i < 4; i++) { 59 | if (depth == 0) { 60 | if (tree->kids[i]) 61 | abort(); 62 | } else { 63 | if (!tree->kids[i]) 64 | abort(); 65 | validate_tree(tree->kids[i], depth - 1); 66 | } 67 | } 68 | } 69 | 70 | static void print_elapsed(const char *what, unsigned long start) { 71 | unsigned long end = current_time(); 72 | unsigned long msec = (end - start) / 1000; 73 | unsigned long usec = (end - start) % 1000; 74 | printf("Completed %s in %lu.%.3lu msec\n", what, msec, usec); 75 | } 76 | 77 | static size_t parse_size(char *arg, const char *what) { 78 | long val = atol(arg); 79 | if (val <= 0) { 80 | fprintf(stderr, "Failed to parse %s '%s'\n", what, arg); 81 | exit(1); 82 | } 83 | return val; 84 | } 85 | 86 | static size_t tree_size(size_t depth) { 87 | size_t nquads = 0; 88 | size_t leaf_count = 1; 89 | for (size_t i = 0; i <= depth; i++) { 90 | if (nquads > ((size_t)-1) - leaf_count) { 91 | fprintf(stderr, 92 | "error: address space too small for quad tree of depth %zu\n", 93 | depth); 94 | exit(1); 95 | } 96 | nquads += leaf_count; 97 | leaf_count *= 4; 98 | } 99 | return nquads; 100 | } 101 | 102 | #define MAX_THREAD_COUNT 256 103 | 104 | int main(int argc, char *argv[]) { 105 | if (argc < 3 || 4 < argc) { 106 | fprintf(stderr, "usage: %s DEPTH MULTIPLIER [GC-OPTIONS]\n", argv[0]); 107 | return 1; 108 | } 109 | 110 | size_t depth = parse_size(argv[1], "depth"); 111 | double multiplier = atof(argv[2]); 112 | 113 | if (!(1.0 < multiplier && multiplier < 100)) { 114 | fprintf(stderr, "Failed to parse heap multiplier '%s'\n", argv[2]); 115 | return 1; 116 | } 117 | 118 | size_t nquads = tree_size(depth); 119 | size_t tree_bytes = nquads * sizeof(Quad); 120 | size_t heap_size = tree_bytes * multiplier; 121 | 122 | printf("Allocating heap of %.3fGB (%.2f multiplier of live data).\n", 123 | heap_size / 1e9, multiplier); 124 | 125 | struct gc_options *options = gc_allocate_options(); 126 | gc_options_set_int(options, GC_OPTION_HEAP_SIZE_POLICY, GC_HEAP_SIZE_FIXED); 127 | gc_options_set_size(options, GC_OPTION_HEAP_SIZE, heap_size); 128 | if (argc == 4) { 129 | if (!gc_options_parse_and_set_many(options, argv[3])) { 130 | fprintf(stderr, "Failed to set GC options: '%s'\n", argv[3]); 131 | return 1; 132 | } 133 | } 134 | 135 | struct gc_heap *heap; 136 | struct gc_mutator *mut; 137 | struct gc_basic_stats stats; 138 | if (!gc_init(options, gc_empty_stack_addr(), &heap, &mut, 139 | GC_BASIC_STATS, &stats)) { 140 | fprintf(stderr, "Failed to initialize GC with heap size %zu bytes\n", 141 | heap_size); 142 | return 1; 143 | } 144 | struct thread t = { mut, }; 145 | gc_mutator_set_roots(mut, &t.roots); 146 | 147 | QuadHandle quad = { NULL }; 148 | 149 | PUSH_HANDLE(&t, quad); 150 | 151 | printf("Making quad tree of depth %zu (%zu nodes). Total size %.3fGB.\n", 152 | depth, nquads, (nquads * sizeof(Quad)) / 1e9); 153 | unsigned long start = current_time(); 154 | HANDLE_SET(quad, make_tree(&t, depth)); 155 | print_elapsed("construction", start); 156 | 157 | validate_tree(HANDLE_REF(quad), depth); 158 | 159 | size_t garbage_step = heap_size / 7.5; 160 | printf("Allocating %.3f GB of garbage, 20 times, validating live tree each time.\n", 161 | garbage_step / 1e9); 162 | unsigned long garbage_start = current_time(); 163 | for (size_t i = 0; i < 20; i++) { 164 | size_t garbage_depth = 3; 165 | start = current_time(); 166 | for (size_t i = garbage_step/(tree_size(garbage_depth)*4*sizeof(Quad*)); i; i--) 167 | make_tree(&t, garbage_depth); 168 | print_elapsed("allocating garbage", start); 169 | 170 | start = current_time(); 171 | validate_tree(HANDLE_REF(quad), depth); 172 | } 173 | print_elapsed("allocation loop", garbage_start); 174 | 175 | gc_basic_stats_finish(&stats); 176 | fputs("\n", stdout); 177 | gc_basic_stats_print(&stats, stdout); 178 | 179 | POP_HANDLE(&t); 180 | return 0; 181 | } 182 | 183 | -------------------------------------------------------------------------------- /whippet/benchmarks/simple-allocator.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLE_ALLOCATOR_H 2 | #define SIMPLE_ALLOCATOR_H 3 | 4 | #include "simple-tagging-scheme.h" 5 | #include "gc-api.h" 6 | 7 | static inline void* 8 | gc_allocate_with_kind(struct gc_mutator *mut, enum alloc_kind kind, size_t bytes) { 9 | void *obj = gc_allocate(mut, bytes, GC_ALLOCATION_TAGGED); 10 | *tag_word(gc_ref_from_heap_object(obj)) = tag_live(kind); 11 | return obj; 12 | } 13 | 14 | static inline void* 15 | gc_allocate_pointerless_with_kind(struct gc_mutator *mut, enum alloc_kind kind, size_t bytes) { 16 | void *obj = gc_allocate(mut, bytes, GC_ALLOCATION_TAGGED_POINTERLESS); 17 | *tag_word(gc_ref_from_heap_object(obj)) = tag_live(kind); 18 | return obj; 19 | } 20 | 21 | #endif // SIMPLE_ALLOCATOR_H 22 | -------------------------------------------------------------------------------- /whippet/benchmarks/simple-roots-api.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLE_ROOTS_API_H 2 | #define SIMPLE_ROOTS_API_H 3 | 4 | #include "gc-config.h" 5 | #include "simple-roots-types.h" 6 | 7 | #define HANDLE_TO(T) union { T* v; struct handle handle; } 8 | #define HANDLE_LOC(h) &(h).v 9 | #define HANDLE_REF(h) (h).v 10 | #define HANDLE_SET(h,val) do { (h).v = val; } while (0) 11 | #define PUSH_HANDLE(cx, h) push_handle(&(cx)->roots.roots, &h.handle) 12 | #define POP_HANDLE(cx) pop_handle(&(cx)->roots.roots) 13 | 14 | static inline void push_handle(struct handle **roots, struct handle *handle) { 15 | if (GC_PRECISE_ROOTS) { 16 | handle->next = *roots; 17 | *roots = handle; 18 | } 19 | } 20 | 21 | static inline void pop_handle(struct handle **roots) { 22 | if (GC_PRECISE_ROOTS) 23 | *roots = (*roots)->next; 24 | } 25 | 26 | #endif // SIMPLE_ROOTS_API_H 27 | -------------------------------------------------------------------------------- /whippet/benchmarks/simple-roots-types.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLE_ROOTS_TYPES_H 2 | #define SIMPLE_ROOTS_TYPES_H 3 | 4 | struct handle { 5 | void *v; 6 | struct handle *next; 7 | }; 8 | 9 | struct gc_heap_roots { 10 | struct handle *roots; 11 | }; 12 | 13 | struct gc_mutator_roots { 14 | struct handle *roots; 15 | }; 16 | 17 | #endif // SIMPLE_ROOTS_TYPES_H 18 | -------------------------------------------------------------------------------- /whippet/benchmarks/simple-tagging-scheme.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLE_TAGGING_SCHEME_H 2 | #define SIMPLE_TAGGING_SCHEME_H 3 | 4 | #include 5 | 6 | struct gc_header { 7 | uintptr_t tag; 8 | }; 9 | 10 | // Alloc kind is in bits 1-7, for live objects. 11 | static const uintptr_t gcobj_alloc_kind_mask = 0x7f; 12 | static const uintptr_t gcobj_alloc_kind_shift = 1; 13 | static const uintptr_t gcobj_forwarded_mask = 0x1; 14 | static const uintptr_t gcobj_not_forwarded_bit = 0x1; 15 | static const uintptr_t gcobj_busy = 0; 16 | static inline uint8_t tag_live_alloc_kind(uintptr_t tag) { 17 | return (tag >> gcobj_alloc_kind_shift) & gcobj_alloc_kind_mask; 18 | } 19 | static inline uintptr_t tag_live(uint8_t alloc_kind) { 20 | return ((uintptr_t)alloc_kind << gcobj_alloc_kind_shift) 21 | | gcobj_not_forwarded_bit; 22 | } 23 | 24 | static inline uintptr_t* tag_word(struct gc_ref ref) { 25 | struct gc_header *header = gc_ref_heap_object(ref); 26 | return &header->tag; 27 | } 28 | 29 | #endif // SIMPLE_TAGGING_SCHEME_H 30 | -------------------------------------------------------------------------------- /whippet/ctf_to_json.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Any copyright is dedicated to the Public Domain. 3 | # https://creativecommons.org/publicdomain/zero/1.0/ 4 | # 5 | # Originally written by Andy Wingo . 6 | 7 | import bt2 # From the babeltrace2 package. 8 | import sys 9 | import json 10 | from enum import Enum 11 | 12 | # Usage: ./ctf_to_json.py ~/lttng-traces/name-of-your-trace > foo.json 13 | # 14 | # Convert a Common Trace Format (CTF) trace, for example as produced by 15 | # LTTng, to the JSON-based Trace Event Format (TEF), for example as 16 | # consumed by `chrome://tracing`, `https://ui.perfetto.dev/`, or 17 | # `https://profiler.firefox.com`. 18 | 19 | # The Trace Event Format is documented here: 20 | # 21 | # https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview?tab=t.0 22 | 23 | # By default, events are emitted as EventPhase.INSTANT. We also support 24 | # rewriting the event stream so as to generate EventPhase.BEGIN / 25 | # EventPhase.END events for specific named events. 26 | 27 | synthetic_events = { 28 | 'gc': ['whippet:mutator_cause_gc', 29 | 'whippet:restarting_mutators'], 30 | 'stop-the-world': ['whippet:requesting_stop', 31 | 'whippet:mutators_stopped'], 32 | 'trace': ['whippet:prepare_gc', 33 | 'whippet:restarting_mutators'], 34 | 'mutator-stopped': ['whippet:mutator_stopping', 35 | 'whippet:mutator_restarted'], 36 | 'trace-roots': ['whippet:trace_roots_begin', 37 | 'whippet:trace_roots_end'], 38 | 'trace-check-termination': ['whippet:trace_check_termination_begin', 39 | 'whippet:trace_check_termination_end'], 40 | 'trace-objects': ['whippet:trace_objects_begin', 41 | 'whippet:trace_objects_end'], 42 | 'trace-worker': ['whippet:trace_worker_begin', 43 | 'whippet:trace_worker_end'] 44 | } 45 | 46 | class EventPhase(Enum): 47 | BEGIN = 'B' 48 | END = 'E' 49 | COMPLETE = 'X' 50 | INSTANT = 'i' 51 | COUNTER = 'C' 52 | NESTABLE_START = 'b' 53 | NESTABLE_INSTANT = 'n' 54 | NESTABLE_END = 'e' 55 | FLOW_START = 's' 56 | FLOW_STEP = 't' 57 | FLOW_END = 'f' 58 | SAMPLE = 'P' 59 | OBJECT_CREATED = 'N' 60 | OBJECT_SNAPSHOT = 'O' 61 | OBJECT_DESTROYED = 'D' 62 | METADATA = 'M' 63 | MEMORY_DUMP_GLOBAL = 'V' 64 | MEMORY_DUMP_PROCESS = 'V' 65 | MARK = 'R' 66 | CLOCK_SYNC = 'c' 67 | CONTEXT_BEGIN = '(' 68 | CONTEXT_END = ')' 69 | 70 | base_time = None 71 | def event_us(msg): 72 | assert(msg.default_clock_snapshot.clock_class.name == 'monotonic') 73 | assert(msg.default_clock_snapshot.clock_class.frequency == 1e9) 74 | global base_time 75 | ns = msg.default_clock_snapshot.value 76 | if base_time is None: 77 | base_time = ns 78 | return (ns - base_time) * 1e-3 79 | 80 | def lower(x): 81 | if isinstance(x, str) or isinstance(x, int) or isinstance(x, float): 82 | return x 83 | if isinstance(x, dict) or isinstance(x, bt2._StructureFieldConst): 84 | return {lower(k):lower(v) for k, v in x.items()} 85 | if isinstance(x, bt2._BoolValueConst) or isinstance(x, bt2._BoolFieldConst): 86 | return bool(x) 87 | if isinstance(x, bt2._EnumerationFieldConst): 88 | return repr(x) 89 | if isinstance(x, bt2._IntegerValueConst) or isinstance(x, bt2._IntegerFieldConst): 90 | return int(x) 91 | if isinstance(x, bt2._RealValueConst) or isinstance(x, bt2._RealFieldConst): 92 | return float(x) 93 | if isinstance(x, bt2._StringValueConst) or isinstance(x, bt2._StringFieldConst): 94 | return str(x) 95 | raise ValueError("Unexpected value from trace", x) 96 | 97 | # Specific Whippet events. 98 | synthetic_begin = {} 99 | synthetic_end = {} 100 | for synthetic, [begin, end] in synthetic_events.items(): 101 | synthetic_begin[begin] = [] 102 | synthetic_end[end] = [] 103 | for synthetic, [begin, end] in synthetic_events.items(): 104 | synthetic_begin[begin].append(synthetic) 105 | synthetic_end[end].append(synthetic) 106 | 107 | def put(str): 108 | sys.stdout.write(str) 109 | 110 | need_comma = False 111 | def print_event(ev): 112 | global need_comma 113 | if need_comma: 114 | sys.stdout.write(',\n ') 115 | else: 116 | need_comma = True 117 | # It appears to be faster to make a string, then print the string, 118 | # than to call json.dump with a file object. 119 | # json.dump(ev, sys.stdout, ensure_ascii=False, check_circular=False) 120 | put(json.dumps(ev, ensure_ascii=False, check_circular=False)) 121 | 122 | def emit_event(msg, name, phase): 123 | ev = {'name': name, 124 | 'cat': 'whippet', 125 | 'ph': phase.value, 126 | 'ts': event_us(msg), 127 | 'pid': lower(msg.event.common_context_field['vpid']), 128 | 'tid': lower(msg.event.common_context_field['vtid']), 129 | 'args': lower(msg.event.payload_field)} 130 | print_event(ev) 131 | def emit_begin_event(msg, name): 132 | emit_event(msg, name, EventPhase.BEGIN) 133 | def emit_end_event(msg, name): 134 | emit_event(msg, name, EventPhase.END) 135 | 136 | def emit_events(msg): 137 | emit_event(msg, msg.event.name, EventPhase.INSTANT) 138 | for begin in synthetic_begin.get(msg.event.name, []): 139 | emit_begin_event(msg, begin) 140 | for end in synthetic_end.get(msg.event.name, []): 141 | emit_end_event(msg, end) 142 | 143 | def ctf_to_json(path): 144 | msg_it = bt2.TraceCollectionMessageIterator(path) 145 | put('{\n') 146 | put(' "traceEvents": [\n ') 147 | for msg in msg_it: 148 | if hasattr(msg, 'event'): 149 | emit_events(msg) 150 | put('\n') 151 | put('\n ],\n') 152 | put(' "displayTimeUnit": "ns"\n') 153 | put('}\n') 154 | 155 | if len(sys.argv) != 2: 156 | sys.stderr.write( 157 | 'usage: ' + sys.argv[0] + ' ~/lttng-traces/name-of-your-trace\n') 158 | sys.exit(1) 159 | else: 160 | ctf_to_json(sys.argv[1]) 161 | -------------------------------------------------------------------------------- /whippet/doc/README.md: -------------------------------------------------------------------------------- 1 | # Whippet documentation 2 | 3 | * [Manual](./manual.md): How do you get your program to use 4 | Whippet? What is the API? 5 | 6 | * [Collector implementations](./collectors.md): There are a number of 7 | implementations of the Whippet API with differing performance 8 | characteristics and which impose different requirements on the 9 | embedder. 10 | 11 | * [Guile](./guile.md): Some notes on a potential rebase of Guile on 12 | top of Whippet. 13 | 14 | -------------------------------------------------------------------------------- /whippet/doc/collector-bdw.md: -------------------------------------------------------------------------------- 1 | # Boehm-Demers-Weiser collector 2 | 3 | Whippet's `bdw` collector is backed by a third-party garbage collector, 4 | the [Boehm-Demers-Weiser collector](https://github.com/ivmai/bdwgc). 5 | 6 | BDW-GC is a mark-sweep collector with conservative root-finding, 7 | conservative heap tracing, and parallel tracing. 8 | 9 | Whereas the other Whippet collectors which rely on mutators to 10 | [periodically check if they need to 11 | stop](https://github.com/wingo/whippet/blob/main/doc/manual.md#safepoints), 12 | `bdw` will stop mutators with a POSIX signal. Also, it doesn't really 13 | support ephemerons (the Whippet `bdw` collector simulates them using 14 | finalizers), and both ephemerons and finalizers only approximate the 15 | Whippet behavior, because they are implemented in terms of what BDW-GC 16 | provides. 17 | 18 | `bdw` supports the `fixed` and `growable` heap-sizing policies, but not 19 | `adaptive`, as BDW-GC can't reliably return memory to the OS. Also, 20 | [`growable` has an effective limit of a 3x heap 21 | multiplier](https://github.com/wingo/whippet/blob/main/src/bdw.c#L478). 22 | Oh well! 23 | 24 | It's a bit of an oddball from a Whippet perspective, but useful as a 25 | migration path if you have an embedder that is already using BDW-GC. 26 | And, it is a useful performance comparison. 27 | -------------------------------------------------------------------------------- /whippet/doc/collector-pcc.md: -------------------------------------------------------------------------------- 1 | # Parallel copying collector 2 | 3 | Whippet's `pcc` collector is a copying collector, like the more simple 4 | [`semi`](./collector-semi.md), but supporting multiple mutator threads, 5 | multiple tracing threads, and using an external FIFO worklist instead of 6 | a Cheney worklist. 7 | 8 | Like `semi`, `pcc` traces by evacuation: it moves all live objects on 9 | every collection. (Exception: objects larger than 8192 bytes are 10 | placed into a partitioned space which traces by marking in place instead 11 | of copying.) Evacuation requires precise roots, so if your embedder 12 | does not support precise roots, `pcc` is not for you. 13 | 14 | Again like `semi`, `pcc` generally requires a heap size at least twice 15 | as large as the maximum live heap size, and performs best with ample 16 | heap sizes; between 3× and 5× is best. 17 | 18 | Overall, `pcc` is a better version of `semi`. It should have broadly 19 | the same performance characteristics with a single mutator and with 20 | parallelism disabled, additionally allowing multiple mutators, and 21 | scaling better with multiple tracing threads. 22 | 23 | `pcc` has a generational configuration, conventionally referred to as 24 | `generational-pcc`, in which both the nursery and the old generation are 25 | copy spaces. Objects stay in the nursery for one cycle before moving on 26 | to the old generation. This configuration is a bit new (January 2025) 27 | and still needs some tuning. 28 | 29 | ## Implementation notes 30 | 31 | Unlike `semi` which has a single global bump-pointer allocation region, 32 | `pcc` structures the heap into 64-kB blocks. In this way it supports 33 | multiple mutator threads: mutators do local bump-pointer allocation into 34 | their own block, and when their block is full, they fetch another from 35 | the global store. 36 | 37 | The block size is 64 kB, but really it's 128 kB, because each block has 38 | two halves: the active region and the copy reserve. Dividing each block 39 | in two allows the collector to easily grow and shrink the heap while 40 | ensuring there is always enough reserve space. 41 | 42 | Blocks are allocated in 64-MB aligned slabs, so there are 512 blocks in 43 | a slab. The first block in a slab is used by the collector itself, to 44 | keep metadata for the rest of the blocks, for example a chain pointer 45 | allowing blocks to be collected in lists, a saved allocation pointer for 46 | partially-filled blocks, whether the block is paged in or out, and so 47 | on. 48 | 49 | `pcc` supports tracing in parallel. This mechanism works somewhat like 50 | allocation, in which multiple trace workers compete to evacuate objects 51 | into their local allocation buffers; when an allocation buffer is full, 52 | the trace worker grabs another, just like mutators do. 53 | 54 | Unlike the simple semi-space collector which uses a Cheney grey 55 | worklist, `pcc` uses an external worklist. If parallelism is disabled 56 | at compile-time, it uses a simple first-in, first-out queue of objects 57 | to be traced. Like a Cheney worklist, this should result in objects 58 | being copied in breadth-first order. The literature would suggest that 59 | depth-first is generally better for locality, but that preserving 60 | allocation order is generally best. This is something to experiment 61 | with in the future. 62 | 63 | If parallelism is enabled, as it is by default, `pcc` uses a 64 | [fine-grained work-stealing parallel tracer](../src/parallel-tracer.h). 65 | Each trace worker maintains a [local queue of objects that need 66 | tracing](../src/local-worklist.h), which currently has 1024 entries. If 67 | the local queue becomes full, the worker will publish 3/4 of those 68 | entries to the worker's [shared worklist](../src/shared-worklist.h). 69 | When a worker runs out of local work, it will first try to remove work 70 | from its own shared worklist, then will try to steal from other workers. 71 | 72 | If only one tracing thread is enabled at run-time (`parallelism=1`) (or 73 | if parallelism is disabled at compile-time), `pcc` will evacuate by 74 | non-atomic forwarding, but if multiple threads compete to evacuate 75 | objects, `pcc` uses [atomic compare-and-swap instead of simple 76 | forwarding pointer updates](./manual.md#forwarding-objects). This 77 | imposes around a ~30% performance penalty but having multiple tracing 78 | threads is generally worth it, unless the object graph is itself serial. 79 | 80 | The memory used for the external worklist is dynamically allocated from 81 | the OS and is not currently counted as contributing to the heap size. 82 | If you are targetting a microcontroller or something, probably you need 83 | to choose a different kind of collector that never dynamically 84 | allocates, such as `semi`. 85 | -------------------------------------------------------------------------------- /whippet/doc/collector-semi.md: -------------------------------------------------------------------------------- 1 | # Semi-space collector 2 | 3 | The `semi` collector is simple. It is mostly useful as a first 4 | collector to try out, to make sure that a mutator correctly records all 5 | roots: because `semi` moves every live object on every collection, it is 6 | very effective at shaking out mutator bugs. 7 | 8 | If your embedder chooses to not precisely record roots, for example 9 | instead choosing to conservatively scan the stack, then the semi-space 10 | collector is not for you: `semi` requires precise roots. 11 | 12 | For more on semi-space collectors, see 13 | https://wingolog.org/archives/2022/12/10/a-simple-semi-space-collector. 14 | 15 | Whippet's `semi` collector incorporates a large-object space, which 16 | marks objects in place instead of moving. Otherwise, `semi` generally 17 | requires a heap size at least twice as large as the maximum live heap 18 | size, and performs best with ample heap sizes; between 3× and 5× is 19 | best. 20 | 21 | The semi-space collector doesn't support multiple mutator threads. If 22 | you want a copying collector for a multi-threaded mutator, look at 23 | [pcc](./collector-pcc.md). 24 | -------------------------------------------------------------------------------- /whippet/doc/collectors.md: -------------------------------------------------------------------------------- 1 | # Whippet collectors 2 | 3 | Whippet has four collectors currently: 4 | - [Semi-space collector (`semi`)](./collector-semi.md): For 5 | single-threaded embedders who are not too tight on memory. 6 | - [Parallel copying collector (`pcc`)](./collector-pcc.md): Like 7 | `semi`, but with support for multiple mutator and tracing threads and 8 | generational collection. 9 | - [Mostly marking collector (`mmc`)](./collector-mmc.md): 10 | Immix-inspired collector. Optionally parallel, conservative (stack 11 | and/or heap), and/or generational. 12 | - [Boehm-Demers-Weiser collector (`bdw`)](./collector-bdw.md): 13 | Conservative mark-sweep collector, implemented by 14 | Boehm-Demers-Weiser library. 15 | 16 | ## How to choose? 17 | 18 | If you are migrating an embedder off BDW-GC, then it could be reasonable 19 | to first go to `bdw`, then `stack-conservative-parallel-mmc`. 20 | 21 | If you have an embedder with precise roots, use `pcc`. That will shake 22 | out mutator/embedder bugs. Then if memory is tight, switch to 23 | `parallel-mmc`, possibly `parallel-generational-mmc`. 24 | 25 | If you are aiming for maximum simplicity and minimal code size (ten 26 | kilobytes or so), use `semi`. 27 | 28 | If you are writing a new project, you have a choice as to whether to pay 29 | the development cost of precise roots or not. If you choose to not have 30 | precise roots, then go for `stack-conservative-parallel-mmc` directly. 31 | 32 | ## More collectors 33 | 34 | It would be nice to have a generational GC that uses the space from 35 | `parallel-mmc` for the old generation but a pcc-style copying nursery. 36 | We have `generational-pcc` now, so this should be possible. 37 | 38 | Support for concurrent marking in `mmc` would be good as well, perhaps 39 | with a SATB barrier. (Or, if you are the sort of person to bet on 40 | conservative stack scanning, perhaps a retreating-wavefront barrier 41 | would be more appropriate.) 42 | 43 | Contributions are welcome, provided they have no more dependencies! 44 | -------------------------------------------------------------------------------- /whippet/doc/guile.md: -------------------------------------------------------------------------------- 1 | # Whippet and Guile 2 | 3 | If the `mmc` collector works out, it could replace Guile's garbage 4 | collector. Guile currently uses BDW-GC. Guile has a widely used C API 5 | and implements part of its run-time in C. For this reason it may be 6 | infeasible to require precise enumeration of GC roots -- we may need to 7 | allow GC roots to be conservatively identified from data sections and 8 | from stacks. Such conservative roots would be pinned, but other objects 9 | can be moved by the collector if it chooses to do so. We assume that 10 | object references within a heap object can be precisely identified. 11 | (However, Guile currently uses BDW-GC in its default configuration, 12 | which scans for references conservatively even on the heap.) 13 | 14 | The existing C API allows direct access to mutable object fields, 15 | without the mediation of read or write barriers. Therefore it may be 16 | impossible to switch to collector strategies that need barriers, such as 17 | generational or concurrent collectors. However, we shouldn't write off 18 | this possibility entirely; an ideal replacement for Guile's GC will 19 | offer the possibility of migration to other GC designs without imposing 20 | new requirements on C API users in the initial phase. 21 | 22 | In this regard, the Whippet experiment also has the goal of identifying 23 | a smallish GC abstraction in Guile, so that we might consider evolving 24 | GC implementation in the future without too much pain. If we switch 25 | away from BDW-GC, we should be able to evaluate that it's a win for a 26 | large majority of use cases. 27 | -------------------------------------------------------------------------------- /whippet/doc/perfetto-minor-gc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wingo/whiffle/81c1e37e0f207011414d2653c5051a3b97e7fa89/whippet/doc/perfetto-minor-gc.png -------------------------------------------------------------------------------- /whippet/doc/tracepoints.md: -------------------------------------------------------------------------------- 1 | # Whippet performance tracing 2 | 3 | Whippet includes support for run-time tracing via 4 | [LTTng](https://LTTng.org) user-space tracepoints. This allows you to 5 | get a detailed look at how Whippet is performing on your system. 6 | Tracing support is currently limited to Linux systems. 7 | 8 | ## Getting started 9 | 10 | First, you need to build Whippet with LTTng support. Usually this is as 11 | easy as building it in an environment where the `lttng-ust` library is 12 | present, as determined by `pkg-config --libs lttng-ust`. You can know 13 | if your Whippet has tracing support by seeing if the resulting binaries 14 | are dynamically linked to `liblttng-ust`. 15 | 16 | If we take as an example the `mt-gcbench` test in the Whippet source 17 | tree, we would have: 18 | 19 | ``` 20 | $ ldd bin/mt-gcbench.pcc | grep lttng 21 | ... 22 | liblttng-ust.so.1 => ... 23 | ... 24 | ``` 25 | 26 | ### Capturing traces 27 | 28 | Actually capturing traces is a little annoying; it's not as easy as 29 | `perf run`. The [LTTng 30 | documentation](https://lttng.org/docs/v2.13/#doc-controlling-tracing) is 31 | quite thorough, but here is a summary. 32 | 33 | First, create your tracing session: 34 | 35 | ``` 36 | $ lttng create 37 | Session auto-20250214-091153 created. 38 | Traces will be output to ~/lttng-traces/auto-20250214-091153 39 | ``` 40 | 41 | You run all these commands as your own user; they don't require root 42 | permissions or system-wide modifications, as all of the Whippet 43 | tracepoints are user-space tracepoints (UST). 44 | 45 | Just having an LTTng session created won't do anything though; you need 46 | to configure the session. Monotonic nanosecond-resolution timestamps 47 | are already implicitly part of each event. We also want to have process 48 | and thread IDs for all events: 49 | 50 | ``` 51 | $ lttng add-context --userspace --type=vpid --type=vtid 52 | ust context vpid added to all channels 53 | ust context vtid added to all channels 54 | ``` 55 | 56 | Now enable Whippet events: 57 | 58 | ``` 59 | $ lttng enable-event --userspace 'whippet:*' 60 | ust event whippet:* created in channel channel0 61 | ``` 62 | 63 | And now, start recording: 64 | 65 | ``` 66 | $ lttng start 67 | Tracing started for session auto-20250214-091153 68 | ``` 69 | 70 | With this, traces will be captured for our program of interest: 71 | 72 | ``` 73 | $ bin/mt-gcbench.pcc 2.5 8 74 | ... 75 | ``` 76 | 77 | Now stop the trace: 78 | 79 | ``` 80 | $ lttng stop 81 | Waiting for data availability 82 | Tracing stopped for session auto-20250214-091153 83 | ``` 84 | 85 | Whew. If we did it right, our data is now in 86 | `~/lttng-traces/auto-20250214-091153`. 87 | 88 | ### Visualizing traces 89 | 90 | LTTng produces traces in the [Common Trace Format 91 | (CTF)](https://diamon.org/ctf/). My favorite trace viewing tool is the 92 | family of web-based trace viewers derived from `chrome://tracing`. The 93 | best of these appear to be [the Firefox 94 | profiler](https://profiler.firefox.com) and 95 | [Perfetto](https://ui.perfetto.dev). Unfortunately neither of these can 96 | work with CTF directly, so we instead need to run a trace converter. 97 | 98 | Oddly, there is no trace converter that can read CTF and write something 99 | that Perfetto (e.g.) can read. However there is a [JSON-based tracing 100 | format that these tools can 101 | read](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview?tab=t.0#heading=h.yr4qxyxotyw), 102 | and [Python bindings for Babeltrace, a library that works with 103 | CTF](https://babeltrace.org/), so that's what we do: 104 | 105 | ``` 106 | $ python3 ctf_to_json.py ~/lttng-traces/auto-20250214-091153 > trace.json 107 | ``` 108 | 109 | While Firefox Profiler can load this file, it works better on Perfetto, 110 | as the Whippet events are visually rendered on their respective threads. 111 | 112 | ![Screenshot of part of Perfetto UI showing a minor GC](./perfetto-minor-gc.png) 113 | 114 | ### Expanding the set of events 115 | 116 | As of February 2025, 117 | the current set of tracepoints includes the [heap 118 | events](https://github.com/wingo/whippet/blob/main/doc/manual.md#statistics) 119 | and some detailed internals of the parallel tracer. We expect this set 120 | of tracepoints to expand over time. 121 | 122 | ### Overhead of tracepoints 123 | 124 | When tracepoints are compiled in but no events are enabled, tracepoints 125 | appear to have no impact on run-time. When event collection is on, for 126 | x86-64 hardware, [emitting a tracepoint event takes about 127 | 100ns](https://discuss.systems/@DesnoyersMa/113986344940256872). 128 | -------------------------------------------------------------------------------- /whippet/embed.mk: -------------------------------------------------------------------------------- 1 | GC_COLLECTOR ?= pcc 2 | GC_EMBEDDER_H ?= whippet-embedder.h 3 | GC_EMBEDDER_CPPFLAGS ?= 4 | 5 | DEFAULT_BUILD := opt 6 | 7 | BUILD_CPPFLAGS_opt = -DNDEBUG 8 | BUILD_CPPFLAGS_optdebug = -DGC_DEBUG=1 9 | BUILD_CPPFLAGS_debug = -DGC_DEBUG=1 10 | 11 | BUILD_CFLAGS_opt = -O2 -g 12 | BUILD_CFLAGS_optdebug = -O2 -g 13 | BUILD_CFLAGS_debug = -O0 -g 14 | 15 | GC_BUILD_CPPFLAGS = $(BUILD_CPPFLAGS_$(or $(GC_BUILD),$(DEFAULT_BUILD))) 16 | GC_BUILD_CFLAGS = $(BUILD_CFLAGS_$(or $(GC_BUILD),$(DEFAULT_BUILD))) 17 | 18 | V ?= 1 19 | v_0 = @ 20 | v_1 = 21 | 22 | GC_USE_LTTNG_0 := 23 | GC_USE_LTTNG_1 := 1 24 | GC_USE_LTTNG := $(shell pkg-config --exists lttng-ust && echo 1 || echo 0) 25 | GC_LTTNG_CPPFLAGS := $(if $(GC_USE_LTTNG_$(GC_USE_LTTNG)), $(shell pkg-config --cflags lttng-ust),) 26 | GC_LTTNG_LIBS := $(if $(GC_USE_LTTNG_$(GC_USE_LTTNG)), $(shell pkg-config --libs lttng-ust),) 27 | GC_TRACEPOINT_CPPFLAGS = $(if $(GC_USE_LTTNG_$(GC_USE_LTTNG)),$(GC_LTTNG_CPPFLAGS) -DGC_TRACEPOINT_LTTNG=1,) 28 | GC_TRACEPOINT_LIBS = $(GC_LTTNG_LIBS) 29 | 30 | GC_V = $(v_$(V)) 31 | GC_CC = gcc 32 | GC_CFLAGS = -Wall -flto -fno-strict-aliasing -fvisibility=hidden -Wno-unused $(GC_BUILD_CFLAGS) 33 | GC_CPPFLAGS = -I$(GC_BASE)api $(GC_TRACEPOINT_CPPFLAGS) $(GC_BUILD_CPPFLAGS) 34 | GC_LDFLAGS = -lpthread -flto=auto $(GC_TRACEPOINT_LIBS) 35 | GC_DEPFLAGS = 36 | GC_COMPILE = $(GC_V)$(GC_CC) $(GC_CFLAGS) $(GC_CPPFLAGS) $(GC_DEPFLAGS) -o $@ 37 | GC_LINK = $(GC_V)$(GC_CC) $(GC_LDFLAGS) -o $@ 38 | GC_PLATFORM = gnu-linux 39 | GC_OBJDIR = 40 | GC_EMBEDDER_CPPFLAGS += -DGC_EMBEDDER=\"$(abspath $(GC_EMBEDDER_H))\" 41 | 42 | $(GC_OBJDIR)gc-platform.o: $(GC_BASE)src/gc-platform-$(GC_PLATFORM).c 43 | $(GC_COMPILE) -c $< 44 | $(GC_OBJDIR)gc-stack.o: $(GC_BASE)src/gc-stack.c 45 | $(GC_COMPILE) -c $< 46 | $(GC_OBJDIR)gc-options.o: $(GC_BASE)src/gc-options.c 47 | $(GC_COMPILE) -c $< 48 | $(GC_OBJDIR)gc-tracepoint.o: $(GC_BASE)src/gc-tracepoint.c 49 | $(GC_COMPILE) -c $< 50 | $(GC_OBJDIR)gc-ephemeron.o: $(GC_BASE)src/gc-ephemeron.c 51 | $(GC_COMPILE) $(GC_EMBEDDER_CPPFLAGS) -c $< 52 | $(GC_OBJDIR)gc-finalizer.o: $(GC_BASE)src/gc-finalizer.c 53 | $(GC_COMPILE) $(GC_EMBEDDER_CPPFLAGS) -c $< 54 | 55 | GC_STEM_bdw = bdw 56 | GC_CPPFLAGS_bdw = -DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1 57 | GC_IMPL_CFLAGS_bdw = `pkg-config --cflags bdw-gc` 58 | GC_LIBS_bdw = `pkg-config --libs bdw-gc` 59 | 60 | GC_STEM_semi = semi 61 | GC_CPPFLAGS_semi = -DGC_PRECISE_ROOTS=1 62 | GC_LIBS_semi = -lm 63 | 64 | GC_STEM_pcc = pcc 65 | GC_CPPFLAGS_pcc = -DGC_PRECISE_ROOTS=1 -DGC_PARALLEL=1 66 | GC_LIBS_pcc = -lm 67 | 68 | GC_STEM_generational_pcc = $(GC_STEM_pcc) 69 | GC_CFLAGS_generational_pcc = $(GC_CFLAGS_pcc) -DGC_GENERATIONAL=1 70 | GC_LIBS_generational_pcc = $(GC_LIBS_pcc) 71 | 72 | define mmc_variant 73 | GC_STEM_$(1) = mmc 74 | GC_CPPFLAGS_$(1) = $(2) 75 | GC_LIBS_$(1) = -lm 76 | endef 77 | 78 | define generational_mmc_variants 79 | $(call mmc_variant,$(1)mmc,$(2)) 80 | $(call mmc_variant,$(1)generational_mmc,$(2) -DGC_GENERATIONAL=1) 81 | endef 82 | 83 | define parallel_mmc_variants 84 | $(call generational_mmc_variants,$(1),$(2)) 85 | $(call generational_mmc_variants,$(1)parallel_,$(2) -DGC_PARALLEL=1) 86 | endef 87 | 88 | define trace_mmc_variants 89 | $(call parallel_mmc_variants,,-DGC_PRECISE_ROOTS=1) 90 | $(call parallel_mmc_variants,stack_conservative_,-DGC_CONSERVATIVE_ROOTS=1) 91 | $(call parallel_mmc_variants,heap_conservative_,-DGC_CONSERVATIVE_ROOTS=1 -DGC_CONSERVATIVE_TRACE=1) 92 | endef 93 | 94 | $(eval $(call trace_mmc_variants)) 95 | 96 | gc_var = $($(1)$(subst -,_,$(2))) 97 | gc_impl = $(call gc_var,GC_STEM_,$(1)).c 98 | gc_attrs = $(call gc_var,GC_STEM_,$(1))-attrs.h 99 | gc_cppflags = $(call gc_var,GC_CPPFLAGS_,$(1)) 100 | gc_impl_cflags = $(call gc_var,GC_IMPL_CFLAGS_,$(1)) 101 | gc_libs = $(call gc_var,GC_LIBS_,$(1)) 102 | 103 | GC_IMPL = $(call gc_impl,$(GC_COLLECTOR)) 104 | GC_CPPFLAGS += $(call gc_cppflags,$(GC_COLLECTOR)) 105 | GC_ATTRS_H = $(GC_BASE)api/$(call gc_attrs,$(GC_COLLECTOR)) 106 | GC_CPPFLAGS += -DGC_ATTRS=\"$(abspath $(GC_ATTRS_H))\" 107 | GC_IMPL_CFLAGS = $(call gc_impl_cflags,$(GC_COLLECTOR)) 108 | GC_LIBS = $(call gc_libs,$(GC_COLLECTOR)) 109 | 110 | $(GC_OBJDIR)gc-impl.o: $(GC_BASE)src/$(call gc_impl,$(GC_COLLECTOR)) 111 | $(GC_COMPILE) $(GC_IMPL_CFLAGS) $(GC_EMBEDDER_CPPFLAGS) -c $< 112 | 113 | GC_OBJS=$(foreach O,gc-platform.o gc-stack.o gc-options.o gc-tracepoint.o gc-ephemeron.o gc-finalizer.o gc-impl.o,$(GC_OBJDIR)$(O)) 114 | -------------------------------------------------------------------------------- /whippet/manifest.scm: -------------------------------------------------------------------------------- 1 | (use-modules (guix packages)) 2 | 3 | (specifications->manifest 4 | '("bash" 5 | "coreutils" 6 | "gcc-toolchain" 7 | "lttng-ust" 8 | "glibc" 9 | "libgc" 10 | "make" 11 | "pkg-config")) 12 | -------------------------------------------------------------------------------- /whippet/src/address-hash.h: -------------------------------------------------------------------------------- 1 | #ifndef ADDRESS_HASH_H 2 | #define ADDRESS_HASH_H 3 | 4 | #include 5 | 6 | static uintptr_t hash_address(uintptr_t x) { 7 | if (sizeof (x) < 8) { 8 | // Chris Wellon's lowbias32, from https://nullprogram.com/blog/2018/07/31/. 9 | x ^= x >> 16; 10 | x *= 0x7feb352dU; 11 | x ^= x >> 15; 12 | x *= 0x846ca68bU; 13 | x ^= x >> 16; 14 | return x; 15 | } else { 16 | // Sebastiano Vigna's splitmix64 integer mixer, from 17 | // https://prng.di.unimi.it/splitmix64.c. 18 | x ^= x >> 30; 19 | x *= 0xbf58476d1ce4e5b9U; 20 | x ^= x >> 27; 21 | x *= 0x94d049bb133111ebU; 22 | x ^= x >> 31; 23 | return x; 24 | } 25 | } 26 | // Inverse of hash_address from https://nullprogram.com/blog/2018/07/31/. 27 | static uintptr_t unhash_address(uintptr_t x) { 28 | if (sizeof (x) < 8) { 29 | x ^= x >> 16; 30 | x *= 0x43021123U; 31 | x ^= x >> 15 ^ x >> 30; 32 | x *= 0x1d69e2a5U; 33 | x ^= x >> 16; 34 | return x; 35 | } else { 36 | x ^= x >> 31 ^ x >> 62; 37 | x *= 0x319642b2d24d8ec3U; 38 | x ^= x >> 27 ^ x >> 54; 39 | x *= 0x96de1b173f119089U; 40 | x ^= x >> 30 ^ x >> 60; 41 | return x; 42 | } 43 | } 44 | 45 | #endif // ADDRESS_HASH_H 46 | -------------------------------------------------------------------------------- /whippet/src/assert.h: -------------------------------------------------------------------------------- 1 | #ifndef ASSERT_H 2 | #define ASSERT_H 3 | 4 | #define STATIC_ASSERT_EQ(a, b) _Static_assert((a) == (b), "eq") 5 | 6 | #define UNLIKELY(e) __builtin_expect(e, 0) 7 | #define LIKELY(e) __builtin_expect(e, 1) 8 | 9 | #ifndef NDEBUG 10 | #define ASSERT(x) do { if (UNLIKELY(!(x))) __builtin_trap(); } while (0) 11 | #else 12 | #define ASSERT(x) do { } while (0) 13 | #endif 14 | #define ASSERT_EQ(a,b) ASSERT((a) == (b)) 15 | 16 | #endif // ASSERT_H 17 | -------------------------------------------------------------------------------- /whippet/src/background-thread.h: -------------------------------------------------------------------------------- 1 | #ifndef BACKGROUND_THREAD_H 2 | #define BACKGROUND_THREAD_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "assert.h" 9 | #include "debug.h" 10 | 11 | enum { 12 | GC_BACKGROUND_TASK_START = 0, 13 | GC_BACKGROUND_TASK_MIDDLE = 100, 14 | GC_BACKGROUND_TASK_END = 200 15 | }; 16 | 17 | struct gc_background_task { 18 | int id; 19 | int priority; 20 | void (*run)(void *data); 21 | void *data; 22 | }; 23 | 24 | enum gc_background_thread_state { 25 | GC_BACKGROUND_THREAD_STARTING, 26 | GC_BACKGROUND_THREAD_RUNNING, 27 | GC_BACKGROUND_THREAD_STOPPING 28 | }; 29 | 30 | struct gc_background_thread { 31 | size_t count; 32 | size_t capacity; 33 | struct gc_background_task *tasks; 34 | int next_id; 35 | enum gc_background_thread_state state; 36 | pthread_t thread; 37 | pthread_mutex_t lock; 38 | pthread_cond_t cond; 39 | }; 40 | 41 | static void* 42 | gc_background_thread(void *data) { 43 | struct gc_background_thread *thread = data; 44 | pthread_mutex_lock(&thread->lock); 45 | while (thread->state == GC_BACKGROUND_THREAD_STARTING) 46 | pthread_cond_wait(&thread->cond, &thread->lock); 47 | struct timespec ts; 48 | if (clock_gettime(CLOCK_REALTIME, &ts)) { 49 | perror("background thread: failed to get time!"); 50 | return NULL; 51 | } 52 | while (thread->state == GC_BACKGROUND_THREAD_RUNNING) { 53 | ts.tv_sec += 1; 54 | pthread_cond_timedwait(&thread->cond, &thread->lock, &ts); 55 | if (thread->state == GC_BACKGROUND_THREAD_RUNNING) 56 | for (size_t i = 0; i < thread->count; i++) 57 | thread->tasks[i].run(thread->tasks[i].data); 58 | } 59 | pthread_mutex_unlock(&thread->lock); 60 | return NULL; 61 | } 62 | 63 | static struct gc_background_thread* 64 | gc_make_background_thread(void) { 65 | struct gc_background_thread *thread; 66 | thread = malloc(sizeof(*thread)); 67 | if (!thread) 68 | GC_CRASH(); 69 | memset(thread, 0, sizeof(*thread)); 70 | thread->tasks = NULL; 71 | thread->count = 0; 72 | thread->capacity = 0; 73 | thread->state = GC_BACKGROUND_THREAD_STARTING; 74 | pthread_mutex_init(&thread->lock, NULL); 75 | pthread_cond_init(&thread->cond, NULL); 76 | if (pthread_create(&thread->thread, NULL, gc_background_thread, thread)) { 77 | perror("spawning background thread failed"); 78 | GC_CRASH(); 79 | } 80 | return thread; 81 | } 82 | 83 | static void 84 | gc_background_thread_start(struct gc_background_thread *thread) { 85 | pthread_mutex_lock(&thread->lock); 86 | GC_ASSERT_EQ(thread->state, GC_BACKGROUND_THREAD_STARTING); 87 | thread->state = GC_BACKGROUND_THREAD_RUNNING; 88 | pthread_mutex_unlock(&thread->lock); 89 | pthread_cond_signal(&thread->cond); 90 | } 91 | 92 | static int 93 | gc_background_thread_add_task(struct gc_background_thread *thread, 94 | int priority, void (*run)(void *data), 95 | void *data) { 96 | pthread_mutex_lock(&thread->lock); 97 | if (thread->count == thread->capacity) { 98 | size_t new_capacity = thread->capacity * 2 + 1; 99 | struct gc_background_task *new_tasks = 100 | realloc(thread->tasks, sizeof(struct gc_background_task) * new_capacity); 101 | if (!new_tasks) { 102 | perror("ran out of space for background tasks!"); 103 | GC_CRASH(); 104 | } 105 | thread->capacity = new_capacity; 106 | thread->tasks = new_tasks; 107 | } 108 | size_t insert = 0; 109 | for (; insert < thread->count; insert++) { 110 | if (priority < thread->tasks[insert].priority) 111 | break; 112 | } 113 | size_t bytes_to_move = 114 | (thread->count - insert) * sizeof(struct gc_background_task); 115 | memmove(&thread->tasks[insert + 1], &thread->tasks[insert], bytes_to_move); 116 | int id = thread->next_id++; 117 | thread->tasks[insert].id = id; 118 | thread->tasks[insert].priority = priority; 119 | thread->tasks[insert].run = run; 120 | thread->tasks[insert].data = data; 121 | thread->count++; 122 | pthread_mutex_unlock(&thread->lock); 123 | return id; 124 | } 125 | 126 | static void 127 | gc_background_thread_remove_task(struct gc_background_thread *thread, 128 | int id) { 129 | pthread_mutex_lock(&thread->lock); 130 | size_t remove = 0; 131 | for (; remove < thread->count; remove++) { 132 | if (thread->tasks[remove].id == id) 133 | break; 134 | } 135 | if (remove == thread->count) 136 | GC_CRASH(); 137 | size_t bytes_to_move = 138 | (thread->count - (remove + 1)) * sizeof(struct gc_background_task); 139 | memmove(&thread->tasks[remove], &thread->tasks[remove + 1], bytes_to_move); 140 | pthread_mutex_unlock(&thread->lock); 141 | } 142 | 143 | static void 144 | gc_destroy_background_thread(struct gc_background_thread *thread) { 145 | pthread_mutex_lock(&thread->lock); 146 | GC_ASSERT(thread->state == GC_BACKGROUND_THREAD_RUNNING); 147 | thread->state = GC_BACKGROUND_THREAD_STOPPING; 148 | pthread_mutex_unlock(&thread->lock); 149 | pthread_cond_signal(&thread->cond); 150 | pthread_join(thread->thread, NULL); 151 | free(thread->tasks); 152 | free(thread); 153 | } 154 | 155 | #endif // BACKGROUND_THREAD_H 156 | -------------------------------------------------------------------------------- /whippet/src/debug.h: -------------------------------------------------------------------------------- 1 | #ifndef DEBUG_H 2 | #define DEBUG_H 3 | 4 | #ifndef NDEBUG 5 | #define DEBUG(...) fprintf (stderr, "DEBUG: " __VA_ARGS__) 6 | #else 7 | #define DEBUG(...) do { } while (0) 8 | #endif 9 | 10 | #endif // DEBUG_H 11 | -------------------------------------------------------------------------------- /whippet/src/embedder-api-impl.h: -------------------------------------------------------------------------------- 1 | #ifndef EMBEDDER_API_IMPL_H 2 | #define EMBEDDER_API_IMPL_H 3 | 4 | #include "gc-embedder-api.h" 5 | 6 | #ifndef GC_EMBEDDER 7 | #define GC_EMBEDDER "whippet-embedder.h" 8 | #endif 9 | 10 | #include GC_EMBEDDER 11 | 12 | #endif // EMBEDDER_API_IMPL_H 13 | -------------------------------------------------------------------------------- /whippet/src/extents.h: -------------------------------------------------------------------------------- 1 | #ifndef EXTENTS_H 2 | #define EXTENTS_H 3 | 4 | #include 5 | #include 6 | 7 | #include "gc-assert.h" 8 | 9 | struct extent_range { 10 | uintptr_t lo_addr; 11 | uintptr_t hi_addr; 12 | }; 13 | 14 | struct extents { 15 | size_t size; 16 | size_t capacity; 17 | struct extent_range ranges[]; 18 | }; 19 | 20 | static inline int 21 | extents_contain_addr(struct extents *extents, uintptr_t addr) { 22 | size_t lo = 0; 23 | size_t hi = extents->size; 24 | while (lo != hi) { 25 | size_t mid = (lo + hi) / 2; 26 | struct extent_range range = extents->ranges[mid]; 27 | if (addr < range.lo_addr) { 28 | hi = mid; 29 | } else if (addr < range.hi_addr) { 30 | return 1; 31 | } else { 32 | lo = mid + 1; 33 | } 34 | } 35 | return 0; 36 | } 37 | 38 | static struct extents* 39 | extents_allocate(size_t capacity) { 40 | size_t byte_size = 41 | sizeof(struct extents) + sizeof(struct extent_range) * capacity; 42 | struct extents *ret = malloc(byte_size); 43 | if (!ret) __builtin_trap(); 44 | memset(ret, 0, byte_size); 45 | ret->capacity = capacity; 46 | return ret; 47 | } 48 | 49 | static struct extents* 50 | extents_insert(struct extents *old, size_t idx, struct extent_range range) { 51 | if (old->size < old->capacity) { 52 | size_t bytes_to_move = sizeof(struct extent_range) * (old->size - idx); 53 | memmove(&old->ranges[idx + 1], &old->ranges[idx], bytes_to_move); 54 | old->ranges[idx] = range; 55 | old->size++; 56 | return old; 57 | } else { 58 | struct extents *new_ = extents_allocate(old->capacity * 2 + 1); 59 | memcpy(&new_->ranges[0], &old->ranges[0], 60 | sizeof(struct extent_range) * idx); 61 | memcpy(&new_->ranges[idx + 1], &old->ranges[idx], 62 | sizeof(struct extent_range) * (old->size - idx)); 63 | new_->ranges[idx] = range; 64 | new_->size = old->size + 1; 65 | free(old); 66 | return new_; 67 | } 68 | } 69 | 70 | static struct extents* 71 | extents_adjoin(struct extents *extents, void *lo_addr, size_t size) { 72 | size_t i; 73 | struct extent_range range = { (uintptr_t)lo_addr, (uintptr_t)lo_addr + size }; 74 | for (i = 0; i < extents->size; i++) { 75 | if (range.hi_addr < extents->ranges[i].lo_addr) { 76 | break; 77 | } else if (range.hi_addr == extents->ranges[i].lo_addr) { 78 | extents->ranges[i].lo_addr = range.lo_addr; 79 | return extents; 80 | } else if (range.lo_addr == extents->ranges[i].hi_addr) { 81 | extents->ranges[i].hi_addr = range.hi_addr; 82 | return extents; 83 | } 84 | } 85 | return extents_insert(extents, i, range); 86 | } 87 | 88 | #endif // EXTENTS_H 89 | -------------------------------------------------------------------------------- /whippet/src/freelist.h: -------------------------------------------------------------------------------- 1 | #ifndef FREELIST_H 2 | #define FREELIST_H 3 | 4 | // A size-segregated freelist with linear-log buckets à la 5 | // https://pvk.ca/Blog/2015/06/27/linear-log-bucketing-fast-versatile-simple/. 6 | 7 | #include "gc-assert.h" 8 | #include "gc-histogram.h" 9 | 10 | #include 11 | 12 | #define DEFINE_FREELIST(name, max_value_bits, precision, node) \ 13 | struct name { node buckets[((max_value_bits) << (precision)) + 1]; }; \ 14 | static inline size_t name##_num_size_classes(void) { \ 15 | return ((max_value_bits) << (precision)) + 1; \ 16 | } \ 17 | static inline uint64_t name##_bucket_min_val(size_t idx) { \ 18 | GC_ASSERT(idx < name##_num_size_classes()); \ 19 | return gc_histogram_bucket_min_val((precision), idx); \ 20 | } \ 21 | static inline void name##_init(struct name *f) { \ 22 | memset(f, 0, sizeof(*f)); \ 23 | } \ 24 | static inline size_t name##_size_class(uint64_t val) { \ 25 | return gc_histogram_bucket((max_value_bits), (precision), val); \ 26 | } \ 27 | static inline node* name##_bucket(struct name *f, uint64_t val) { \ 28 | return &f->buckets[name##_size_class(val)]; \ 29 | } 30 | 31 | #endif // FREELIST_H 32 | -------------------------------------------------------------------------------- /whippet/src/gc-align.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_ALIGN_H 2 | #define GC_ALIGN_H 3 | 4 | #ifndef GC_IMPL 5 | #error internal header file, not part of API 6 | #endif 7 | 8 | #include 9 | 10 | static inline uintptr_t align_down(uintptr_t addr, size_t align) { 11 | return addr & ~(align - 1); 12 | } 13 | static inline uintptr_t align_up(uintptr_t addr, size_t align) { 14 | return align_down(addr + align - 1, align); 15 | } 16 | 17 | // Poor man's equivalent of std::hardware_destructive_interference_size. 18 | #define AVOID_FALSE_SHARING 128 19 | #define ALIGNED_TO_AVOID_FALSE_SHARING \ 20 | __attribute__((aligned(AVOID_FALSE_SHARING))) 21 | 22 | #endif // GC_ALIGN_H 23 | -------------------------------------------------------------------------------- /whippet/src/gc-ephemeron-internal.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_EPHEMERON_INTERNAL_H 2 | #define GC_EPHEMERON_INTERNAL_H 3 | 4 | #ifndef GC_IMPL 5 | #error internal header file, not part of API 6 | #endif 7 | 8 | #include "gc-ephemeron.h" 9 | 10 | struct gc_pending_ephemerons; 11 | 12 | // API implemented by collector, for use by ephemerons: 13 | GC_INTERNAL int gc_visit_ephemeron_key(struct gc_edge edge, 14 | struct gc_heap *heap); 15 | GC_INTERNAL struct gc_pending_ephemerons* 16 | gc_heap_pending_ephemerons(struct gc_heap *heap); 17 | GC_INTERNAL unsigned gc_heap_ephemeron_trace_epoch(struct gc_heap *heap); 18 | 19 | // API implemented by ephemerons, for use by collector: 20 | GC_INTERNAL struct gc_edge gc_ephemeron_key_edge(struct gc_ephemeron *eph); 21 | GC_INTERNAL struct gc_edge gc_ephemeron_value_edge(struct gc_ephemeron *eph); 22 | 23 | GC_INTERNAL struct gc_pending_ephemerons* 24 | gc_prepare_pending_ephemerons(struct gc_pending_ephemerons *state, 25 | size_t target_size, double slop); 26 | 27 | GC_INTERNAL void 28 | gc_resolve_pending_ephemerons(struct gc_ref obj, struct gc_heap *heap); 29 | 30 | GC_INTERNAL void 31 | gc_scan_pending_ephemerons(struct gc_pending_ephemerons *state, 32 | struct gc_heap *heap, size_t shard, 33 | size_t nshards); 34 | 35 | GC_INTERNAL struct gc_ephemeron* 36 | gc_pop_resolved_ephemerons(struct gc_heap *heap); 37 | 38 | GC_INTERNAL void 39 | gc_trace_resolved_ephemerons(struct gc_ephemeron *resolved, 40 | void (*visit)(struct gc_edge edge, 41 | struct gc_heap *heap, 42 | void *visit_data), 43 | struct gc_heap *heap, 44 | void *trace_data); 45 | 46 | GC_INTERNAL void 47 | gc_sweep_pending_ephemerons(struct gc_pending_ephemerons *state, 48 | size_t shard, size_t nshards); 49 | 50 | GC_INTERNAL void gc_ephemeron_init_internal(struct gc_heap *heap, 51 | struct gc_ephemeron *ephemeron, 52 | struct gc_ref key, 53 | struct gc_ref value); 54 | 55 | #endif // GC_EPHEMERON_INTERNAL_H 56 | -------------------------------------------------------------------------------- /whippet/src/gc-finalizer-internal.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_FINALIZER_INTERNAL_H 2 | #define GC_FINALIZER_INTERNAL_H 3 | 4 | #ifndef GC_IMPL 5 | #error internal header file, not part of API 6 | #endif 7 | 8 | #include "gc-finalizer.h" 9 | #include "root.h" 10 | 11 | struct gc_finalizer_state; 12 | 13 | GC_INTERNAL 14 | struct gc_finalizer_state* gc_make_finalizer_state(void); 15 | 16 | GC_INTERNAL 17 | void gc_finalizer_init_internal(struct gc_finalizer *f, 18 | struct gc_ref object, 19 | struct gc_ref closure); 20 | 21 | GC_INTERNAL 22 | void gc_finalizer_attach_internal(struct gc_finalizer_state *state, 23 | struct gc_finalizer *f, 24 | unsigned priority); 25 | 26 | GC_INTERNAL 27 | void gc_finalizer_externally_activated(struct gc_finalizer *f); 28 | 29 | GC_INTERNAL 30 | void gc_finalizer_externally_fired(struct gc_finalizer_state *state, 31 | struct gc_finalizer *finalizer); 32 | 33 | GC_INTERNAL 34 | struct gc_finalizer* gc_finalizer_state_pop(struct gc_finalizer_state *state); 35 | 36 | GC_INTERNAL 37 | void gc_finalizer_fire(struct gc_finalizer **fired_list_loc, 38 | struct gc_finalizer *finalizer); 39 | 40 | GC_INTERNAL 41 | void gc_finalizer_state_set_callback(struct gc_finalizer_state *state, 42 | gc_finalizer_callback callback); 43 | 44 | GC_INTERNAL 45 | size_t gc_visit_finalizer_roots(struct gc_finalizer_state *state, 46 | void (*visit)(struct gc_edge edge, 47 | struct gc_heap *heap, 48 | void *visit_data), 49 | struct gc_heap *heap, 50 | void *visit_data); 51 | 52 | GC_INTERNAL 53 | size_t gc_resolve_finalizers(struct gc_finalizer_state *state, 54 | size_t priority, 55 | void (*visit)(struct gc_edge edge, 56 | struct gc_heap *heap, 57 | void *visit_data), 58 | struct gc_heap *heap, 59 | void *visit_data); 60 | 61 | GC_INTERNAL 62 | void gc_notify_finalizers(struct gc_finalizer_state *state, 63 | struct gc_heap *heap); 64 | 65 | #endif // GC_FINALIZER_INTERNAL_H 66 | -------------------------------------------------------------------------------- /whippet/src/gc-internal.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_INTERNAL_H 2 | #define GC_INTERNAL_H 3 | 4 | #ifndef GC_IMPL 5 | #error internal header file, not part of API 6 | #endif 7 | 8 | #include "embedder-api-impl.h" 9 | #include "gc-ephemeron-internal.h" 10 | #include "gc-finalizer-internal.h" 11 | #include "gc-options-internal.h" 12 | 13 | uint64_t gc_heap_total_bytes_allocated(struct gc_heap *heap); 14 | void gc_mutator_adjust_heap_size(struct gc_mutator *mut, uint64_t new_size); 15 | 16 | 17 | #endif // GC_INTERNAL_H 18 | -------------------------------------------------------------------------------- /whippet/src/gc-lock.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_LOCK_H 2 | #define GC_LOCK_H 3 | 4 | #include 5 | #include "gc-assert.h" 6 | 7 | struct gc_lock { 8 | pthread_mutex_t *lock; 9 | }; 10 | 11 | static struct gc_lock 12 | gc_lock_acquire(pthread_mutex_t *lock) { 13 | pthread_mutex_lock(lock); 14 | return (struct gc_lock){ lock }; 15 | } 16 | 17 | static void 18 | gc_lock_release(struct gc_lock *lock) { 19 | GC_ASSERT(lock->lock); 20 | pthread_mutex_unlock(lock->lock); 21 | lock->lock = NULL; 22 | } 23 | 24 | #endif // GC_LOCK_H 25 | -------------------------------------------------------------------------------- /whippet/src/gc-options-internal.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_OPTIONS_INTERNAL_H 2 | #define GC_OPTIONS_INTERNAL_H 3 | 4 | #ifndef GC_IMPL 5 | #error internal header file, not part of API 6 | #endif 7 | 8 | #include "gc-options.h" 9 | 10 | struct gc_common_options { 11 | enum gc_heap_size_policy heap_size_policy; 12 | size_t heap_size; 13 | size_t maximum_heap_size; 14 | double heap_size_multiplier; 15 | double heap_expansiveness; 16 | int parallelism; 17 | }; 18 | 19 | GC_INTERNAL void gc_init_common_options(struct gc_common_options *options); 20 | 21 | GC_INTERNAL int gc_common_option_from_string(const char *str); 22 | 23 | GC_INTERNAL int gc_common_options_set_int(struct gc_common_options *options, 24 | int option, int value); 25 | GC_INTERNAL int gc_common_options_set_size(struct gc_common_options *options, 26 | int option, size_t value); 27 | GC_INTERNAL int gc_common_options_set_double(struct gc_common_options *options, 28 | int option, double value); 29 | GC_INTERNAL int gc_common_options_parse_and_set(struct gc_common_options *options, 30 | int option, const char *value); 31 | 32 | #endif // GC_OPTIONS_INTERNAL_H 33 | -------------------------------------------------------------------------------- /whippet/src/gc-platform.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_PLATFORM_H 2 | #define GC_PLATFORM_H 3 | 4 | #ifndef GC_IMPL 5 | #error internal header file, not part of API 6 | #endif 7 | 8 | #include 9 | 10 | #include "gc-visibility.h" 11 | 12 | struct gc_heap; 13 | 14 | GC_INTERNAL void gc_platform_init(void); 15 | GC_INTERNAL uintptr_t gc_platform_current_thread_stack_base(void); 16 | GC_INTERNAL 17 | void gc_platform_visit_global_conservative_roots(void (*f)(uintptr_t start, 18 | uintptr_t end, 19 | struct gc_heap *heap, 20 | void *data), 21 | struct gc_heap *heap, 22 | void *data); 23 | GC_INTERNAL int gc_platform_processor_count(void); 24 | GC_INTERNAL uint64_t gc_platform_monotonic_nanoseconds(void); 25 | 26 | GC_INTERNAL size_t gc_platform_page_size(void); 27 | 28 | struct gc_reservation { 29 | uintptr_t base; 30 | size_t size; 31 | }; 32 | 33 | GC_INTERNAL 34 | struct gc_reservation gc_platform_reserve_memory(size_t size, size_t alignment); 35 | GC_INTERNAL 36 | void* 37 | gc_platform_acquire_memory_from_reservation(struct gc_reservation reservation, 38 | size_t offset, size_t size); 39 | GC_INTERNAL 40 | void gc_platform_release_reservation(struct gc_reservation reservation); 41 | 42 | GC_INTERNAL void* gc_platform_acquire_memory(size_t size, size_t alignment); 43 | GC_INTERNAL void gc_platform_release_memory(void *base, size_t size); 44 | 45 | GC_INTERNAL int gc_platform_populate_memory(void *addr, size_t size); 46 | GC_INTERNAL int gc_platform_discard_memory(void *addr, size_t size); 47 | 48 | #endif // GC_PLATFORM_H 49 | -------------------------------------------------------------------------------- /whippet/src/gc-stack.c: -------------------------------------------------------------------------------- 1 | // For pthread_getattr_np. 2 | #define _GNU_SOURCE 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define GC_IMPL 1 9 | 10 | #include "debug.h" 11 | #include "gc-align.h" 12 | #include "gc-assert.h" 13 | #include "gc-inline.h" 14 | #include "gc-platform.h" 15 | #include "gc-stack.h" 16 | 17 | static uintptr_t current_thread_hot_stack_addr(void) { 18 | #ifdef __GNUC__ 19 | return (uintptr_t)__builtin_frame_address(0); 20 | #else 21 | uintptr_t local; 22 | return (uintptr_t)&local; 23 | #endif 24 | } 25 | 26 | // FIXME: check platform stack growth direction. 27 | #define HOTTER_THAN <= 28 | 29 | static struct gc_stack_addr capture_current_thread_hot_stack_addr(void) { 30 | return gc_stack_addr(current_thread_hot_stack_addr()); 31 | } 32 | 33 | static struct gc_stack_addr capture_current_thread_cold_stack_addr(void) { 34 | return gc_stack_addr(gc_platform_current_thread_stack_base()); 35 | } 36 | 37 | void gc_stack_init(struct gc_stack *stack, struct gc_stack_addr base) { 38 | if (gc_stack_addr_is_empty (base)) 39 | base = capture_current_thread_cold_stack_addr(); 40 | stack->cold = stack->hot = base; 41 | } 42 | 43 | void gc_stack_capture_hot(struct gc_stack *stack) { 44 | stack->hot = capture_current_thread_hot_stack_addr(); 45 | setjmp(stack->registers); 46 | GC_ASSERT(stack->hot.addr HOTTER_THAN stack->cold.addr); 47 | } 48 | 49 | static void* call_with_stack(void* (*)(struct gc_stack_addr, void*), 50 | struct gc_stack_addr, void*) GC_NEVER_INLINE; 51 | static void* call_with_stack(void* (*f)(struct gc_stack_addr, void *), 52 | struct gc_stack_addr addr, void *arg) { 53 | return f(addr, arg); 54 | } 55 | void* gc_call_with_stack_addr(void* (*f)(struct gc_stack_addr base, 56 | void *arg), 57 | void *arg) { 58 | struct gc_stack_addr base = capture_current_thread_hot_stack_addr(); 59 | return call_with_stack(f, base, arg); 60 | } 61 | 62 | void gc_stack_visit(struct gc_stack *stack, 63 | void (*visit)(uintptr_t low, uintptr_t high, 64 | struct gc_heap *heap, void *data), 65 | struct gc_heap *heap, 66 | void *data) { 67 | { 68 | uintptr_t low = (uintptr_t)stack->registers; 69 | GC_ASSERT(low == align_down(low, sizeof(uintptr_t))); 70 | uintptr_t high = low + sizeof(jmp_buf); 71 | DEBUG("found mutator register roots for %p: [%p,%p)\n", stack, 72 | (void*)low, (void*)high); 73 | visit(low, high, heap, data); 74 | } 75 | 76 | if (0 HOTTER_THAN 1) { 77 | DEBUG("found mutator stack roots for %p: [%p,%p)\n", stack, 78 | (void*)stack->hot.addr, (void*)stack->cold.addr); 79 | visit(align_up(stack->hot.addr, sizeof(uintptr_t)), 80 | align_down(stack->cold.addr, sizeof(uintptr_t)), 81 | heap, data); 82 | } else { 83 | DEBUG("found mutator stack roots for %p: [%p,%p)\n", stack, 84 | (void*)stack->cold.addr, (void*)stack->hot.addr); 85 | visit(align_up(stack->cold.addr, sizeof(uintptr_t)), 86 | align_down(stack->hot.addr, sizeof(uintptr_t)), 87 | heap, data); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /whippet/src/gc-stack.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_STACK_H 2 | #define GC_STACK_H 3 | 4 | #ifndef GC_IMPL 5 | #error internal header file, not part of API 6 | #endif 7 | 8 | #include "gc-inline.h" 9 | #include "gc-stack-addr.h" 10 | #include 11 | 12 | struct gc_stack { 13 | struct gc_stack_addr cold; 14 | struct gc_stack_addr hot; 15 | jmp_buf registers; 16 | }; 17 | 18 | struct gc_heap; 19 | 20 | GC_INTERNAL void gc_stack_init(struct gc_stack *stack, 21 | struct gc_stack_addr base); 22 | GC_INTERNAL void gc_stack_capture_hot(struct gc_stack *stack); 23 | GC_INTERNAL void gc_stack_visit(struct gc_stack *stack, 24 | void (*visit)(uintptr_t low, uintptr_t high, 25 | struct gc_heap *heap, 26 | void *data), 27 | struct gc_heap *heap, 28 | void *data); 29 | 30 | #endif // GC_STACK_H 31 | -------------------------------------------------------------------------------- /whippet/src/gc-trace.h: -------------------------------------------------------------------------------- 1 | #ifndef GC_TRACE_H 2 | #define GC_TRACE_H 3 | 4 | #ifndef GC_IMPL 5 | #error internal header file, not part of API 6 | #endif 7 | 8 | #include "gc-config.h" 9 | #include "gc-assert.h" 10 | #include "gc-conservative-ref.h" 11 | #include "gc-embedder-api.h" 12 | 13 | static inline int gc_has_mutator_conservative_roots(void) { 14 | return GC_CONSERVATIVE_ROOTS; 15 | } 16 | static inline int gc_mutator_conservative_roots_may_be_interior(void) { 17 | return 1; 18 | } 19 | static inline int gc_has_global_conservative_roots(void) { 20 | return GC_CONSERVATIVE_ROOTS; 21 | } 22 | static inline int gc_has_conservative_intraheap_edges(void) { 23 | return GC_CONSERVATIVE_TRACE; 24 | } 25 | 26 | static inline int gc_has_conservative_roots(void) { 27 | return gc_has_mutator_conservative_roots() || 28 | gc_has_global_conservative_roots(); 29 | } 30 | 31 | enum gc_trace_kind { 32 | GC_TRACE_PRECISELY, 33 | GC_TRACE_NONE, 34 | GC_TRACE_CONSERVATIVELY, 35 | GC_TRACE_EPHEMERON, 36 | }; 37 | 38 | struct gc_trace_plan { 39 | enum gc_trace_kind kind; 40 | size_t size; // For conservative tracing. 41 | }; 42 | 43 | static inline int 44 | gc_conservative_ref_might_be_a_heap_object(struct gc_conservative_ref ref, 45 | int possibly_interior) { 46 | // Assume that the minimum page size is 4096, and that the first page 47 | // will contain no heap objects. 48 | if (gc_conservative_ref_value(ref) < 4096) 49 | return 0; 50 | if (possibly_interior) 51 | return 1; 52 | return gc_is_valid_conservative_ref_displacement 53 | (gc_conservative_ref_value(ref) & (sizeof(uintptr_t) - 1)); 54 | } 55 | 56 | #endif // GC_TRACE_H 57 | -------------------------------------------------------------------------------- /whippet/src/gc-tracepoint.c: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef GC_TRACEPOINT_LTTNG 3 | #define LTTNG_UST_TRACEPOINT_DEFINE 4 | #define LTTNG_UST_TRACEPOINT_CREATE_PROBES 5 | #include "gc-lttng.h" 6 | #endif // GC_TRACEPOINT_LTTNG 7 | -------------------------------------------------------------------------------- /whippet/src/growable-heap-sizer.h: -------------------------------------------------------------------------------- 1 | #ifndef GROWABLE_HEAP_SIZER_H 2 | #define GROWABLE_HEAP_SIZER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "assert.h" 9 | #include "heap-sizer.h" 10 | 11 | // This is a simple heap-sizing algorithm that will grow the heap if it is 12 | // smaller than a given multiplier of the live data size. It does not shrink 13 | // the heap. 14 | 15 | struct gc_growable_heap_sizer { 16 | struct gc_heap *heap; 17 | double multiplier; 18 | pthread_mutex_t lock; 19 | }; 20 | 21 | static void 22 | gc_growable_heap_sizer_set_multiplier(struct gc_growable_heap_sizer *sizer, 23 | double multiplier) { 24 | pthread_mutex_lock(&sizer->lock); 25 | sizer->multiplier = multiplier; 26 | pthread_mutex_unlock(&sizer->lock); 27 | } 28 | 29 | static void 30 | gc_growable_heap_sizer_on_gc(struct gc_growable_heap_sizer *sizer, 31 | size_t heap_size, size_t live_bytes, 32 | uint64_t pause_ns, 33 | void (*set_heap_size)(struct gc_heap*, size_t)) { 34 | pthread_mutex_lock(&sizer->lock); 35 | size_t target_size = live_bytes * sizer->multiplier; 36 | if (target_size > heap_size) 37 | set_heap_size(sizer->heap, target_size); 38 | pthread_mutex_unlock(&sizer->lock); 39 | } 40 | 41 | static struct gc_growable_heap_sizer* 42 | gc_make_growable_heap_sizer(struct gc_heap *heap, double multiplier) { 43 | struct gc_growable_heap_sizer *sizer; 44 | sizer = malloc(sizeof(*sizer)); 45 | if (!sizer) 46 | GC_CRASH(); 47 | memset(sizer, 0, sizeof(*sizer)); 48 | sizer->heap = heap; 49 | sizer->multiplier = multiplier; 50 | pthread_mutex_init(&sizer->lock, NULL); 51 | return sizer; 52 | } 53 | 54 | static void 55 | gc_destroy_growable_heap_sizer(struct gc_growable_heap_sizer *sizer) { 56 | free(sizer); 57 | } 58 | 59 | #endif // GROWABLE_HEAP_SIZER_H 60 | -------------------------------------------------------------------------------- /whippet/src/heap-sizer.h: -------------------------------------------------------------------------------- 1 | #ifndef HEAP_SIZER_H 2 | #define HEAP_SIZER_H 3 | 4 | #include "gc-api.h" 5 | 6 | #include "gc-options-internal.h" 7 | #include "growable-heap-sizer.h" 8 | #include "adaptive-heap-sizer.h" 9 | 10 | struct gc_heap_sizer { 11 | enum gc_heap_size_policy policy; 12 | union { 13 | struct gc_growable_heap_sizer* growable; 14 | struct gc_adaptive_heap_sizer* adaptive; 15 | }; 16 | }; 17 | 18 | static struct gc_heap_sizer 19 | gc_make_heap_sizer(struct gc_heap *heap, 20 | const struct gc_common_options *options, 21 | uint64_t (*get_allocation_counter_from_thread)(struct gc_heap*), 22 | void (*set_heap_size_from_thread)(struct gc_heap*, size_t), 23 | struct gc_background_thread *thread) { 24 | struct gc_heap_sizer ret = { options->heap_size_policy, }; 25 | switch (options->heap_size_policy) { 26 | case GC_HEAP_SIZE_FIXED: 27 | break; 28 | 29 | case GC_HEAP_SIZE_GROWABLE: 30 | ret.growable = 31 | gc_make_growable_heap_sizer(heap, options->heap_size_multiplier); 32 | break; 33 | 34 | case GC_HEAP_SIZE_ADAPTIVE: 35 | ret.adaptive = 36 | gc_make_adaptive_heap_sizer (heap, options->heap_expansiveness, 37 | get_allocation_counter_from_thread, 38 | set_heap_size_from_thread, 39 | thread); 40 | break; 41 | 42 | default: 43 | GC_CRASH(); 44 | } 45 | return ret; 46 | } 47 | 48 | static void 49 | gc_heap_sizer_on_gc(struct gc_heap_sizer sizer, size_t heap_size, 50 | size_t live_bytes, size_t pause_ns, 51 | void (*set_heap_size)(struct gc_heap*, size_t)) { 52 | switch (sizer.policy) { 53 | case GC_HEAP_SIZE_FIXED: 54 | break; 55 | 56 | case GC_HEAP_SIZE_GROWABLE: 57 | gc_growable_heap_sizer_on_gc(sizer.growable, heap_size, live_bytes, 58 | pause_ns, set_heap_size); 59 | break; 60 | 61 | case GC_HEAP_SIZE_ADAPTIVE: 62 | if (sizer.adaptive->background_task_id < 0) 63 | gc_adaptive_heap_sizer_background_task(sizer.adaptive); 64 | gc_adaptive_heap_sizer_on_gc(sizer.adaptive, live_bytes, pause_ns, 65 | set_heap_size); 66 | break; 67 | 68 | default: 69 | GC_CRASH(); 70 | } 71 | } 72 | 73 | 74 | #endif // HEAP_SIZER_H 75 | -------------------------------------------------------------------------------- /whippet/src/local-worklist.h: -------------------------------------------------------------------------------- 1 | #ifndef LOCAL_WORKLIST_H 2 | #define LOCAL_WORKLIST_H 3 | 4 | #include "assert.h" 5 | 6 | #define LOCAL_WORKLIST_SIZE 1024 7 | #define LOCAL_WORKLIST_MASK (LOCAL_WORKLIST_SIZE - 1) 8 | #define LOCAL_WORKLIST_SHARE_AMOUNT (LOCAL_WORKLIST_SIZE * 3 / 4) 9 | struct local_worklist { 10 | size_t read; 11 | size_t write; 12 | struct gc_ref data[LOCAL_WORKLIST_SIZE]; 13 | }; 14 | 15 | static inline void 16 | local_worklist_init(struct local_worklist *q) { 17 | q->read = q->write = 0; 18 | } 19 | static inline void 20 | local_worklist_poison(struct local_worklist *q) { 21 | q->read = 0; q->write = LOCAL_WORKLIST_SIZE; 22 | } 23 | static inline size_t 24 | local_worklist_size(struct local_worklist *q) { 25 | return q->write - q->read; 26 | } 27 | static inline int 28 | local_worklist_empty(struct local_worklist *q) { 29 | return local_worklist_size(q) == 0; 30 | } 31 | static inline int 32 | local_worklist_full(struct local_worklist *q) { 33 | return local_worklist_size(q) >= LOCAL_WORKLIST_SIZE; 34 | } 35 | static inline void 36 | local_worklist_push(struct local_worklist *q, struct gc_ref v) { 37 | ASSERT(!local_worklist_full(q)); 38 | q->data[q->write++ & LOCAL_WORKLIST_MASK] = v; 39 | } 40 | static inline struct gc_ref 41 | local_worklist_pop(struct local_worklist *q) { 42 | ASSERT(!local_worklist_empty(q)); 43 | return q->data[q->read++ & LOCAL_WORKLIST_MASK]; 44 | } 45 | 46 | static inline size_t 47 | local_worklist_pop_many(struct local_worklist *q, struct gc_ref **objv, 48 | size_t limit) { 49 | size_t avail = local_worklist_size(q); 50 | size_t read = q->read & LOCAL_WORKLIST_MASK; 51 | size_t contig = LOCAL_WORKLIST_SIZE - read; 52 | if (contig < avail) avail = contig; 53 | if (limit < avail) avail = limit; 54 | *objv = q->data + read; 55 | q->read += avail; 56 | return avail; 57 | } 58 | 59 | #endif // LOCAL_WORKLIST_H 60 | -------------------------------------------------------------------------------- /whippet/src/root-worklist.h: -------------------------------------------------------------------------------- 1 | #ifndef ROOT_WORKLIST_H 2 | #define ROOT_WORKLIST_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "assert.h" 9 | #include "debug.h" 10 | #include "gc-inline.h" 11 | #include "gc-ref.h" 12 | #include "root.h" 13 | 14 | // A single-producer, multiple-consumer worklist that has two phases: 15 | // one in which roots are added by the producer, then one in which roots 16 | // are consumed from the worklist. Roots are never added once the 17 | // consumer phase starts. 18 | struct root_worklist { 19 | size_t size; 20 | size_t read; 21 | size_t write; 22 | struct gc_root *buf; 23 | }; 24 | 25 | void 26 | root_worklist_alloc(struct root_worklist *q) { 27 | q->buf = realloc(q->buf, q->size * sizeof(struct gc_root)); 28 | if (!q->buf) { 29 | perror("Failed to grow root worklist"); 30 | GC_CRASH(); 31 | } 32 | } 33 | 34 | static void 35 | root_worklist_init(struct root_worklist *q) { 36 | q->size = 16; 37 | q->read = 0; 38 | q->write = 0; 39 | q->buf = NULL; 40 | root_worklist_alloc(q); 41 | } 42 | 43 | static inline void 44 | root_worklist_push(struct root_worklist *q, struct gc_root root) { 45 | if (UNLIKELY(q->write == q->size)) { 46 | q->size *= 2; 47 | root_worklist_alloc(q); 48 | } 49 | q->buf[q->write++] = root; 50 | } 51 | 52 | // Not atomic. 53 | static inline size_t 54 | root_worklist_size(struct root_worklist *q) { 55 | return q->write - q->read; 56 | } 57 | 58 | static inline struct gc_root 59 | root_worklist_pop(struct root_worklist *q) { 60 | size_t idx = atomic_fetch_add(&q->read, 1); 61 | if (idx < q->write) 62 | return q->buf[idx]; 63 | return (struct gc_root){ GC_ROOT_KIND_NONE, }; 64 | } 65 | 66 | static void 67 | root_worklist_reset(struct root_worklist *q) { 68 | q->read = q->write = 0; 69 | } 70 | 71 | static void 72 | root_worklist_destroy(struct root_worklist *q) { 73 | free(q->buf); 74 | } 75 | 76 | #endif // ROOT_WORKLIST_H 77 | -------------------------------------------------------------------------------- /whippet/src/root.h: -------------------------------------------------------------------------------- 1 | #ifndef ROOT_H 2 | #define ROOT_H 3 | 4 | #include "gc-edge.h" 5 | #include "extents.h" 6 | 7 | struct gc_ephemeron; 8 | struct gc_heap; 9 | struct gc_mutator; 10 | struct gc_edge_buffer; 11 | 12 | enum gc_root_kind { 13 | GC_ROOT_KIND_NONE, 14 | GC_ROOT_KIND_HEAP, 15 | GC_ROOT_KIND_MUTATOR, 16 | GC_ROOT_KIND_CONSERVATIVE_EDGES, 17 | GC_ROOT_KIND_CONSERVATIVE_POSSIBLY_INTERIOR_EDGES, 18 | GC_ROOT_KIND_RESOLVED_EPHEMERONS, 19 | GC_ROOT_KIND_EDGE, 20 | GC_ROOT_KIND_EDGE_BUFFER, 21 | }; 22 | 23 | struct gc_root { 24 | enum gc_root_kind kind; 25 | union { 26 | struct gc_heap *heap; 27 | struct gc_mutator *mutator; 28 | struct gc_ephemeron *resolved_ephemerons; 29 | struct extent_range range; 30 | struct gc_edge edge; 31 | struct gc_edge_buffer *edge_buffer; 32 | }; 33 | }; 34 | 35 | static inline struct gc_root 36 | gc_root_heap(struct gc_heap* heap) { 37 | struct gc_root ret = { GC_ROOT_KIND_HEAP }; 38 | ret.heap = heap; 39 | return ret; 40 | } 41 | 42 | static inline struct gc_root 43 | gc_root_mutator(struct gc_mutator* mutator) { 44 | struct gc_root ret = { GC_ROOT_KIND_MUTATOR }; 45 | ret.mutator = mutator; 46 | return ret; 47 | } 48 | 49 | static inline struct gc_root 50 | gc_root_conservative_edges(uintptr_t lo_addr, uintptr_t hi_addr, 51 | int possibly_interior) { 52 | enum gc_root_kind kind = possibly_interior 53 | ? GC_ROOT_KIND_CONSERVATIVE_POSSIBLY_INTERIOR_EDGES 54 | : GC_ROOT_KIND_CONSERVATIVE_EDGES; 55 | struct gc_root ret = { kind }; 56 | ret.range = (struct extent_range) {lo_addr, hi_addr}; 57 | return ret; 58 | } 59 | 60 | static inline struct gc_root 61 | gc_root_resolved_ephemerons(struct gc_ephemeron* resolved) { 62 | struct gc_root ret = { GC_ROOT_KIND_RESOLVED_EPHEMERONS }; 63 | ret.resolved_ephemerons = resolved; 64 | return ret; 65 | } 66 | 67 | static inline struct gc_root 68 | gc_root_edge(struct gc_edge edge) { 69 | struct gc_root ret = { GC_ROOT_KIND_EDGE }; 70 | ret.edge = edge; 71 | return ret; 72 | } 73 | 74 | static inline struct gc_root 75 | gc_root_edge_buffer(struct gc_edge_buffer *buf) { 76 | struct gc_root ret = { GC_ROOT_KIND_EDGE_BUFFER }; 77 | ret.edge_buffer = buf; 78 | return ret; 79 | } 80 | 81 | #endif // ROOT_H 82 | -------------------------------------------------------------------------------- /whippet/src/serial-tracer.h: -------------------------------------------------------------------------------- 1 | #ifndef SERIAL_TRACER_H 2 | #define SERIAL_TRACER_H 3 | 4 | #include 5 | #include 6 | 7 | #include "assert.h" 8 | #include "debug.h" 9 | #include "simple-worklist.h" 10 | #include "root-worklist.h" 11 | #include "tracer.h" 12 | 13 | struct gc_tracer { 14 | struct gc_heap *heap; 15 | int trace_roots_only; 16 | struct root_worklist roots; 17 | struct simple_worklist worklist; 18 | }; 19 | 20 | struct gc_trace_worker { 21 | struct gc_tracer *tracer; 22 | struct gc_trace_worker_data *data; 23 | }; 24 | 25 | static inline struct gc_trace_worker_data* 26 | gc_trace_worker_data(struct gc_trace_worker *worker) { 27 | return worker->data; 28 | } 29 | 30 | static int 31 | gc_tracer_init(struct gc_tracer *tracer, struct gc_heap *heap, 32 | size_t parallelism) { 33 | tracer->heap = heap; 34 | tracer->trace_roots_only = 0; 35 | root_worklist_init(&tracer->roots); 36 | return simple_worklist_init(&tracer->worklist); 37 | } 38 | static void gc_tracer_prepare(struct gc_tracer *tracer) {} 39 | static void gc_tracer_release(struct gc_tracer *tracer) { 40 | simple_worklist_release(&tracer->worklist); 41 | } 42 | 43 | static inline void 44 | gc_tracer_add_root(struct gc_tracer *tracer, struct gc_root root) { 45 | root_worklist_push(&tracer->roots, root); 46 | } 47 | 48 | static inline void 49 | gc_trace_worker_enqueue(struct gc_trace_worker *worker, struct gc_ref ref) { 50 | simple_worklist_push(&worker->tracer->worklist, ref); 51 | } 52 | 53 | static inline void 54 | tracer_trace_with_data(struct gc_tracer *tracer, struct gc_heap *heap, 55 | struct gc_trace_worker *worker, 56 | struct gc_trace_worker_data *data) { 57 | worker->data = data; 58 | do { 59 | struct gc_root root = root_worklist_pop(&tracer->roots); 60 | if (root.kind == GC_ROOT_KIND_NONE) 61 | break; 62 | trace_root(root, heap, worker); 63 | } while (1); 64 | root_worklist_reset(&tracer->roots); 65 | if (!tracer->trace_roots_only) { 66 | do { 67 | struct gc_ref obj = simple_worklist_pop(&tracer->worklist); 68 | if (gc_ref_is_null(obj)) 69 | break; 70 | trace_one(obj, heap, worker); 71 | } while (1); 72 | } 73 | } 74 | static inline void 75 | gc_tracer_trace(struct gc_tracer *tracer) { 76 | struct gc_trace_worker worker = { tracer }; 77 | gc_trace_worker_call_with_data(tracer_trace_with_data, tracer, tracer->heap, 78 | &worker); 79 | } 80 | 81 | static inline void 82 | gc_tracer_trace_roots(struct gc_tracer *tracer) { 83 | tracer->trace_roots_only = 1; 84 | gc_tracer_trace(tracer); 85 | tracer->trace_roots_only = 0; 86 | } 87 | 88 | #endif // SERIAL_TRACER_H 89 | -------------------------------------------------------------------------------- /whippet/src/simple-worklist.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLE_WORKLIST_H 2 | #define SIMPLE_WORKLIST_H 3 | 4 | #include "assert.h" 5 | #include "debug.h" 6 | #include "gc-inline.h" 7 | #include "gc-ref.h" 8 | #include "gc-platform.h" 9 | 10 | struct simple_worklist { 11 | size_t size; 12 | size_t read; 13 | size_t write; 14 | struct gc_ref *buf; 15 | }; 16 | 17 | static const size_t simple_worklist_max_size = 18 | (1ULL << (sizeof(struct gc_ref) * 8 - 1)) / sizeof(struct gc_ref); 19 | static const size_t simple_worklist_release_byte_threshold = 1 * 1024 * 1024; 20 | 21 | static struct gc_ref * 22 | simple_worklist_alloc(size_t size) { 23 | void *mem = gc_platform_acquire_memory(size * sizeof(struct gc_ref), 0); 24 | if (!mem) { 25 | perror("Failed to grow trace queue"); 26 | DEBUG("Failed to allocate %zu bytes", size); 27 | return NULL; 28 | } 29 | return mem; 30 | } 31 | 32 | static int 33 | simple_worklist_init(struct simple_worklist *q) { 34 | q->size = gc_platform_page_size() / sizeof(struct gc_ref); 35 | q->read = 0; 36 | q->write = 0; 37 | q->buf = simple_worklist_alloc(q->size); 38 | return !!q->buf; 39 | } 40 | 41 | static inline struct gc_ref 42 | simple_worklist_get(struct simple_worklist *q, size_t idx) { 43 | return q->buf[idx & (q->size - 1)]; 44 | } 45 | 46 | static inline void 47 | simple_worklist_put(struct simple_worklist *q, size_t idx, struct gc_ref x) { 48 | q->buf[idx & (q->size - 1)] = x; 49 | } 50 | 51 | static int simple_worklist_grow(struct simple_worklist *q) GC_NEVER_INLINE; 52 | 53 | static int 54 | simple_worklist_grow(struct simple_worklist *q) { 55 | size_t old_size = q->size; 56 | struct gc_ref *old_buf = q->buf; 57 | if (old_size >= simple_worklist_max_size) { 58 | DEBUG("trace queue already at max size of %zu bytes", old_size); 59 | return 0; 60 | } 61 | 62 | size_t new_size = old_size * 2; 63 | struct gc_ref *new_buf = simple_worklist_alloc(new_size); 64 | if (!new_buf) 65 | return 0; 66 | 67 | size_t old_mask = old_size - 1; 68 | size_t new_mask = new_size - 1; 69 | 70 | for (size_t i = q->read; i < q->write; i++) 71 | new_buf[i & new_mask] = old_buf[i & old_mask]; 72 | 73 | munmap(old_buf, old_size * sizeof(struct gc_ref)); 74 | 75 | q->size = new_size; 76 | q->buf = new_buf; 77 | return 1; 78 | } 79 | 80 | static inline void 81 | simple_worklist_push(struct simple_worklist *q, struct gc_ref p) { 82 | if (UNLIKELY(q->write - q->read == q->size)) { 83 | if (!simple_worklist_grow(q)) 84 | GC_CRASH(); 85 | } 86 | simple_worklist_put(q, q->write++, p); 87 | } 88 | 89 | static inline void 90 | simple_worklist_push_many(struct simple_worklist *q, struct gc_ref *pv, 91 | size_t count) { 92 | while (q->size - (q->write - q->read) < count) { 93 | if (!simple_worklist_grow(q)) 94 | GC_CRASH(); 95 | } 96 | for (size_t i = 0; i < count; i++) 97 | simple_worklist_put(q, q->write++, pv[i]); 98 | } 99 | 100 | static inline struct gc_ref 101 | simple_worklist_pop(struct simple_worklist *q) { 102 | if (UNLIKELY(q->read == q->write)) 103 | return gc_ref_null(); 104 | return simple_worklist_get(q, q->read++); 105 | } 106 | 107 | static void 108 | simple_worklist_release(struct simple_worklist *q) { 109 | size_t byte_size = q->size * sizeof(struct gc_ref); 110 | if (byte_size >= simple_worklist_release_byte_threshold) 111 | madvise(q->buf, byte_size, MADV_DONTNEED); 112 | q->read = q->write = 0; 113 | } 114 | 115 | static void 116 | simple_worklist_destroy(struct simple_worklist *q) { 117 | size_t byte_size = q->size * sizeof(struct gc_ref); 118 | munmap(q->buf, byte_size); 119 | } 120 | 121 | #endif // SIMPLE_WORKLIST_H 122 | -------------------------------------------------------------------------------- /whippet/src/spin.h: -------------------------------------------------------------------------------- 1 | #ifndef SPIN_H 2 | #define SPIN_H 3 | 4 | #include 5 | #include 6 | 7 | static inline void yield_for_spin(size_t spin_count) { 8 | if (spin_count < 10) 9 | __builtin_ia32_pause(); 10 | else if (spin_count < 20) 11 | sched_yield(); 12 | else if (spin_count < 40) 13 | usleep(0); 14 | else 15 | usleep(1); 16 | } 17 | 18 | #endif // SPIN_H 19 | -------------------------------------------------------------------------------- /whippet/src/swar.h: -------------------------------------------------------------------------------- 1 | #ifndef SWAR_H 2 | #define SWAR_H 3 | 4 | #include 5 | 6 | static inline size_t 7 | count_zero_bytes(uint64_t bytes) { 8 | return bytes ? (__builtin_ctzll(bytes) / 8) : sizeof(bytes); 9 | } 10 | 11 | static uint64_t 12 | broadcast_byte(uint8_t byte) { 13 | uint64_t result = byte; 14 | return result * 0x0101010101010101ULL; 15 | } 16 | 17 | static inline uint64_t 18 | load_eight_aligned_bytes(uint8_t *ptr) { 19 | GC_ASSERT(((uintptr_t)ptr & 7) == 0); 20 | uint8_t * __attribute__((aligned(8))) aligned_ptr = ptr; 21 | uint64_t word; 22 | memcpy(&word, aligned_ptr, 8); 23 | #ifdef WORDS_BIGENDIAN 24 | word = __builtin_bswap64(word); 25 | #endif 26 | return word; 27 | } 28 | 29 | static inline uint64_t 30 | match_bytes_against_bits(uint64_t bytes, uint8_t mask) { 31 | return bytes & broadcast_byte(mask); 32 | } 33 | 34 | static inline size_t 35 | scan_for_byte_with_bits(uint8_t *ptr, size_t limit, uint8_t mask) { 36 | size_t n = 0; 37 | size_t unaligned = ((uintptr_t) ptr) & 7; 38 | if (unaligned) { 39 | uint64_t bytes = load_eight_aligned_bytes(ptr - unaligned) >> (unaligned * 8); 40 | uint64_t match = match_bytes_against_bits(bytes, mask); 41 | if (match) 42 | return count_zero_bytes(match); 43 | n += 8 - unaligned; 44 | } 45 | 46 | for(; n < limit; n += 8) { 47 | uint64_t bytes = load_eight_aligned_bytes(ptr + n); 48 | uint64_t match = match_bytes_against_bits(bytes, mask); 49 | if (match) 50 | return n + count_zero_bytes(match); 51 | } 52 | 53 | return limit; 54 | } 55 | 56 | static inline uint64_t 57 | match_bytes_against_tag(uint64_t bytes, uint8_t mask, uint8_t tag) { 58 | // Precondition: tag within mask. 59 | GC_ASSERT_EQ(tag & mask, tag); 60 | // Precondition: high bit of mask byte is empty, so that we can add without 61 | // overflow. 62 | GC_ASSERT_EQ(mask & 0x7f, mask); 63 | // Precondition: mask is low bits of byte. 64 | GC_ASSERT(mask); 65 | GC_ASSERT_EQ(mask & (mask + 1), 0); 66 | 67 | uint64_t vmask = broadcast_byte(mask); 68 | uint64_t vtest = broadcast_byte(mask + 1); 69 | uint64_t vtag = broadcast_byte(tag); 70 | 71 | bytes &= vmask; 72 | uint64_t m = (bytes ^ vtag) + vmask; 73 | return (m & vtest) ^ vtest; 74 | } 75 | 76 | static inline size_t 77 | scan_for_byte_with_tag(uint8_t *ptr, size_t limit, uint8_t mask, uint8_t tag) { 78 | // The way we handle unaligned reads by padding high bytes with zeroes assumes 79 | // that all-zeroes is not a matching byte. 80 | GC_ASSERT(tag); 81 | 82 | size_t n = 0; 83 | size_t unaligned = ((uintptr_t) ptr) & 7; 84 | if (unaligned) { 85 | uint64_t bytes = load_eight_aligned_bytes(ptr - unaligned) >> (unaligned * 8); 86 | uint64_t match = match_bytes_against_tag(bytes, mask, tag); 87 | if (match) 88 | return count_zero_bytes(match); 89 | n += 8 - unaligned; 90 | } 91 | 92 | for(; n < limit; n += 8) { 93 | uint64_t bytes = load_eight_aligned_bytes(ptr + n); 94 | uint64_t match = match_bytes_against_tag(bytes, mask, tag); 95 | if (match) 96 | return n + count_zero_bytes(match); 97 | } 98 | 99 | return limit; 100 | } 101 | 102 | static inline uint64_t 103 | match_bytes_against_2_tags(uint64_t bytes, uint8_t mask, uint8_t tag1, 104 | uint8_t tag2) 105 | { 106 | // Precondition: tags are covered by within mask. 107 | GC_ASSERT_EQ(tag1 & mask, tag1); 108 | GC_ASSERT_EQ(tag2 & mask, tag2); 109 | // Precondition: high bit of mask byte is empty, so that we can add without 110 | // overflow. 111 | GC_ASSERT_EQ(mask & 0x7f, mask); 112 | // Precondition: mask is low bits of byte. 113 | GC_ASSERT(mask); 114 | GC_ASSERT_EQ(mask & (mask + 1), 0); 115 | 116 | uint64_t vmask = broadcast_byte(mask); 117 | uint64_t vtest = broadcast_byte(mask + 1); 118 | uint64_t vtag1 = broadcast_byte(tag1); 119 | uint64_t vtag2 = broadcast_byte(tag2); 120 | 121 | bytes &= vmask; 122 | uint64_t m1 = (bytes ^ vtag1) + vmask; 123 | uint64_t m2 = (bytes ^ vtag2) + vmask; 124 | return ((m1 & m2) & vtest) ^ vtest; 125 | } 126 | 127 | static inline size_t 128 | scan_for_byte_with_tags(uint8_t *ptr, size_t limit, uint8_t mask, 129 | uint8_t tag1, uint8_t tag2) { 130 | // The way we handle unaligned reads by padding high bytes with zeroes assumes 131 | // that all-zeroes is not a matching byte. 132 | GC_ASSERT(tag1 && tag2); 133 | 134 | size_t n = 0; 135 | size_t unaligned = ((uintptr_t) ptr) & 7; 136 | if (unaligned) { 137 | uint64_t bytes = load_eight_aligned_bytes(ptr - unaligned) >> (unaligned * 8); 138 | uint64_t match = match_bytes_against_2_tags(bytes, mask, tag1, tag2); 139 | if (match) 140 | return count_zero_bytes(match); 141 | n += 8 - unaligned; 142 | } 143 | 144 | for(; n < limit; n += 8) { 145 | uint64_t bytes = load_eight_aligned_bytes(ptr + n); 146 | uint64_t match = match_bytes_against_2_tags(bytes, mask, tag1, tag2); 147 | if (match) 148 | return n + count_zero_bytes(match); 149 | } 150 | 151 | return limit; 152 | } 153 | 154 | #endif // SWAR_H 155 | -------------------------------------------------------------------------------- /whippet/src/tracer.h: -------------------------------------------------------------------------------- 1 | #ifndef TRACER_H 2 | #define TRACER_H 3 | 4 | #include "gc-ref.h" 5 | #include "gc-edge.h" 6 | #include "root.h" 7 | 8 | struct gc_heap; 9 | 10 | // Data types to be implemented by tracer. 11 | struct gc_tracer; 12 | struct gc_trace_worker; 13 | // Data types to be implemented by collector. 14 | struct gc_trace_worker_data; 15 | 16 | //////////////////////////////////////////////////////////////////////// 17 | /// To be implemented by collector. 18 | //////////////////////////////////////////////////////////////////////// 19 | 20 | // Visit all fields in an object. 21 | static inline void trace_one(struct gc_ref ref, struct gc_heap *heap, 22 | struct gc_trace_worker *worker) GC_ALWAYS_INLINE; 23 | static inline void trace_root(struct gc_root root, struct gc_heap *heap, 24 | struct gc_trace_worker *worker) GC_ALWAYS_INLINE; 25 | 26 | static void 27 | gc_trace_worker_call_with_data(void (*f)(struct gc_tracer *tracer, 28 | struct gc_heap *heap, 29 | struct gc_trace_worker *worker, 30 | struct gc_trace_worker_data *data), 31 | struct gc_tracer *tracer, 32 | struct gc_heap *heap, 33 | struct gc_trace_worker *worker); 34 | 35 | //////////////////////////////////////////////////////////////////////// 36 | /// To be implemented by tracer. 37 | //////////////////////////////////////////////////////////////////////// 38 | 39 | // Initialize the tracer when the heap is created. 40 | static int gc_tracer_init(struct gc_tracer *tracer, struct gc_heap *heap, 41 | size_t parallelism); 42 | 43 | // Initialize the tracer for a new GC cycle. 44 | static void gc_tracer_prepare(struct gc_tracer *tracer); 45 | 46 | // Release any resources allocated during the trace. 47 | static void gc_tracer_release(struct gc_tracer *tracer); 48 | 49 | // Add root objects to the trace. Call before tracer_trace. 50 | static inline void gc_tracer_add_root(struct gc_tracer *tracer, 51 | struct gc_root root); 52 | 53 | // Given that an object has been shaded grey, enqueue for tracing. 54 | static inline void gc_trace_worker_enqueue(struct gc_trace_worker *worker, 55 | struct gc_ref ref) GC_ALWAYS_INLINE; 56 | static inline struct gc_trace_worker_data* 57 | gc_trace_worker_data(struct gc_trace_worker *worker) GC_ALWAYS_INLINE; 58 | 59 | // Just trace roots. 60 | static inline void gc_tracer_trace_roots(struct gc_tracer *tracer); 61 | 62 | // Run the full trace, including roots. 63 | static inline void gc_tracer_trace(struct gc_tracer *tracer); 64 | 65 | #endif // TRACER_H 66 | -------------------------------------------------------------------------------- /whippet/test/test-address-map.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "address-map.h" 4 | 5 | #define COUNT (1000 * 1000) 6 | 7 | static void add_to_other(uintptr_t addr, uintptr_t val, void *data) { 8 | struct address_map *other = data; 9 | if (addr >= COUNT) 10 | fprintf(stdout, "unexpected address: %zu\n", addr); 11 | if (address_map_contains(other, addr)) 12 | fprintf(stdout, "missing: %zu\n", addr); 13 | address_map_add(other, addr, val); 14 | } 15 | 16 | int main(int argc, char *arv[]) { 17 | struct address_map set; 18 | address_map_init(&set); 19 | for (size_t i = 0; i < COUNT; i++) 20 | address_map_add(&set, i, -i); 21 | fprintf(stdout, "after initial add, %zu/%zu\n", set.hash_map.n_items, 22 | set.hash_map.size); 23 | for (size_t i = 0; i < COUNT; i++) { 24 | if (!address_map_contains(&set, i)) { 25 | fprintf(stdout, "missing: %zu\n", i); 26 | return 1; 27 | } 28 | if (address_map_lookup(&set, i, -1) != -i) { 29 | fprintf(stdout, "missing: %zu\n", i); 30 | return 1; 31 | } 32 | } 33 | for (size_t i = COUNT; i < COUNT * 2; i++) { 34 | if (address_map_contains(&set, i)) { 35 | fprintf(stdout, "unexpectedly present: %zu\n", i); 36 | return 1; 37 | } 38 | } 39 | address_map_clear(&set); 40 | fprintf(stdout, "after clear, %zu/%zu\n", set.hash_map.n_items, 41 | set.hash_map.size); 42 | for (size_t i = 0; i < COUNT; i++) 43 | address_map_add(&set, i, 0); 44 | // Now update. 45 | fprintf(stdout, "after re-add, %zu/%zu\n", set.hash_map.n_items, 46 | set.hash_map.size); 47 | for (size_t i = 0; i < COUNT; i++) 48 | address_map_add(&set, i, i + 1); 49 | fprintf(stdout, "after idempotent re-add, %zu/%zu\n", set.hash_map.n_items, 50 | set.hash_map.size); 51 | for (size_t i = 0; i < COUNT; i++) { 52 | if (!address_map_contains(&set, i)) { 53 | fprintf(stdout, "missing: %zu\n", i); 54 | return 1; 55 | } 56 | if (address_map_lookup(&set, i, -1) != i + 1) { 57 | fprintf(stdout, "missing: %zu\n", i); 58 | return 1; 59 | } 60 | } 61 | for (size_t i = 0; i < COUNT; i++) 62 | address_map_remove(&set, i); 63 | fprintf(stdout, "after one-by-one removal, %zu/%zu\n", set.hash_map.n_items, 64 | set.hash_map.size); 65 | for (size_t i = COUNT; i < 2 * COUNT; i++) { 66 | if (address_map_contains(&set, i)) { 67 | fprintf(stdout, "unexpectedly present: %zu\n", i); 68 | return 1; 69 | } 70 | } 71 | for (size_t i = 0; i < COUNT; i++) 72 | address_map_add(&set, i, i + 2); 73 | struct address_map set2; 74 | address_map_init(&set2); 75 | address_map_for_each(&set, add_to_other, &set2); 76 | fprintf(stdout, "after for-each set, %zu/%zu\n", set2.hash_map.n_items, 77 | set2.hash_map.size); 78 | for (size_t i = 0; i < COUNT; i++) { 79 | if (address_map_lookup(&set2, i, -1) != i + 2) { 80 | fprintf(stdout, "missing: %zu\n", i); 81 | return 1; 82 | } 83 | } 84 | address_map_destroy(&set2); 85 | 86 | size_t burnin = 1000 * 1000 * 1000 / COUNT; 87 | fprintf(stdout, "beginning clear then add %zu items, %zu times\n", 88 | (size_t)COUNT, burnin); 89 | for (size_t j = 0; j < burnin; j++) { 90 | address_map_clear(&set); 91 | for (size_t i = 0; i < COUNT; i++) 92 | address_map_add(&set, i, i + 3); 93 | } 94 | fprintf(stdout, "after burnin, %zu/%zu\n", set.hash_map.n_items, 95 | set.hash_map.size); 96 | fprintf(stdout, "beginning lookup %zu items, %zu times\n", 97 | (size_t)COUNT, burnin); 98 | for (size_t j = 0; j < burnin; j++) { 99 | for (size_t i = 0; i < COUNT; i++) { 100 | if (address_map_lookup(&set, i, -1) != i + 3) { 101 | fprintf(stdout, "missing: %zu\n", i); 102 | return 1; 103 | } 104 | } 105 | } 106 | fprintf(stdout, "after burnin, %zu/%zu\n", set.hash_map.n_items, 107 | set.hash_map.size); 108 | address_map_destroy(&set); 109 | } 110 | -------------------------------------------------------------------------------- /whippet/test/test-address-set.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "address-set.h" 4 | 5 | #define COUNT (1000 * 1000) 6 | 7 | static void remove_from_other(uintptr_t addr, void *data) { 8 | struct address_set *other = data; 9 | if (addr >= COUNT) 10 | fprintf(stdout, "unexpected address: %zu\n", addr); 11 | if (!address_set_contains(other, addr)) 12 | fprintf(stdout, "missing: %zu\n", addr); 13 | address_set_remove(other, addr); 14 | } 15 | 16 | int main(int argc, char *arv[]) { 17 | struct address_set set; 18 | address_set_init(&set); 19 | for (size_t i = 0; i < COUNT; i++) 20 | address_set_add(&set, i); 21 | fprintf(stdout, "after initial add, %zu/%zu\n", set.hash_set.n_items, 22 | set.hash_set.size); 23 | for (size_t i = 0; i < COUNT; i++) { 24 | if (!address_set_contains(&set, i)) { 25 | fprintf(stdout, "missing: %zu\n", i); 26 | return 1; 27 | } 28 | } 29 | for (size_t i = COUNT; i < COUNT * 2; i++) { 30 | if (address_set_contains(&set, i)) { 31 | fprintf(stdout, "unexpectedly present: %zu\n", i); 32 | return 1; 33 | } 34 | } 35 | address_set_clear(&set); 36 | fprintf(stdout, "after clear, %zu/%zu\n", set.hash_set.n_items, 37 | set.hash_set.size); 38 | for (size_t i = 0; i < COUNT; i++) 39 | address_set_add(&set, i); 40 | // Do it twice. 41 | fprintf(stdout, "after re-add, %zu/%zu\n", set.hash_set.n_items, 42 | set.hash_set.size); 43 | for (size_t i = 0; i < COUNT; i++) 44 | address_set_add(&set, i); 45 | fprintf(stdout, "after idempotent re-add, %zu/%zu\n", set.hash_set.n_items, 46 | set.hash_set.size); 47 | for (size_t i = 0; i < COUNT; i++) { 48 | if (!address_set_contains(&set, i)) { 49 | fprintf(stdout, "missing: %zu\n", i); 50 | return 1; 51 | } 52 | } 53 | for (size_t i = 0; i < COUNT; i++) 54 | address_set_remove(&set, i); 55 | fprintf(stdout, "after one-by-one removal, %zu/%zu\n", set.hash_set.n_items, 56 | set.hash_set.size); 57 | for (size_t i = COUNT; i < 2 * COUNT; i++) { 58 | if (address_set_contains(&set, i)) { 59 | fprintf(stdout, "unexpectedly present: %zu\n", i); 60 | return 1; 61 | } 62 | } 63 | for (size_t i = 0; i < COUNT; i++) 64 | address_set_add(&set, i); 65 | struct address_set set2; 66 | address_set_init(&set2); 67 | address_set_union(&set2, &set); 68 | fprintf(stdout, "populated set2, %zu/%zu\n", set2.hash_set.n_items, 69 | set2.hash_set.size); 70 | address_set_for_each(&set, remove_from_other, &set2); 71 | fprintf(stdout, "after for-each removal, %zu/%zu\n", set2.hash_set.n_items, 72 | set2.hash_set.size); 73 | address_set_destroy(&set2); 74 | 75 | size_t burnin = 1000 * 1000 * 1000 / COUNT; 76 | fprintf(stdout, "beginning clear then add %zu items, %zu times\n", 77 | (size_t)COUNT, burnin); 78 | for (size_t j = 0; j < burnin; j++) { 79 | address_set_clear(&set); 80 | for (size_t i = 0; i < COUNT; i++) 81 | address_set_add(&set, i); 82 | } 83 | fprintf(stdout, "after burnin, %zu/%zu\n", set.hash_set.n_items, 84 | set.hash_set.size); 85 | fprintf(stdout, "beginning lookup %zu items, %zu times\n", 86 | (size_t)COUNT, burnin); 87 | for (size_t j = 0; j < burnin; j++) { 88 | for (size_t i = 0; i < COUNT; i++) { 89 | if (!address_set_contains(&set, i)) { 90 | fprintf(stdout, "missing: %zu\n", i); 91 | return 1; 92 | } 93 | } 94 | } 95 | fprintf(stdout, "after burnin, %zu/%zu\n", set.hash_set.n_items, 96 | set.hash_set.size); 97 | address_set_destroy(&set); 98 | } 99 | -------------------------------------------------------------------------------- /whippet/test/test-splay-tree.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | struct object { 8 | uintptr_t addr; 9 | size_t size; 10 | }; 11 | 12 | struct data { 13 | size_t idx; 14 | }; 15 | 16 | #define SPLAY_TREE_PREFIX object_ 17 | typedef struct object object_key_span; 18 | typedef uintptr_t object_key; 19 | typedef struct data object_value; 20 | static inline int 21 | object_compare(uintptr_t addr, struct object obj) { 22 | if (addr < obj.addr) return -1; 23 | if (addr - obj.addr < obj.size) return 0; 24 | return 1; 25 | } 26 | static inline uintptr_t 27 | object_span_start(struct object obj) { 28 | return obj.addr; 29 | } 30 | #include "splay-tree.h" 31 | 32 | // A power-law distribution. Each integer was selected by starting at 33 | // 0, taking a random number in [0,1), and then accepting the integer if 34 | // the random number was less than 0.15, or trying again with the next 35 | // integer otherwise. Useful for modelling allocation sizes or number 36 | // of garbage objects to allocate between live allocations. 37 | static const uint8_t power_law_distribution[256] = { 38 | 1, 15, 3, 12, 2, 8, 4, 0, 18, 7, 9, 8, 15, 2, 36, 5, 39 | 1, 9, 6, 11, 9, 19, 2, 0, 0, 3, 9, 6, 3, 2, 1, 1, 40 | 6, 1, 8, 4, 2, 0, 5, 3, 7, 0, 0, 3, 0, 4, 1, 7, 41 | 1, 8, 2, 2, 2, 14, 0, 7, 8, 0, 2, 1, 4, 12, 7, 5, 42 | 0, 3, 4, 13, 10, 2, 3, 7, 0, 8, 0, 23, 0, 16, 1, 1, 43 | 6, 28, 1, 18, 0, 3, 6, 5, 8, 6, 14, 5, 2, 5, 0, 11, 44 | 0, 18, 4, 16, 1, 4, 3, 13, 3, 23, 7, 4, 10, 5, 3, 13, 45 | 0, 14, 5, 5, 2, 5, 0, 16, 2, 0, 1, 1, 0, 0, 4, 2, 46 | 7, 7, 0, 5, 7, 2, 1, 24, 27, 3, 7, 1, 0, 8, 1, 4, 47 | 0, 3, 0, 7, 7, 3, 9, 2, 9, 2, 5, 10, 1, 1, 12, 6, 48 | 2, 9, 5, 0, 4, 6, 0, 7, 2, 1, 5, 4, 1, 0, 1, 15, 49 | 4, 0, 15, 4, 0, 0, 32, 18, 2, 2, 1, 7, 8, 3, 11, 1, 50 | 2, 7, 11, 1, 9, 1, 2, 6, 11, 17, 1, 2, 5, 1, 14, 3, 51 | 6, 1, 1, 15, 3, 1, 0, 6, 10, 8, 1, 3, 2, 7, 0, 1, 52 | 0, 11, 3, 3, 5, 8, 2, 0, 0, 7, 12, 2, 5, 20, 3, 7, 53 | 4, 4, 5, 22, 1, 5, 2, 7, 15, 2, 4, 6, 11, 8, 12, 1 54 | }; 55 | 56 | static size_t power_law(size_t *counter) { 57 | return power_law_distribution[(*counter)++ & 0xff]; 58 | } 59 | 60 | static uintptr_t allocate(size_t size) { 61 | void *ret = mmap(NULL, size, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 62 | if (ret == MAP_FAILED) { 63 | perror("mmap failed"); 64 | exit(1); 65 | } 66 | return (uintptr_t)ret; 67 | } 68 | 69 | static const size_t GB = 1024 * 1024 * 1024; 70 | 71 | // Page size is at least 4 kB, so we will have at most 256 * 1024 allocations. 72 | static uintptr_t all_objects[256 * 1024 + 1]; 73 | static size_t object_count; 74 | 75 | #define ASSERT(x) do { if (!(x)) abort(); } while (0) 76 | 77 | int main(int argc, char *arv[]) { 78 | struct object_tree tree; 79 | 80 | object_tree_init(&tree); 81 | 82 | size_t counter = 0; 83 | size_t page_size = getpagesize(); 84 | 85 | // Use mmap as a source of nonoverlapping spans. Allocate 1 GB of address space. 86 | size_t allocated = 0; 87 | while (allocated < 1 * GB) { 88 | size_t size = power_law(&counter) * page_size; 89 | if (!size) 90 | continue; 91 | uintptr_t addr = allocate(size); 92 | object_tree_insert(&tree, 93 | (struct object){addr, size}, 94 | (struct data){object_count}); 95 | all_objects[object_count++] = addr; 96 | ASSERT(object_count < sizeof(all_objects) / sizeof(all_objects[0])); 97 | allocated += size; 98 | } 99 | 100 | for (size_t i = 0; i < object_count; i++) 101 | ASSERT(object_tree_contains(&tree, all_objects[i])); 102 | 103 | for (size_t i = 0; i < object_count; i++) 104 | ASSERT(object_tree_lookup(&tree, all_objects[i])->value.idx == i); 105 | 106 | for (size_t i = 0; i < object_count; i++) 107 | ASSERT(object_tree_lookup(&tree, all_objects[i] + 42)->value.idx == i); 108 | 109 | for (size_t i = 0; i < object_count; i++) 110 | object_tree_remove(&tree, all_objects[i]); 111 | 112 | for (size_t i = 0; i < object_count; i++) 113 | ASSERT(!object_tree_contains(&tree, all_objects[i])); 114 | for (size_t i = 0; i < object_count; i++) 115 | ASSERT(object_tree_lookup(&tree, all_objects[i]) == NULL); 116 | } 117 | --------------------------------------------------------------------------------