runtime (gc_blocks.go): use a linked stack to scan marked objects

The blocks GC originally used a fixed-size stack to hold objects to scan.
When this stack overflowed, the GC would fully rescan all marked objects.
This could cause the GC to degrade to O(n^2) when scanning large linked data structures.

Instead of using a fixed-size stack, we now add a pointer field to the start of each object.
This pointer field is used to implement an unbounded linked stack.
This also consolidates the heap object scanning into one place, which simplifies the process.

This comes at the cost of introducing a pointer field to the start of the object, plus the cost of aligning the result.
This translates to:
- 16 bytes of overhead on x86/arm64 with the conservative collector
- 0 bytes of overhead on x86/arm64 with the precise collector (the layout field cost gets aligned up to 16 bytes anyway)
- 8 bytes of overhead on other 64-bit systems
- 4 bytes of overhead on 32-bit systems
- 2 bytes of overhead on AVR
This commit is contained in:
Nia Waldvogel
2025-11-29 19:58:03 -05:00
committed by Ron Evans
parent 887ac286ff
commit c9aa88b8ef
4 changed files with 106 additions and 104 deletions
+3 -3
View File
@@ -42,9 +42,9 @@ func TestBinarySize(t *testing.T) {
// This is a small number of very diverse targets that we want to test.
tests := []sizeTest{
// microcontrollers
{"hifive1b", "examples/echo", 3896, 280, 0, 2268},
{"microbit", "examples/serial", 2860, 360, 8, 2272},
{"wioterminal", "examples/pininterrupt", 7361, 1491, 116, 6912},
{"hifive1b", "examples/echo", 3756, 280, 0, 2268},
{"microbit", "examples/serial", 2756, 340, 8, 2272},
{"wioterminal", "examples/pininterrupt", 7297, 1491, 116, 6912},
// TODO: also check wasm. Right now this is difficult, because
// wasm binaries are run through wasm-opt and therefore the