From 4398946169229d3ad223118fddf6e626b99d5003 Mon Sep 17 00:00:00 2001 From: qinchenghan Date: Fri, 4 Jul 2025 10:12:08 +0800 Subject: [PATCH] cmd/compile: enabling Continuous Feature Guided Optimization and layout optimization --- src/cmd/compile/internal/base/debug.go | 5 + src/cmd/compile/internal/base/flag.go | 12 +++ src/cmd/compile/internal/devirtualize/pgo.go | 66 ++++++++++---- src/cmd/compile/internal/gc/main.go | 20 +++- src/cmd/compile/internal/inline/inl.go | 34 ++++--- src/cmd/compile/internal/ssa/layout.go | 12 +++ src/cmd/compile/internal/ssa/layout_test.go | 39 ++++++++ src/cmd/dist/build.go | 2 +- src/cmd/go/internal/cfg/cfg.go | 1 + src/cmd/go/internal/list/list.go | 96 +++++++++++++------- src/cmd/go/internal/load/pkg.go | 75 ++++++++++++--- src/cmd/go/internal/load/test.go | 9 ++ src/cmd/go/internal/work/build.go | 1 + src/cmd/go/internal/work/exec.go | 3 + src/cmd/go/internal/work/gc.go | 3 + 15 files changed, 302 insertions(+), 76 deletions(-) create mode 100644 src/cmd/compile/internal/ssa/layout_test.go diff --git a/src/cmd/compile/internal/base/debug.go b/src/cmd/compile/internal/base/debug.go index 1f05ed9..2076855 100644 --- a/src/cmd/compile/internal/base/debug.go +++ b/src/cmd/compile/internal/base/debug.go @@ -55,6 +55,11 @@ type DebugFlags struct { PGOInlineCDFThreshold string `help:"cumulative threshold percentage for determining call sites as hot candidates for inlining" concurrent:"ok"` PGOInlineBudget int `help:"inline budget for hot functions" concurrent:"ok"` PGODevirtualize int `help:"enable profile-guided devirtualization" concurrent:"ok"` + CFGODebug int `help:"debug continuous feature guided optimizations"` + CFGOInline int `help:"enable continuous feature guided inlining: concurrent:"ok"` + CFGOInlineCDFThreshold string `help:"cumulative threshold percentage for determining call sites as hot candidates for inlining: concurrent:"ok"` + CFGOInlineBudget int `help:"inline budget for hot functions" concurrent:"ok"` + CFGODevirtualize int `help:"enable continuous feature guided devirtualization" concurrent:"ok"` WrapGlobalMapDbg int `help:"debug trace output for global map init wrapping"` WrapGlobalMapCtl int `help:"global map init wrap control (0 => default, 1 => off, 2 => stress mode, no size cutoff)"` diff --git a/src/cmd/compile/internal/base/flag.go b/src/cmd/compile/internal/base/flag.go index 753a60a..e4e3f64 100644 --- a/src/cmd/compile/internal/base/flag.go +++ b/src/cmd/compile/internal/base/flag.go @@ -124,6 +124,7 @@ type CmdFlags struct { TrimPath string "help:\"remove `prefix` from recorded source file paths\"" WB bool "help:\"enable write barrier\"" // TODO: remove PgoProfile string "help:\"read profile from `file`\"" + CfgoProfile string "help:\"read profile from `file`\"" ErrorURL bool "help:\"print explanatory URL with error message if applicable\"" // Configuration derived from flags; not a flag itself. @@ -143,6 +144,15 @@ type CmdFlags struct { } } +var ENABLE_CFGO = true + +func CFGOSwitch() (*int, *int, *string, *int, *int) { + if ENABLE_CFGO { + return &Debug.CFGODebug, &Debug.CFGOInline, &Debug.CFGOInlineCDFThreshold, &Debug.CFGOInlineBudget, &Debug.CFGODevirtualize + } + return &Debug.PGODebug, &Debug.PGOInline, &Debug.PGOInlineCDFThreshold, &Debug.PGOInlineBudget, &Debug.PGODevirtualize +} + // ParseFlags parses the command-line flags into Flag. func ParseFlags() { Flag.I = addImportDir @@ -170,6 +180,8 @@ func ParseFlags() { Debug.InlStaticInit = 1 Debug.PGOInline = 1 Debug.PGODevirtualize = 1 + Debug.CFGOInline = 1 + Debug.CFGODevirtualize = 1 Debug.SyncFrames = -1 // disable sync markers by default Debug.Checkptr = -1 // so we can tell whether it is set explicitly diff --git a/src/cmd/compile/internal/devirtualize/pgo.go b/src/cmd/compile/internal/devirtualize/pgo.go index 068e0ef..4fed8d8 100644 --- a/src/cmd/compile/internal/devirtualize/pgo.go +++ b/src/cmd/compile/internal/devirtualize/pgo.go @@ -81,6 +81,13 @@ type CallStat struct { // The primary benefit of this transformation is enabling inlining of the // direct call. func ProfileGuided(fn *ir.Func, p *pgo.Profile) { + debug, _, _, _, _ := base.CFGOSwitch() + var pgoStr string + if base.ENABLE_CFGO { + pgoStr = "CFGO" + } else { + pgoStr = "PGO" + } ir.CurFunc = fn name := ir.LinkFuncName(fn) @@ -89,7 +96,7 @@ func ProfileGuided(fn *ir.Func, p *pgo.Profile) { goDeferCall := make(map[*ir.CallExpr]bool) var jsonW *json.Encoder - if base.Debug.PGODebug >= 3 { + if *debug >= 3 { jsonW = json.NewEncoder(os.Stdout) } @@ -113,7 +120,7 @@ func ProfileGuided(fn *ir.Func, p *pgo.Profile) { } var stat *CallStat - if base.Debug.PGODebug >= 3 { + if *debug >= 3 { // Statistics about every single call. Handy for external data analysis. // // TODO(prattmic): Log via logopt? @@ -129,13 +136,13 @@ func ProfileGuided(fn *ir.Func, p *pgo.Profile) { return n } - if base.Debug.PGODebug >= 2 { - fmt.Printf("%v: PGO devirtualize considering call %v\n", ir.Line(call), call) + if *debug >= 2 { + fmt.Printf("%v: %s devirtualize considering call %v\n", ir.Line(call), pgoStr, call) } if goDeferCall[call] { - if base.Debug.PGODebug >= 2 { - fmt.Printf("%v: can't PGO devirtualize go/defer call %v\n", ir.Line(call), call) + if *debug >= 2 { + fmt.Printf("%v: can't %s devirtualize go/defer call %v\n", ir.Line(call), pgoStr, call) } return n } @@ -176,11 +183,17 @@ func shouldPGODevirt(fn *ir.Func) bool { if base.Flag.LowerM > 1 || logopt.Enabled() { defer func() { if reason != "" { + var pgoStr string + if base.ENABLE_CFGO { + pgoStr = "CFGO" + } else { + pgoStr = "PGO" + } if base.Flag.LowerM > 1 { - fmt.Printf("%v: should not PGO devirtualize %v: %s\n", ir.Line(fn), ir.FuncName(fn), reason) + fmt.Printf("%v: should not %s devirtualize %v: %s\n", pgoStr, ir.Line(fn), ir.FuncName(fn), reason) } if logopt.Enabled() { - logopt.LogOpt(fn.Pos(), ": should not PGO devirtualize function", "pgo-devirtualize", ir.FuncName(fn), reason) + logopt.LogOpt(fn.Pos(), ": should not %s devirtualize function", "cfgo-devirtualize", pgoStr, ir.FuncName(fn), reason) } } }() @@ -266,7 +279,13 @@ func constructCallStat(p *pgo.Profile, fn *ir.Func, name string, call *ir.CallEx // concretetyp. func rewriteCondCall(call *ir.CallExpr, curfn, callee *ir.Func, concretetyp *types.Type) ir.Node { if base.Flag.LowerM != 0 { - fmt.Printf("%v: PGO devirtualizing %v to %v\n", ir.Line(call), call.X, callee) + var pgoStr string + if base.ENABLE_CFGO { + pgoStr = "CFGO" + } else { + pgoStr = "PGO" + } + fmt.Printf("%v: %s devirtualizing %v to %v\n", ir.Line(call), pgoStr, call.X, callee) } // We generate an OINCALL of: @@ -379,9 +398,17 @@ func rewriteCondCall(call *ir.CallExpr, curfn, callee *ir.Func, concretetyp *typ res := ir.NewInlinedCallExpr(pos, body, retvars) res.SetType(call.Type()) res.SetTypecheck(1) + + debug, _, _, _, _ := base.CFGOSwitch() - if base.Debug.PGODebug >= 3 { - fmt.Printf("PGO devirtualizing call to %+v. After: %+v\n", concretetyp, res) + if *debug >= 3 { + var pgoStr string + if base.ENABLE_CFGO { + pgoStr = "CFGO" + } else { + pgoStr = "PGO" + } + fmt.Printf("%s devirtualizing call to %+v. After: %+v\n", pgoStr, concretetyp, res) } return res @@ -415,6 +442,7 @@ func interfaceCallRecvTypeAndMethod(call *ir.CallExpr) (*types.Type, *types.Sym) // findHotConcreteCallee returns the *ir.Func of the hottest callee of an // indirect call, if available, and its edge weight. func findHotConcreteCallee(p *pgo.Profile, caller *ir.Func, call *ir.CallExpr) (*ir.Func, int64) { + debug, _, _, _, _ := base.CFGOSwitch() callerName := ir.LinkFuncName(caller) callerNode := p.WeightedCG.IRNodes[callerName] callOffset := pgo.NodeLineOffset(call, caller) @@ -461,7 +489,7 @@ func findHotConcreteCallee(p *pgo.Profile, caller *ir.Func, call *ir.CallExpr) ( // maybe don't devirtualize? Similarly, if this is call // is globally very cold, there is not much value in // devirtualizing. - if base.Debug.PGODebug >= 2 { + if *debug >= 2 { fmt.Printf("%v: edge %s:%d -> %s (weight %d): too cold (hottest %d)\n", ir.Line(call), callerName, callOffset, e.Dst.Name(), e.Weight, hottest.Weight) } continue @@ -477,7 +505,7 @@ func findHotConcreteCallee(p *pgo.Profile, caller *ir.Func, call *ir.CallExpr) ( // because we only want to return the #1 hottest // callee. If we skip this then we'd return the #2 // hottest callee. - if base.Debug.PGODebug >= 2 { + if *debug >= 2 { fmt.Printf("%v: edge %s:%d -> %s (weight %d) (missing IR): hottest so far\n", ir.Line(call), callerName, callOffset, e.Dst.Name(), e.Weight) } hottest = e @@ -488,7 +516,7 @@ func findHotConcreteCallee(p *pgo.Profile, caller *ir.Func, call *ir.CallExpr) ( if ctyp == nil { // Not a method. // TODO(prattmic): Support non-interface indirect calls. - if base.Debug.PGODebug >= 2 { + if *debug >= 2 { fmt.Printf("%v: edge %s:%d -> %s (weight %d): callee not a method\n", ir.Line(call), callerName, callOffset, e.Dst.Name(), e.Weight) } continue @@ -506,7 +534,7 @@ func findHotConcreteCallee(p *pgo.Profile, caller *ir.Func, call *ir.CallExpr) ( // What we'd need to do is check that the function // pointer in the itab matches the method we want, // rather than doing a full type assertion. - if base.Debug.PGODebug >= 2 { + if *debug >= 2 { why := typecheck.ImplementsExplain(ctyp, inter) fmt.Printf("%v: edge %s:%d -> %s (weight %d): %v doesn't implement %v (%s)\n", ir.Line(call), callerName, callOffset, e.Dst.Name(), e.Weight, ctyp, inter, why) } @@ -516,26 +544,26 @@ func findHotConcreteCallee(p *pgo.Profile, caller *ir.Func, call *ir.CallExpr) ( // If the method name is different it is most likely from a // different call on the same line if !strings.HasSuffix(e.Dst.Name(), "."+method.Name) { - if base.Debug.PGODebug >= 2 { + if *debug >= 2 { fmt.Printf("%v: edge %s:%d -> %s (weight %d): callee is a different method\n", ir.Line(call), callerName, callOffset, e.Dst.Name(), e.Weight) } continue } - if base.Debug.PGODebug >= 2 { + if *debug >= 2 { fmt.Printf("%v: edge %s:%d -> %s (weight %d): hottest so far\n", ir.Line(call), callerName, callOffset, e.Dst.Name(), e.Weight) } hottest = e } if hottest == nil { - if base.Debug.PGODebug >= 2 { + if *debug >= 2 { fmt.Printf("%v: call %s:%d: no hot callee\n", ir.Line(call), callerName, callOffset) } return nil, 0 } - if base.Debug.PGODebug >= 2 { + if *debug >= 2 { fmt.Printf("%v call %s:%d: hottest callee %s (weight %d)\n", ir.Line(call), callerName, callOffset, hottest.Dst.Name(), hottest.Weight) } return hottest.Dst.AST, hottest.Weight diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index 937d1c4..99a1501 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -254,17 +254,35 @@ func Main(archInit func(*ssagen.ArchInfo)) { // Read profile file and build profile-graph and weighted-call-graph. base.Timer.Start("fe", "pgo-load-profile") + base.Timer.Start("fe", "cfgo-load-profile") var profile *pgo.Profile if base.Flag.PgoProfile != "" { var err error + base.ENABLE_CFGO = false profile, err = pgo.New(base.Flag.PgoProfile) if err != nil { log.Fatalf("%s: PGO error: %v", base.Flag.PgoProfile, err) } + } else if base.Flag.CfgoProfile != "" { + result := os.Getenv("KP_AI_OPT") + if result == "1" { + base.ENABLE_CFGO = true + var err error + profile, err = pgo.New(base.Flag.CfgoProfile) + if err != nil { + log.Fatalf("%s: CFGO error: %v", base.Flag.CfgoProfile, err) + } + } else { + base.ENABLE_CFGO = false + } + } else { + base.ENABLE_CFGO = false } + _, _, _, _, godevirtualize := base.CFGOSwitch() base.Timer.Start("fe", "pgo-devirtualization") - if profile != nil && base.Debug.PGODevirtualize > 0 { + base.Timer.Start("fe", "cfgo-devirtualization") + if profile != nil && *godevirtualize > 0 { // TODO(prattmic): No need to use bottom-up visit order. This // is mirroring the PGO IRGraph visit order, which also need // not be bottom-up. diff --git a/src/cmd/compile/internal/inline/inl.go b/src/cmd/compile/internal/inline/inl.go index 4ae7fa9..2535441 100644 --- a/src/cmd/compile/internal/inline/inl.go +++ b/src/cmd/compile/internal/inline/inl.go @@ -78,20 +78,27 @@ var ( // pgoInlinePrologue records the hot callsites from ir-graph. func pgoInlinePrologue(p *pgo.Profile, decls []ir.Node) { - if base.Debug.PGOInlineCDFThreshold != "" { - if s, err := strconv.ParseFloat(base.Debug.PGOInlineCDFThreshold, 64); err == nil && s >= 0 && s <= 100 { + debug, _, inlineCDFThreshold, inlineBudget, _ := base.CFGOSwitch() + if *inlineCDFThreshold != "" { + if s, err := strconv.ParseFloat(*inlineCDFThreshold, 64); err == nil && s >= 0 && s <= 100 { inlineCDFHotCallSiteThresholdPercent = s } else { - base.Fatalf("invalid PGOInlineCDFThreshold, must be between 0 and 100") + var pgoStr string + if base.ENABLE_CFGO { + pgoStr = "CFGO" + } else { + pgoStr = "PGO" + } + base.Fatalf("invalid %sInlineCDFThreshold, must be between 0 and 100\n", pgoStr) } } var hotCallsites []pgo.NodeMapKey inlineHotCallSiteThresholdPercent, hotCallsites = hotNodesFromCDF(p) - if base.Debug.PGODebug > 0 { + if *debug > 0 { fmt.Printf("hot-callsite-thres-from-CDF=%v\n", inlineHotCallSiteThresholdPercent) } - if x := base.Debug.PGOInlineBudget; x != 0 { + if x := *inlineBudget; x != 0 { inlineHotMaxBudget = int32(x) } @@ -107,7 +114,7 @@ func pgoInlinePrologue(p *pgo.Profile, decls []ir.Node) { } } - if base.Debug.PGODebug >= 3 { + if *debug >= 3 { fmt.Printf("hot-cg before inline in dot format:") p.PrintWeightedCallGraphDOT(inlineHotCallSiteThresholdPercent) } @@ -156,7 +163,8 @@ func hotNodesFromCDF(p *pgo.Profile) (float64, []pgo.NodeMapKey) { // InlinePackage finds functions that can be inlined and clones them before walk expands them. func InlinePackage(p *pgo.Profile) { - if base.Debug.PGOInline == 0 { + _, inline, _, _, _ := base.CFGOSwitch() + if *inline == 0 { p = nil } @@ -308,7 +316,8 @@ func CanInline(fn *ir.Func, profile *pgo.Profile) { if n, ok := profile.WeightedCG.IRNodes[ir.LinkFuncName(fn)]; ok { if _, ok := candHotCalleeMap[n]; ok { budget = int32(inlineHotMaxBudget) - if base.Debug.PGODebug > 0 { + debug, _, _, _, _ := base.CFGOSwitch() + if *debug > 0 { fmt.Printf("hot-node enabled increased budget=%v for func=%v\n", budget, ir.PkgFuncName(fn)) } } @@ -577,7 +586,8 @@ func (v *hairyVisitor) doNode(n ir.Node) bool { lineOffset := pgo.NodeLineOffset(n, fn) csi := pgo.CallSiteInfo{LineOffset: lineOffset, Caller: v.curFunc} if _, o := candHotEdgeMap[csi]; o { - if base.Debug.PGODebug > 0 { + debug, _, _, _, _ := base.CFGOSwitch() + if *debug > 0 { fmt.Printf("hot-callsite identified at line=%v for func=%v\n", ir.Line(n), ir.PkgFuncName(v.curFunc)) } } @@ -1009,9 +1019,9 @@ func inlineCostOK(n *ir.CallExpr, caller, callee *ir.Func, bigCaller bool) (bool } // Hot - + debug, _, _, _, _ := base.CFGOSwitch() if bigCaller { - if base.Debug.PGODebug > 0 { + if *debug > 0 { fmt.Printf("hot-big check disallows inlining for call %s (cost %d) at %v in big function %s\n", ir.PkgFuncName(callee), callee.Inl.Cost, ir.Line(n), ir.PkgFuncName(caller)) } return false, maxCost @@ -1021,7 +1031,7 @@ func inlineCostOK(n *ir.CallExpr, caller, callee *ir.Func, bigCaller bool) (bool return false, inlineHotMaxBudget } - if base.Debug.PGODebug > 0 { + if *debug > 0 { fmt.Printf("hot-budget check allows inlining for call %s (cost %d) at %v in function %s\n", ir.PkgFuncName(callee), callee.Inl.Cost, ir.Line(n), ir.PkgFuncName(caller)) } diff --git a/src/cmd/compile/internal/ssa/layout.go b/src/cmd/compile/internal/ssa/layout.go index e4a8c6f..492ca53 100644 --- a/src/cmd/compile/internal/ssa/layout.go +++ b/src/cmd/compile/internal/ssa/layout.go @@ -36,6 +36,7 @@ func layoutOrder(f *Func) []*Block { // scheduled block. var succs []ID exit := f.newSparseSet(f.NumBlocks()) // exit blocks + likelyPath := false defer f.retSparseSet(exit) // Populate idToBlock and find exit blocks. @@ -131,10 +132,21 @@ blockloop: likely = b.Succs[1].b } if likely != nil && !scheduled[likely.ID] { + likelyPath = true bid = likely.ID continue } + if likelyPath && len(b.Succs) == 1 { + likelyBlock := b.Succs[0].b + if !scheduled[likelyBlock.ID] { + bid = likelyBlock.ID + continue blockloop + } + } + + likelyPath = false + // Use degree for now. bid = 0 // TODO: improve this part diff --git a/src/cmd/compile/internal/ssa/layout_test.go b/src/cmd/compile/internal/ssa/layout_test.go new file mode 100644 index 0000000..d5cbd15 --- /dev/null +++ b/src/cmd/compile/internal/ssa/layout_test.go @@ -0,0 +1,39 @@ +// Copyright 2025 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 ssa + +import ( + "cmd/compile/internal/types" + "testing" +) + +func TestLayoutPredicatedBranch(t *testing.T) { + c := testConfig(t) + fun := c.Fun("entry", + Bloc("entry", + Valu("mem", OpInitMem, types.TypeMem, 0, nil), + Valu("branch", OpConstBool, types.Types[types.TBOOL], 1, nil), + If("branch", "likely", "unlikely")), + Bloc("likely", + Goto("successor")), + Bloc("unlikely", + Goto("end")), + Bloc("successor", + Goto("end")), + Bloc("end", + Exit("mem")), + ) + fun.blocks["entry"].Likely = BranchLikely + expectedOrder := [5]ID{1, 2, 4, 5, 3} + CheckFunc(fun.f) + layout(fun.f) + CheckFunc(fun.f) + + for i, b := range fun.f.Blocks { + if b.ID != expectedOrder[i] { + t.Errof("block layout order want %d, got %d", expectedOrder[i], b.ID) + } + } +} diff --git a/src/cmd/dist/build.go b/src/cmd/dist/build.go index 8973a87..7c005ad 100644 --- a/src/cmd/dist/build.go +++ b/src/cmd/dist/build.go @@ -1472,7 +1472,7 @@ func cmdbootstrap() { // Now that cmd/go is in charge of the build process, enable GOEXPERIMENT. os.Setenv("GOEXPERIMENT", goexperiment) // No need to enable PGO for toolchain2. - goInstall(toolenv(), goBootstrap, append([]string{"-pgo=off"}, toolchain...)...) + goInstall(toolenv(), goBootstrap, append([]string{"-pgo=off", "-cfgo=off"}, toolchain...)...) if debug { run("", ShowOutput|CheckExit, pathf("%s/compile", tooldir), "-V=full") copyfile(pathf("%s/compile2", tooldir), pathf("%s/compile", tooldir), writeExec) diff --git a/src/cmd/go/internal/cfg/cfg.go b/src/cmd/go/internal/cfg/cfg.go index 8caa22a..8d9f83a 100644 --- a/src/cmd/go/internal/cfg/cfg.go +++ b/src/cmd/go/internal/cfg/cfg.go @@ -82,6 +82,7 @@ var ( BuildO string // -o flag BuildP = runtime.GOMAXPROCS(0) // -p flag BuildPGO string // -pgo flag + BuildCFGO string // -cfgo flag BuildPkgdir string // -pkgdir flag BuildRace bool // -race flag BuildToolexec []string // -toolexec flag diff --git a/src/cmd/go/internal/list/list.go b/src/cmd/go/internal/list/list.go index 92020da..0584a97 100644 --- a/src/cmd/go/internal/list/list.go +++ b/src/cmd/go/internal/list/list.go @@ -747,41 +747,75 @@ func runList(ctx context.Context, cmd *base.Command, args []string) { } } - if *listTest || (cfg.BuildPGO == "auto" && len(cmdline) > 1) { - all := pkgs - if !*listDeps { - all = loadPackageList(pkgs) - } - // Update import paths to distinguish the real package p - // from p recompiled for q.test, or to distinguish between - // p compiled with different PGO profiles. - // This must happen only once the build code is done - // looking at import paths, because it will get very confused - // if it sees these. - old := make(map[string]string) - for _, p := range all { - if p.ForTest != "" || p.Internal.ForMain != "" { - new := p.Desc() - old[new] = p.ImportPath - p.ImportPath = new - } - p.DepOnly = !cmdline[p] - } - // Update import path lists to use new strings. - m := make(map[string]string) - for _, p := range all { - for _, p1 := range p.Internal.Imports { - if p1.ForTest != "" || p1.Internal.ForMain != "" { - m[old[p1.ImportPath]] = p1.ImportPath + if load.ENABLE_CFGO { + if *listTest || (cfg.BuildCFGO == "auto" && len(cmdline) > 1) { + all := pkgs + if !*listDeps { + all = loadPackageList(pkgs) + } + old := make(map[string]string) + for _, p := range all { + if p.ForTest != "" || p.Internal.ForMain != "" { + new := p.Desc() + old[new] = p.ImportPath + p.ImportPath = new } + p.DepOnly = !cmdline[p] } - for i, old := range p.Imports { - if new := m[old]; new != "" { - p.Imports[i] = new + m := make(map[string]string) + for _, p := range all { + for _, p1 := range p.Internal.Imports { + if p1.ForTest != "" || p1.Internal.ForMain != "" { + m[old[p1.ImportPath]] = p1.ImportPath + } + } + for i, old := range p.Imports { + if new := m[old]; new != "" { + p.Imports[i] = new + } + } + for old := range m { + delete(m, old) } } - for old := range m { - delete(m, old) + } + } else { + if *listTest || (cfg.BuildPGO == "auto" && len(cmdline) > 1) { + all := pkgs + if !*listDeps { + all = loadPackageList(pkgs) + } + // Update import paths to distinguish the real package p + // from p recompiled for q.test, or to distinguish between + // p compiled with different PGO profiles. + // This must happen only once the build code is done + // looking at import paths, because it will get very confused + // if it sees these. + old := make(map[string]string) + for _, p := range all { + if p.ForTest != "" || p.Internal.ForMain != "" { + new := p.Desc() + old[new] = p.ImportPath + p.ImportPath = new + } + p.DepOnly = !cmdline[p] + } + // Update import path lists to use new strings. + m := make(map[string]string) + for _, p := range all { + for _, p1 := range p.Internal.Imports { + if p1.ForTest != "" || p1.Internal.ForMain != "" { + m[old[p1.ImportPath]] = p1.ImportPath + } + } + for i, old := range p.Imports { + if new := m[old]; new != "" { + p.Imports[i] = new + } + } + for old := range m { + delete(m, old) + } } } } diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go index c0e6265..2bcb586 100644 --- a/src/cmd/go/internal/load/pkg.go +++ b/src/cmd/go/internal/load/pkg.go @@ -51,6 +51,8 @@ import ( "golang.org/x/mod/module" ) +var ENABLE_CFGO = true + // A Package describes a single package found in a directory. type Package struct { PackagePublic // visible in 'go list' @@ -238,6 +240,7 @@ type PackageInternal struct { Embed map[string][]string // //go:embed comment mapping OrigImportPath string // original import path before adding '_test' suffix PGOProfile string // path to PGO profile + CFGOProfile string // path to CFGO profile ForMain string // the main package if this package is built specifically for it Asmflags []string // -asmflags for this package @@ -2925,14 +2928,42 @@ func setPGOProfilePath(pkgs []*Package) { return } - if cfg.BuildTrimpath { - appendBuildSetting(p.Internal.BuildInfo, "-pgo", filepath.Base(file)) + if ENABLE_CFGO { + if cfg.BuildTrimpath { + appendBuildSetting(p.Internal.BuildInfo, "-cfgo", filepath.Base(file)) + } else { + appendBuildSetting(p.Internal.BuildInfo, "-cfgo", file) + } } else { - appendBuildSetting(p.Internal.BuildInfo, "-pgo", file) + if cfg.BuildTrimpath { + appendBuildSetting(p.Internal.BuildInfo, "-pgo", filepath.Base(file)) + } else { + appendBuildSetting(p.Internal.BuildInfo, "-pgo", file) + } } } - switch cfg.BuildPGO { + if cfg.BuildCFGO != "off" && cfg.BuildCFGO != "auto" { + cfg.BuildPGO = "off" + } else if cfg.BuildCFGO == "off" { + ENABLE_CFGO = false + } else if cfg.BuildCFGO == "auto" && cfg.BuildPGO != "off" { + cfg.BuildCFGO = "off" + ENABLE_CFGO = false + } + + var build string + var pgoStr string + + if ENABLE_CFGO { + build = cfg.BuildCFGO + pgoStr = "CFGO" + } else { + build = cfg.BuildPGO + pgoStr = "PGO" + } + + switch build { case "off": return @@ -2969,8 +3000,14 @@ func setPGOProfilePath(pkgs []*Package) { // No need to copy if there is only one root package (we can // attach profile directly in-place). // Also no need to copy the main package. - if p.Internal.PGOProfile != "" { - panic("setPGOProfilePath: already have profile") + if ENABLE_CFGO { + if p.Internal.CFGOProfile != "" { + panic("setPGOProfilePath: already have profile") + } + } else { + if p.Internal.PGOProfile != "" { + panic("setPGOProfilePath: already have profile") + } } p1 := new(Package) *p1 = *p @@ -2983,7 +3020,11 @@ func setPGOProfilePath(pkgs []*Package) { } else { visited[p] = p } - p.Internal.PGOProfile = file + if ENABLE_CFGO { + p.Internal.CFGOProfile = file + } else { + p.Internal.PGOProfile = file + } updateBuildInfo(p, file) // Recurse to dependencies. for i, pp := range p.Internal.Imports { @@ -2999,13 +3040,17 @@ func setPGOProfilePath(pkgs []*Package) { default: // Profile specified from the command line. // Make it absolute path, as the compiler runs on various directories. - file, err := filepath.Abs(cfg.BuildPGO) + file, err := filepath.Abs(build) if err != nil { - base.Fatalf("fail to get absolute path of PGO file %s: %v", cfg.BuildPGO, err) + base.Fatalf("fail to get absolute path of %s file %s: %v", pgoStr, build, err) } for _, p := range PackageList(pkgs) { - p.Internal.PGOProfile = file + if ENABLE_CFGO { + p.Internal.CFGOProfile = file + } else { + p.Internal.PGOProfile = file + } updateBuildInfo(p, file) } } @@ -3042,8 +3087,14 @@ func CheckPackageErrors(pkgs []*Package) { // built multiple times (with different profiles). // We check that package import path + profile path is unique. key := pkg.ImportPath - if pkg.Internal.PGOProfile != "" { - key += " pgo:" + pkg.Internal.PGOProfile + if ENABLE_CFGO { + if pkg.Internal.CFGOProfile != "" { + key += " cfgo:" + pkg.Internal.CFGOProfile + } + } else { + if pkg.Internal.PGOProfile != "" { + key += " pgo:" + pkg.Internal.PGOProfile + } } if seen[key] && !reported[key] { reported[key] = true diff --git a/src/cmd/go/internal/load/test.go b/src/cmd/go/internal/load/test.go index e9ed0d3..4065f3e 100644 --- a/src/cmd/go/internal/load/test.go +++ b/src/cmd/go/internal/load/test.go @@ -219,6 +219,7 @@ func TestPackagesAndErrors(ctx context.Context, done func(), opts PackageOpts, p ptest.EmbedFiles = str.StringList(p.EmbedFiles, p.TestEmbedFiles) ptest.Internal.OrigImportPath = p.Internal.OrigImportPath ptest.Internal.PGOProfile = p.Internal.PGOProfile + ptest.Internal.CFGOProfile = p.Internal.CFGOProfile ptest.Internal.Build.Directives = append(slices.Clip(p.Internal.Build.Directives), p.Internal.Build.TestDirectives...) } else { ptest = p @@ -257,6 +258,7 @@ func TestPackagesAndErrors(ctx context.Context, done func(), opts PackageOpts, p Embed: xtestEmbed, OrigImportPath: p.Internal.OrigImportPath, PGOProfile: p.Internal.PGOProfile, + CFGOProfile: p.Internal.CFGOProfile, }, } if pxtestNeedsPtest { @@ -288,6 +290,7 @@ func TestPackagesAndErrors(ctx context.Context, done func(), opts PackageOpts, p Gccgoflags: gccgoflags, OrigImportPath: p.Internal.OrigImportPath, PGOProfile: p.Internal.PGOProfile, + CFGOProfile: p.Internal.CFGOProfile, }, } @@ -474,6 +477,7 @@ func recompileForTest(pmain, preal, ptest, pxtest *Package) *PackageError { p.Internal.BuildInfo = nil p.Internal.ForceLibrary = true p.Internal.PGOProfile = preal.Internal.PGOProfile + p.Internal.CFGOProfile = preal.Internal.CFGOProfile } // Update p.Internal.Imports to use test copies. @@ -502,6 +506,11 @@ func recompileForTest(pmain, preal, ptest, pxtest *Package) *PackageError { if preal.Internal.PGOProfile != "" && p.Internal.PGOProfile == "" { split() } + // Splite and attach CFGO information to test dependencies if preal + // is built with CFGO. + if preal.Internal.CFGOProfile != "" && p.Internal.CFGOProfile == "" { + split() + } } // Do search to find cycle. diff --git a/src/cmd/go/internal/work/build.go b/src/cmd/go/internal/work/build.go index e2e0e07..7ec62ff 100644 --- a/src/cmd/go/internal/work/build.go +++ b/src/cmd/go/internal/work/build.go @@ -328,6 +328,7 @@ func AddBuildFlags(cmd *base.Command, mask BuildFlagMask) { cmd.Flag.Var(&load.BuildLdflags, "ldflags", "") cmd.Flag.BoolVar(&cfg.BuildLinkshared, "linkshared", false, "") cmd.Flag.StringVar(&cfg.BuildPGO, "pgo", "auto", "") + cmd.Flag.StringVar(&cfg.BuildCFGO, "cfgo", "auto", "") cmd.Flag.StringVar(&cfg.BuildPkgdir, "pkgdir", "", "") cmd.Flag.BoolVar(&cfg.BuildRace, "race", false, "") cmd.Flag.BoolVar(&cfg.BuildMSan, "msan", false, "") diff --git a/src/cmd/go/internal/work/exec.go b/src/cmd/go/internal/work/exec.go index 13d2a78..229cc6b 100644 --- a/src/cmd/go/internal/work/exec.go +++ b/src/cmd/go/internal/work/exec.go @@ -393,6 +393,9 @@ func (b *Builder) buildActionID(a *Action) cache.ActionID { if p.Internal.PGOProfile != "" { fmt.Fprintf(h, "pgofile %s\n", b.fileHash(p.Internal.PGOProfile)) } + if p.Internal.CFGOProfile != "" { + fmt.Fprintf(h, "cfgofile %s\n", b.fileHash(p.Internal.CFGOProfile)) + } for _, a1 := range a.Deps { p1 := a1.Package if p1 != nil { diff --git a/src/cmd/go/internal/work/gc.go b/src/cmd/go/internal/work/gc.go index 26b4e0f..2dbc738 100644 --- a/src/cmd/go/internal/work/gc.go +++ b/src/cmd/go/internal/work/gc.go @@ -139,6 +139,9 @@ func (gcToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg if p.Internal.PGOProfile != "" { defaultGcFlags = append(defaultGcFlags, "-pgoprofile="+p.Internal.PGOProfile) } + if p.Internal.CFGOProfile != "" { + defaultGcFlags = append(defaultGcFlags, "-cfgoprofile="+p.Internal.CFGOProfile) + } if symabis != "" { defaultGcFlags = append(defaultGcFlags, "-symabis", symabis) } -- Gitee