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 <khr@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
Auto-Submit: Cuong Manh Le <cuong.manhle.vn@gmail.com>
This commit is contained in:
Cuong Manh Le
2026-01-21 18:11:29 +07:00
committed by Gopher Robot
parent e9e05687de
commit 1b7e5836ad
3 changed files with 56 additions and 4 deletions

View File

@@ -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)
}

View File

@@ -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),

View File

@@ -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
}