2.0 KiB
Defer Loop GC Integration
Background
defer chains are stored in a per-thread TLS slot so that unwind paths can locate the active *runtime.Defer. With the default allocator (AllocU) backed by Boehm GC (bdwgc), those TLS-resident pointers were invisible to the collector. In stress scenarios—e.g. TestDeferLoopStress with 1,000,000 defers—the collector reclaimed the defer nodes, leaving dangling pointers and causing crashes inside the deferred closures.
Prior experiments (test-defer-dont-free branch) confirmed the crash disappeared when allocations bypassed GC (plain malloc without free), pointing to a root-registration gap rather than logical corruption.
Solution Overview
-
GC-aware TLS slot helper (from PR #1347)
- Added
runtime/internal/clite/tls, which exposestls.Allocto create per-thread storage that is automatically registered as a Boehm GC root. SetThreadDeferdelegates to this helper so every thread reuses the same GC-safe slot without bespoke plumbing.- The package handles TLS key creation, root registration/removal, and invokes an optional destructor when a thread exits.
- Added
-
SSA codegen synchronization
ssa/eh.gonow callsruntime.SetThreadDeferwhenever it updates the TLS pointer (on first allocation and when restoring the previous link during unwind).- Defer argument nodes and the
runtime.Deferstruct itself are allocated withaggregateAllocU, ensuring new memory comes from GC-managed heaps, and nodes are released viaruntime.FreeDeferNode.
-
Non-GC builds
- The
tlshelper falls back to a malloc-backed TLS slot without GC registration, whileFreeDeferNodecontinues to release nodes viac.Freewhen building with-tags nogc.
- The
Testing
Run the stress and regression suites to validate the integration:
./llgo.sh test ./test -run TestDeferLoopStress
./llgo.sh test ./test
The updated TestDeferLoopStress now asserts 1,000,000 loop defers execute without failure, catching regressions in GC root tracking.