// 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) { 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 { // 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) ir.CurFunc = nil // enforce no further uses of CurFunc } // 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. slices.SortFunc(compilequeue, func(a, b *ir.Func) int { return cmp.Compare(len(b.Body), len(a.Body)) }) } // By default, we perform work right away on the current goroutine // as the solo worker. queue := func(work func(int)) { work(0) } if nWorkers := base.Flag.LowerC; nWorkers > 1 { // For concurrent builds, we allow the work queue // to grow arbitrarily large, but only nWorkers work items // can be running concurrently. workq := make(chan func(int)) done := make(chan int) go func() { ids := make([]int, nWorkers) for i := range ids { ids[i] = i } var pending []func(int) for { select { case work := <-workq: pending = append(pending, work) case id := <-done: ids = append(ids, id) } for len(pending) > 0 && len(ids) > 0 { work := pending[len(pending)-1] id := ids[len(ids)-1] pending = pending[:len(pending)-1] ids = ids[:len(ids)-1] go func() { work(id) done <- id }() } } }() queue = func(work func(int)) { workq <- work } } var wg sync.WaitGroup var compile func([]*ir.Func) compile = func(fns []*ir.Func) { wg.Add(len(fns)) for _, fn := range fns { fn := fn queue(func(worker int) { ssagen.Compile(fn, worker, profile) compile(fn.Closures) wg.Done() }) } } types.CalcSizeDisabled = true // not safe to calculate sizes concurrently base.Ctxt.InParallel = true compile(compilequeue) compilequeue = nil wg.Wait() base.Ctxt.InParallel = false types.CalcSizeDisabled = false }