Table of Contents

Precise vs. conservative scanning

A stack scan must decide which words on a thread's stack are object references. There are two ways.

A precise scan knows. The compiler emits tables (GCInfo, in NativeAOT) describing, for each safepoint in each method, exactly which stack slots and registers hold live references. The collector reports those slots and nothing else. Precise scanning never mistakes an integer for a pointer, and it is what makes moving collectors possible, but it only works at the code locations the tables describe.

A conservative scan guesses. Every pointer-sized word on the stack is treated as a potential reference, and anything that happens to point into the heap keeps its target alive. This works at any instruction with no compiler support, but has two costs: an integer that looks like a heap address retains garbage (a false root), and no conservatively referenced object may ever move, since the word cannot safely be rewritten.

Concretely, a thread's stack walked 8 bytes at a time:

Stack word What it actually is Conservative verdict
0x0000000000000007 int 7 Not in heap range, ignored
0xFFFF8001A2B3C4D0 live List<int> reference In heap range, marked ✅ correct
0xFFFF8001DEADBEEF dead spill slot Still in range, marked ❌ false root
0xFFFFFFFF80123400 a return address into kernel code Kernel address, but outside heap segments, ignored
0xFFFF8001CAFE0000 stale callee pointer Still in range, marked ❌ false root

OrionGC uses both. The thread that triggered the collection sits on a chain of managed call sites where GCInfo is valid, so it is scanned precisely; every other thread was preempted at an arbitrary instruction and is scanned conservatively. See Precise Stack Scanning (GCInfo).