blob: 3d8974ee299b71e52afca2e4a51d128f15b7af14 [file] [log] [blame]
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gc
import (
"cmp"
"internal/race"
"math/rand"
"slices"
"sync"
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/liveness"
"cmd/compile/internal/objw"
"cmd/compile/internal/pgoir"
"cmd/compile/internal/ssagen"
"cmd/compile/internal/staticinit"
"cmd/compile/internal/types"
"cmd/compile/internal/walk"
"cmd/internal/obj"
)
// "Portable" code generation.
var (
compilequeue []*ir.Func // functions waiting to be compiled
)
func enqueueFunc(fn *ir.Func, symABIs *ssagen.SymABIs) {
if ir.CurFunc != nil {
base.FatalfAt(fn.Pos(), "enqueueFunc %v inside %v", fn, ir.CurFunc)
}
if ir.FuncName(fn) == "_" {
// Skip compiling blank functions.
// Frontend already reported any spec-mandated errors (#29870).
return
}
if fn.IsClosure() {
return // we'll get this as part of its enclosing function
}
if ssagen.CreateWasmImportWrapper(fn) {
return
}
if len(fn.Body) == 0 {
if ir.IsIntrinsicSym(fn.Sym()) && fn.Sym().Linkname == "" && !symABIs.HasDef(fn.Sym()) {
// Generate the function body for a bodyless intrinsic, in case it
// is used in a non-call context (e.g. as a function pointer).
// We skip functions defined in assembly, or has a linkname (which
// could be defined in another package).
ssagen.GenIntrinsicBody(fn)
} else {
// Initialize ABI wrappers if necessary.
ir.InitLSym(fn, false)
types.CalcSize(fn.Type())
a := ssagen.AbiForBodylessFuncStackMap(fn)
abiInfo := a.ABIAnalyzeFuncType(fn.Type()) // abiInfo has spill/home locations for wrapper
if fn.ABI == obj.ABI0 {
// The current args_stackmap generation assumes the function
// is ABI0, and only ABI0 assembly function can have a FUNCDATA
// reference to args_stackmap (see cmd/internal/obj/plist.go:Flushplist).
// So avoid introducing an args_stackmap if the func is not ABI0.
liveness.WriteFuncMap(fn, abiInfo)
x := ssagen.EmitArgInfo(fn, abiInfo)
objw.Global(x, int32(len(x.P)), obj.RODATA|obj.LOCAL)
}
return
}
}
errorsBefore := base.Errors()
todo := []*ir.Func{fn}
for len(todo) > 0 {
next := todo[len(todo)-1]
todo = todo[:len(todo)-1]
prepareFunc(next)
todo = append(todo, next.Closures...)
}
if base.Errors() > errorsBefore {
return
}
// Enqueue just fn itself. compileFunctions will handle
// scheduling compilation of its closures after it's done.
compilequeue = append(compilequeue, fn)
}
// prepareFunc handles any remaining frontend compilation tasks that
// aren't yet safe to perform concurrently.
func prepareFunc(fn *ir.Func) {
// Set up the function's LSym early to avoid data races with the assemblers.
// Do this before walk, as walk needs the LSym to set attributes/relocations
// (e.g. in MarkTypeUsedInInterface).
ir.InitLSym(fn, true)
// If this function is a compiler-generated outlined global map
// initializer function, register its LSym for later processing.
if staticinit.MapInitToVar != nil {
if _, ok := staticinit.MapInitToVar[fn]; ok {
ssagen.RegisterMapInitLsym(fn.Linksym())
}
}
// Calculate parameter offsets.
types.CalcSize(fn.Type())
// Generate wrappers between Go ABI and Wasm ABI, for a wasmexport
// function.
// Must be done after InitLSym and CalcSize.
ssagen.GenWasmExportWrapper(fn)
ir.CurFunc = fn
walk.Walk(fn)
if ir.MatchAstDump(fn, "walk") {
ir.AstDump(fn, "walk, "+ir.FuncName(fn))
}
ir.CurFunc = nil // enforce no further uses of CurFunc
base.Ctxt.DwTextCount++
}
// compileFunctions compiles all functions in compilequeue.
// It fans out nBackendWorkers to do the work
// and waits for them to complete.
func compileFunctions(profile *pgoir.Profile) {
if race.Enabled {
// Randomize compilation order to try to shake out races.
tmp := make([]*ir.Func, len(compilequeue))
perm := rand.Perm(len(compilequeue))
for i, v := range perm {
tmp[v] = compilequeue[i]
}
copy(compilequeue, tmp)
} else {
// Compile the longest functions first,
// since they're most likely to be the slowest.
// This helps avoid stragglers.
// Since we remove from the end of the slice queue,
// that means shortest to longest.
slices.SortFunc(compilequeue, func(a, b *ir.Func) int {
return cmp.Compare(len(a.Body), len(b.Body))
})
}
var mu sync.Mutex
var wg sync.WaitGroup
mu.Lock()
for workerId := range base.Flag.LowerC {
// TODO: replace with wg.Go when the oldest bootstrap has it.
// With the current policy, that'd be go1.27.
wg.Add(1)
go func() {
defer wg.Done()
var closures []*ir.Func
for {
mu.Lock()
compilequeue = append(compilequeue, closures...)
remaining := len(compilequeue)
if remaining == 0 {
mu.Unlock()
return
}
fn := compilequeue[len(compilequeue)-1]
compilequeue = compilequeue[:len(compilequeue)-1]
mu.Unlock()
ssagen.Compile(fn, workerId, profile)
closures = fn.Closures
}
}()
}
types.CalcSizeDisabled = true // not safe to calculate sizes concurrently
base.Ctxt.InParallel = true
mu.Unlock()
wg.Wait()
compilequeue = nil
base.Ctxt.InParallel = false
types.CalcSizeDisabled = false
}