From 1b7e5836ad2ae6bc5466e3b1916294d20403386c Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Wed, 21 Jan 2026 18:11:29 +0700 Subject: [PATCH] cmd/compile: speedup large init function compile time Fixes #77153 Change-Id: Ia3906e4d686281be78b65daf7a7a4fd1b2b2483d Reviewed-on: https://go-review.googlesource.com/c/go/+/737880 Reviewed-by: Keith Randall LUCI-TryBot-Result: Go LUCI Reviewed-by: Carlos Amedee Reviewed-by: Keith Randall Auto-Submit: Cuong Manh Le --- src/cmd/compile/internal/noder/reader.go | 3 ++ src/cmd/compile/internal/pkginit/init.go | 5 +- src/cmd/compile/internal/staticinit/sched.go | 52 ++++++++++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/cmd/compile/internal/noder/reader.go b/src/cmd/compile/internal/noder/reader.go index d7dd58d8ca..7f82cfc6ba 100644 --- a/src/cmd/compile/internal/noder/reader.go +++ b/src/cmd/compile/internal/noder/reader.go @@ -3342,6 +3342,9 @@ func (r *reader) pkgInitOrder(target *ir.Package) { // Outline (if legal/profitable) global map inits. staticinit.OutlineMapInits(fn) + // Split large init function. + staticinit.SplitLargeInit(fn) + target.Inits = append(target.Inits, fn) } diff --git a/src/cmd/compile/internal/pkginit/init.go b/src/cmd/compile/internal/pkginit/init.go index 04f6a5d350..9be15860a6 100644 --- a/src/cmd/compile/internal/pkginit/init.go +++ b/src/cmd/compile/internal/pkginit/init.go @@ -87,10 +87,7 @@ func MakeTask() { // Record user init functions. for _, fn := range typecheck.Target.Inits { - if fn.Sym().Name == "init" { - // Synthetic init function for initialization of package-scope - // variables. We can use staticinit to optimize away static - // assignments. + if staticinit.CanOptimize(fn) { s := staticinit.Schedule{ Plans: make(map[ir.Node]*staticinit.Plan), Temps: make(map[ir.Node]*ir.Name), diff --git a/src/cmd/compile/internal/staticinit/sched.go b/src/cmd/compile/internal/staticinit/sched.go index c79715be46..d7ad8ee1c8 100644 --- a/src/cmd/compile/internal/staticinit/sched.go +++ b/src/cmd/compile/internal/staticinit/sched.go @@ -9,6 +9,7 @@ import ( "go/constant" "go/token" "os" + "slices" "strings" "cmd/compile/internal/base" @@ -1245,3 +1246,54 @@ func OutlineMapInits(fn *ir.Func) { fmt.Fprintf(os.Stderr, "=-= outlined %v map initializations\n", outlined) } } + +const maxInitStatements = 1000 + +// SplitLargeInit breaks up a large "init" function into smaller chunks to avoid slow compilation. +func SplitLargeInit(fn *ir.Func) { + if !fn.IsPackageInit() || len(fn.Body) <= maxInitStatements { + return + } + var calls []ir.Node + for chunk := range slices.Chunk(fn.Body, maxInitStatements) { + varInitFn := generateVarInitFunc(chunk) + ir.WithFunc(fn, func() { + calls = append(calls, typecheck.Call(varInitFn.Pos(), varInitFn.Nname, nil, false)) + }) + } + fn.Body = calls +} + +// CanOptimize reports whether the given fn can be optimized for static assignments. +func CanOptimize(fn *ir.Func) bool { + name := fn.Sym().Name + return name == "init" || strings.HasPrefix(name, varInitFuncPrefix) +} + +// varInitGen is a counter used to uniquify compiler-generated functions for initializing variables. +var varInitGen int + +const varInitFuncPrefix = "init.var." + +// Create a new function that will (eventually) have this form: +// +// func init.var.%d() { +// ... +// } +func generateVarInitFunc(body []ir.Node) *ir.Func { + pos := base.AutogeneratedPos + base.Pos = pos + + sym := typecheck.LookupNum(varInitFuncPrefix, varInitGen) + varInitGen++ + + fn := ir.NewFunc(pos, pos, sym, types.NewSignature(nil, nil, nil)) + fn.SetInlinabilityChecked(true) // suppress inlining; otherwise, we end up with giant init eventually. + fn.SetWrapper(true) // less disruptive on backtraces. + typecheck.DeclFunc(fn) + + fn.Body = body + typecheck.FinishFuncBody() + + return fn +} -- 2.52.0