Background
#2048 releases per-compile memory in in-process drivers. Part of that fix clears the runtime-caller tracking memoization when build.Do returns:
func ClearRuntimeCallerCaches() {
runtimeCallerBaseCache.Clear()
runtimeCallerExtendedCache.Clear()
}
runtimeCallerBaseCache / runtimeCallerExtendedCache (cl/instr.go) are package-level sync.Maps keyed by *ssa.Package. Without the clear they pin every compiled package's go/types + go/ssa graphs for the life of the process (the dominant part of the cl.test 140MB→2GB climb); with it they are released per compile.
Problem
The clear is correct today because build.Do runs one compile at a time per process. If Do is ever invoked concurrently in one process (parallel test harness, a daemonized build server, a future -p-style in-process parallelism), one compile's deferred clear would drop another in-flight compile's memoization. That is still correctness-safe (the sets are recomputed on demand) but it degenerates the memoization to per-query recomputation, and the globals would again retain whatever the concurrent compiles insert until the last one finishes.
This concurrency hazard is pre-existing: package-level caches keyed by *ssa.Package were already shared mutable state across any concurrent compiles; #2048 only adds the (equally global) clear.
Proposal
Move the memoization from package globals into a compile-scoped owner so no clear is needed and concurrent compiles are isolated:
- attach the two maps to the
cl compile context (or to llssa.Program, which is already one-per-Do and now has a Dispose lifecycle), and
- delete
ClearRuntimeCallerCaches once nothing global remains.
The memoization's access pattern is per-ssa.Package within one program, so a per-Program map preserves all current hits.
Related: #2004 (follow-ups), #2048.
🤖 Generated with Claude Code
Background
#2048 releases per-compile memory in in-process drivers. Part of that fix clears the runtime-caller tracking memoization when
build.Doreturns:runtimeCallerBaseCache/runtimeCallerExtendedCache(cl/instr.go) are package-levelsync.Maps keyed by*ssa.Package. Without the clear they pin every compiled package's go/types + go/ssa graphs for the life of the process (the dominant part of the cl.test 140MB→2GB climb); with it they are released per compile.Problem
The clear is correct today because
build.Doruns one compile at a time per process. IfDois ever invoked concurrently in one process (parallel test harness, a daemonized build server, a future-p-style in-process parallelism), one compile's deferred clear would drop another in-flight compile's memoization. That is still correctness-safe (the sets are recomputed on demand) but it degenerates the memoization to per-query recomputation, and the globals would again retain whatever the concurrent compiles insert until the last one finishes.This concurrency hazard is pre-existing: package-level caches keyed by
*ssa.Packagewere already shared mutable state across any concurrent compiles; #2048 only adds the (equally global) clear.Proposal
Move the memoization from package globals into a compile-scoped owner so no clear is needed and concurrent compiles are isolated:
clcompile context (or tollssa.Program, which is already one-per-Doand now has aDisposelifecycle), andClearRuntimeCallerCachesonce nothing global remains.The memoization's access pattern is per-
ssa.Packagewithin one program, so a per-Program map preserves all current hits.Related: #2004 (follow-ups), #2048.
🤖 Generated with Claude Code