├── .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 | 
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 |
--------------------------------------------------------------------------------