1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/* Register flush for the conservative GC.
*
* A heap root can be held only in a callee-saved register at the point of
* collection. This shim writes the callee-saved registers into its own stack
* frame, then scans from below that frame up to `stack_base` (the thread's
* stack top), which covers the saved registers and every caller frame.
* Caller-saved registers need no flushing: the C ABI treats them as clobbered
* by the call into the runtime, so the compiled Nova code has already spilled
* any live root out of them before calling the allocator.
* `nova_gc_scan_range` is implemented in Rust (`gc.rs`).
*
* GCC and Clang: `setjmp` alone is not enough. glibc's x86-64 `setjmp` stores
* the saved `rbp`, `rsp` and return address scrambled, and Apple's arm64
* `setjmp` the frame pointer, link register and stack pointer, so a root held
* only in one of those would not look like an address in the saved copy.
* `__builtin_unwind_init()` makes this function save every callee-saved
* register in its own frame instead. The scan runs in a separate non-inlined
* function, so it starts below this frame and covers all of it. The empty
* `asm` after the call reads `regs`, so the call cannot become a tail call
* that pops this frame first. `setjmp` stays as a second copy: that is the
* shape CI first ran (draft PR #98). No test can force a root into a
* callee-saved register, so none discriminates this; see
* `docs/adr/0024-gc-stack-bounds-on-unix.md`.
*
* MSVC: `setjmp` into `regs`, then a scan from `regs` up.
*/
extern void ;
static void
void
void