From 6233b6b571409aa8d05fc9568ea8bdbe48cfe53b Mon Sep 17 00:00:00 2001 From: qinchenghan Date: Thu, 7 Aug 2025 15:49:39 +0800 Subject: [PATCH] [patch] enabling continuous feature guided optimization --- src/cmd/compile/internal/base/debug.go | 6 ++ src/cmd/compile/internal/base/flag.go | 18 ++++ src/cmd/compile/internal/base/hashdebug.go | 1 + src/cmd/compile/internal/devirtualize/pgo.go | 93 +++++++++++++------ .../compile/internal/devirtualize/pgo_test.go | 1 + src/cmd/compile/internal/gc/main.go | 20 ++++ src/cmd/compile/internal/inline/inl.go | 31 +++++-- .../inline/interleaved/interleaved.go | 7 +- src/cmd/compile/internal/pgoir/irgraph.go | 29 ++++-- 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 | 2 +- src/cmd/go/internal/load/pkg.go | 57 ++++++++++-- src/cmd/go/internal/load/test.go | 7 ++ src/cmd/go/internal/work/action.go | 24 ++++- src/cmd/go/internal/work/build.go | 1 + src/cmd/go/internal/work/buildid.go | 2 +- src/cmd/go/internal/work/exec.go | 23 ++++- src/cmd/go/internal/work/gc.go | 5 +- src/cmd/go/internal/work/gccgo.go | 2 +- 22 files changed, 313 insertions(+), 70 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 d42e11b2..5a40b1bb 100644 --- a/src/cmd/compile/internal/base/debug.go +++ b/src/cmd/compile/internal/base/debug.go @@ -71,6 +71,12 @@ 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; 0 to disable, 1 to enable interface devirtualization, 2 to enable function devirtualization" concurrent:"ok"` + CFGODebug int `help:"debug continuous feature guided optimizations"` + CFGOHash string `help:"hash value for debugging continuous feature guided optimizations" concurrent:"ok"` + 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; 0 to disable, 1 to enable interface devirtualization, 2 to enable function devirtualization" concurrent:"ok"` RangeFuncCheck int `help:"insert code to check behavior of range iterator functions" 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 31ea8622..6d2fab86 100644 --- a/src/cmd/compile/internal/base/flag.go +++ b/src/cmd/compile/internal/base/flag.go @@ -126,6 +126,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 or pre-process profile from `file`\"" + CfgoProfile string "help:\"read profile or pre-process profile from `file`\"" ErrorURL bool "help:\"print explanatory URL with error message if applicable\"" // Configuration derived from flags; not a flag itself. @@ -145,6 +146,18 @@ type CmdFlags struct { } } +var ( + ENABLE_CFGO = true +) + +func CFGOSwitch() (*int, *string, *int, *string, *int, *int, **HashDebug) { + if ENABLE_CFGO { + return &Debug.CFGODebug, &Debug.CFGOHash, &Debug.CFGOInline, &Debug.CFGOInlineCDFThreshold, &Debug.CFGOInlineBudget, &Debug.CFGODevirtualize, &CFGOHash + } else { + return &Debug.PGODebug, &Debug.PGOHash, &Debug.PGOInline, &Debug.PGOInlineCDFThreshold, &Debug.PGOInlineBudget, &Debug.PGODevirtualize, &PGOHash + } +} + func addEnv(s string) { i := strings.Index(s, "=") if i < 0 { @@ -183,6 +196,8 @@ func ParseFlags() { Debug.InlStaticInit = 1 Debug.PGOInline = 1 Debug.PGODevirtualize = 2 + Debug.CFGOInline = 1 + Debug.CFGODevirtualize = 2 Debug.SyncFrames = -1 // disable sync markers by default Debug.ZeroCopy = 1 Debug.RangeFuncCheck = 1 @@ -267,6 +282,9 @@ func ParseFlags() { if Debug.PGOHash != "" { PGOHash = NewHashDebug("pgohash", Debug.PGOHash, nil) } + if Debug.CFGOHash != "" { + CFGOHash = NewHashDebug("cfgohash", Debug.CFGOHash, nil) + } if Debug.MergeLocalsHash != "" { MergeLocalsHash = NewHashDebug("mergelocals", Debug.MergeLocalsHash, nil) } diff --git a/src/cmd/compile/internal/base/hashdebug.go b/src/cmd/compile/internal/base/hashdebug.go index 7a5cc425..396b9540 100644 --- a/src/cmd/compile/internal/base/hashdebug.go +++ b/src/cmd/compile/internal/base/hashdebug.go @@ -56,6 +56,7 @@ var hashDebug *HashDebug var FmaHash *HashDebug // for debugging fused-multiply-add floating point changes var LoopVarHash *HashDebug // for debugging shared/private loop variable changes var PGOHash *HashDebug // for debugging PGO optimization decisions +var CFGOHash *HashDebug // for debugging CFGO optimization decisions var MergeLocalsHash *HashDebug // for debugging local stack slot merging changes // DebugHashMatchPkgFunc reports whether debug variable Gossahash diff --git a/src/cmd/compile/internal/devirtualize/pgo.go b/src/cmd/compile/internal/devirtualize/pgo.go index 96c9231b..c24d54a7 100644 --- a/src/cmd/compile/internal/devirtualize/pgo.go +++ b/src/cmd/compile/internal/devirtualize/pgo.go @@ -102,13 +102,17 @@ type CallStat struct { // // The primary benefit of this transformation is enabling inlining of the // direct call. + +var db, dv *int +var h **base.HashDebug func ProfileGuided(fn *ir.Func, p *pgoir.Profile) { + db, _, _, _, _, _, _ = base.CFGOSwitch() ir.CurFunc = fn name := ir.LinkFuncName(fn) var jsonW *json.Encoder - if base.Debug.PGODebug >= 3 { + if *db >= 3 { jsonW = json.NewEncoder(os.Stdout) } @@ -126,7 +130,7 @@ func ProfileGuided(fn *ir.Func, p *pgoir.Profile) { } var stat *CallStat - if base.Debug.PGODebug >= 3 { + if *db >= 3 { // Statistics about every single call. Handy for external data analysis. // // TODO(prattmic): Log via logopt? @@ -143,13 +147,20 @@ func ProfileGuided(fn *ir.Func, p *pgoir.Profile) { return n } - if base.Debug.PGODebug >= 2 { - fmt.Printf("%v: PGO devirtualize considering call %v\n", ir.Line(call), call) + var mode string + if base.ENABLE_CFGO { + mode = "CFGO" + } else { + mode = "PGO" + } + + if *db >= 2 { + fmt.Printf("%v: %s devirtualize considering call %v\n", ir.Line(call), mode, call) } if call.GoDefer { - if base.Debug.PGODebug >= 2 { - fmt.Printf("%v: can't PGO devirtualize go/defer call %v\n", ir.Line(call), call) + if *db >= 2 { + fmt.Printf("%v: can't %s devirtualize go/defer call %v\n", ir.Line(call), mode, call) } return n } @@ -185,7 +196,8 @@ func ProfileGuided(fn *ir.Func, p *pgoir.Profile) { // ir.Node if call was devirtualized, and if so also the callee and weight of // the devirtualized edge. func maybeDevirtualizeInterfaceCall(p *pgoir.Profile, fn *ir.Func, call *ir.CallExpr) (ir.Node, *ir.Func, int64) { - if base.Debug.PGODevirtualize < 1 { + _, _, _, _, _, dv, h = base.CFGOSwitch() + if *dv < 1 { return nil, nil, 0 } @@ -204,7 +216,7 @@ func maybeDevirtualizeInterfaceCall(p *pgoir.Profile, fn *ir.Func, call *ir.Call return nil, nil, 0 } // Bail if de-selected by PGO Hash. - if !base.PGOHash.MatchPosWithInfo(call.Pos(), "devirt", nil) { + if !(*h).MatchPosWithInfo(call.Pos(), "devirt", nil) { return nil, nil, 0 } @@ -215,7 +227,8 @@ func maybeDevirtualizeInterfaceCall(p *pgoir.Profile, fn *ir.Func, call *ir.Call // ir.Node if call was devirtualized, and if so also the callee and weight of // the devirtualized edge. func maybeDevirtualizeFunctionCall(p *pgoir.Profile, fn *ir.Func, call *ir.CallExpr) (ir.Node, *ir.Func, int64) { - if base.Debug.PGODevirtualize < 2 { + db, _, _, _, _, dv, h = base.CFGOSwitch() + if *dv < 2 { return nil, nil, 0 } @@ -235,7 +248,7 @@ func maybeDevirtualizeFunctionCall(p *pgoir.Profile, fn *ir.Func, call *ir.CallE // via the context register. That requires extra plumbing that we // haven't done yet. if callee.OClosure != nil { - if base.Debug.PGODebug >= 3 { + if *db >= 3 { fmt.Printf("callee %s is a closure, skipping\n", ir.FuncName(callee)) } return nil, nil, 0 @@ -245,7 +258,7 @@ func maybeDevirtualizeFunctionCall(p *pgoir.Profile, fn *ir.Func, call *ir.CallE // callers, which are generated by // cmd/compile/internal/reflectdata.genhash. if callee.Sym().Pkg.Path == "runtime" && callee.Sym().Name == "memhash_varlen" { - if base.Debug.PGODebug >= 3 { + if *db >= 3 { fmt.Printf("callee %s is a closure (runtime.memhash_varlen), skipping\n", ir.FuncName(callee)) } return nil, nil, 0 @@ -278,7 +291,7 @@ func maybeDevirtualizeFunctionCall(p *pgoir.Profile, fn *ir.Func, call *ir.CallE // N.B. perf profiles will report wrapper symbols directly, so // ideally we should support direct wrapper references as well. if callee.Type().Recv() != nil { - if base.Debug.PGODebug >= 3 { + if *db >= 3 { fmt.Printf("callee %s is a method, skipping\n", ir.FuncName(callee)) } return nil, nil, 0 @@ -289,7 +302,7 @@ func maybeDevirtualizeFunctionCall(p *pgoir.Profile, fn *ir.Func, call *ir.CallE return nil, nil, 0 } // Bail if de-selected by PGO Hash. - if !base.PGOHash.MatchPosWithInfo(call.Pos(), "devirt", nil) { + if !(*h).MatchPosWithInfo(call.Pos(), "devirt", nil) { return nil, nil, 0 } @@ -306,11 +319,19 @@ func shouldPGODevirt(fn *ir.Func) bool { if base.Flag.LowerM > 1 || logopt.Enabled() { defer func() { if reason != "" { + var mode, log string + if base.ENABLE_CFGO { + mode = "CFGO" + log = ": should not CFGO devirtualize function" + } else { + mode = "PGO" + log = ": should not PGO devirtualize function" + } 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", ir.Line(fn), mode, ir.FuncName(fn), reason) } if logopt.Enabled() { - logopt.LogOpt(fn.Pos(), ": should not PGO devirtualize function", "pgoir-devirtualize", ir.FuncName(fn), reason) + logopt.LogOpt(fn.Pos(), log, "pgoir-devirtualize", ir.FuncName(fn), reason) } } }() @@ -490,8 +511,15 @@ func condCall(curfn *ir.Func, pos src.XPos, cond ir.Node, thenCall, elseCall *ir // rewriteInterfaceCall devirtualizes the given interface call using a direct // method call to concretetyp. func rewriteInterfaceCall(call *ir.CallExpr, curfn, callee *ir.Func, concretetyp *types.Type) ir.Node { + db, _, _, _, _, _, _ = base.CFGOSwitch() + var mode string + if base.ENABLE_CFGO { + mode = "CFGO" + } else { + mode = "PGO" + } if base.Flag.LowerM != 0 { - fmt.Printf("%v: PGO devirtualizing interface call %v to %v\n", ir.Line(call), call.Fun, callee) + fmt.Printf("%v: %s devirtualizing interface call %v to %v\n", ir.Line(call), mode, call.Fun, callee) } // We generate an OINCALL of: @@ -548,8 +576,8 @@ func rewriteInterfaceCall(call *ir.CallExpr, curfn, callee *ir.Func, concretetyp res := condCall(curfn, pos, tmpok, concreteCall, call, init) - if base.Debug.PGODebug >= 3 { - fmt.Printf("PGO devirtualizing interface call to %+v. After: %+v\n", concretetyp, res) + if *db >= 3 { + fmt.Printf("%s devirtualizing interface call to %+v. After: %+v\n", mode, concretetyp, res) } return res @@ -558,8 +586,14 @@ func rewriteInterfaceCall(call *ir.CallExpr, curfn, callee *ir.Func, concretetyp // rewriteFunctionCall devirtualizes the given OCALLFUNC using a direct // function call to callee. func rewriteFunctionCall(call *ir.CallExpr, curfn, callee *ir.Func) ir.Node { + var mode string + if base.ENABLE_CFGO { + mode = "CFGO" + } else { + mode = "PGO" + } if base.Flag.LowerM != 0 { - fmt.Printf("%v: PGO devirtualizing function call %v to %v\n", ir.Line(call), call.Fun, callee) + fmt.Printf("%v: %s devirtualizing function call %v to %v\n", ir.Line(call), mode, call.Fun, callee) } // We generate an OINCALL of: @@ -620,8 +654,8 @@ func rewriteFunctionCall(call *ir.CallExpr, curfn, callee *ir.Func) ir.Node { res := condCall(curfn, pos, pcEq, concreteCall, call, init) - if base.Debug.PGODebug >= 3 { - fmt.Printf("PGO devirtualizing function call to %+v. After: %+v\n", ir.FuncName(callee), res) + if *db >= 3 { + fmt.Printf("%s devirtualizing function call to %+v. After: %+v\n", mode, ir.FuncName(callee), res) } return res @@ -657,6 +691,7 @@ func interfaceCallRecvTypeAndMethod(call *ir.CallExpr) (*types.Type, *types.Sym) // applicability checks on each candidate edge. If extraFn returns false, // candidate will not be considered a valid callee candidate. func findHotConcreteCallee(p *pgoir.Profile, caller *ir.Func, call *ir.CallExpr, extraFn func(callerName string, callOffset int, candidate *pgoir.IREdge) bool) (*ir.Func, int64) { + db, _, _, _, _, _, _ = base.CFGOSwitch() callerName := ir.LinkFuncName(caller) callerNode := p.WeightedCG.IRNodes[callerName] callOffset := pgoir.NodeLineOffset(call, caller) @@ -708,7 +743,7 @@ func findHotConcreteCallee(p *pgoir.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 *db >= 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 @@ -724,7 +759,7 @@ func findHotConcreteCallee(p *pgoir.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 *db >= 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 @@ -735,20 +770,20 @@ func findHotConcreteCallee(p *pgoir.Profile, caller *ir.Func, call *ir.CallExpr, continue } - if base.Debug.PGODebug >= 2 { + if *db >= 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 *db >= 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 *db >= 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 @@ -782,7 +817,7 @@ func findHotConcreteInterfaceCallee(p *pgoir.Profile, caller *ir.Func, call *ir. // 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 *db >= 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) } @@ -792,7 +827,7 @@ func findHotConcreteInterfaceCallee(p *pgoir.Profile, caller *ir.Func, call *ir. // 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 *db >= 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) } return false @@ -818,7 +853,7 @@ func findHotConcreteFunctionCallee(p *pgoir.Profile, caller *ir.Func, call *ir.C // net/http.HandlerFunc can be devirtualized to a function with // the same underlying type. if !types.Identical(typ, ctyp) { - if base.Debug.PGODebug >= 2 { + if *db >= 2 { fmt.Printf("%v: edge %s:%d -> %s (weight %d): %v doesn't match %v\n", ir.Line(call), callerName, callOffset, e.Dst.Name(), e.Weight, ctyp, typ) } return false diff --git a/src/cmd/compile/internal/devirtualize/pgo_test.go b/src/cmd/compile/internal/devirtualize/pgo_test.go index 6153b8c5..b70ae133 100644 --- a/src/cmd/compile/internal/devirtualize/pgo_test.go +++ b/src/cmd/compile/internal/devirtualize/pgo_test.go @@ -27,6 +27,7 @@ func init() { base.Ctxt = &obj.Link{Arch: &obj.LinkArch{Arch: &sys.Arch{Alignment: 1, CanMergeLoads: true}}} typecheck.InitUniverse() base.Debug.PGODebug = 3 + base.Debug.CFGODebug = 3 } func makePos(b *src.PosBase, line, col uint) src.XPos { diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index 253ec325..42108223 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -229,11 +229,31 @@ func Main(archInit func(*ssagen.ArchInfo)) { var profile *pgoir.Profile if base.Flag.PgoProfile != "" { var err error + base.ENABLE_CFGO = false profile, err = pgoir.New(base.Flag.PgoProfile) if err != nil { log.Fatalf("%s: PGO error: %v", base.Flag.PgoProfile, err) } } + base.Timer.Start("fe", "cfgo-load-profile") + if base.Flag.CfgoProfile != "" { + result := os.Getenv("AI_OPT") + // result := "1" + if result == "1" { + var err error + base.ENABLE_CFGO = true + profile, err = pgoir.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 + } + + // fmt.Printf("CfgoProfile: %s, PgoProfile: %s, ENABLE_CFGO: %t, profile: %t\n", base.Flag.CfgoProfile, base.Flag.PgoProfile, base.ENABLE_CFGO, profile != nil) // Interleaved devirtualization and inlining. base.Timer.Start("fe", "devirtualize-and-inline") diff --git a/src/cmd/compile/internal/inline/inl.go b/src/cmd/compile/internal/inline/inl.go index bddf4aa2..2c518243 100644 --- a/src/cmd/compile/internal/inline/inl.go +++ b/src/cmd/compile/internal/inline/inl.go @@ -85,6 +85,10 @@ var ( inlineHotMaxBudget int32 = 2000 ) +var db, ib *int +var h **base.HashDebug +var ict *string + func IsPgoHotFunc(fn *ir.Func, profile *pgoir.Profile) bool { if profile == nil { return false @@ -103,20 +107,25 @@ func HasPgoHotInline(fn *ir.Func) bool { // PGOInlinePrologue records the hot callsites from ir-graph. func PGOInlinePrologue(p *pgoir.Profile) { - if base.Debug.PGOInlineCDFThreshold != "" { - if s, err := strconv.ParseFloat(base.Debug.PGOInlineCDFThreshold, 64); err == nil && s >= 0 && s <= 100 { + db, _, _, ict, ib, _, _ = base.CFGOSwitch() + if *ict != "" { + if s, err := strconv.ParseFloat(*ict, 64); err == nil && s >= 0 && s <= 100 { inlineCDFHotCallSiteThresholdPercent = s } else { - base.Fatalf("invalid PGOInlineCDFThreshold, must be between 0 and 100") + if base.ENABLE_CFGO { + base.Fatalf("invalid CFGOInlineCDFThreshold, must be between 0 and 100") + } else { + base.Fatalf("invalid PGOInlineCDFThreshold, must be between 0 and 100") + } } } var hotCallsites []pgo.NamedCallEdge inlineHotCallSiteThresholdPercent, hotCallsites = hotNodesFromCDF(p) - if base.Debug.PGODebug > 0 { + if *db > 0 { fmt.Printf("hot-callsite-thres-from-CDF=%v\n", inlineHotCallSiteThresholdPercent) } - if x := base.Debug.PGOInlineBudget; x != 0 { + if x := *ib; x != 0 { inlineHotMaxBudget = int32(x) } @@ -132,7 +141,7 @@ func PGOInlinePrologue(p *pgoir.Profile) { } } - if base.Debug.PGODebug >= 3 { + if *db >= 3 { fmt.Printf("hot-cg before inline in dot format:") p.PrintWeightedCallGraphDOT(inlineHotCallSiteThresholdPercent) } @@ -220,6 +229,7 @@ func inlineBudget(fn *ir.Func, profile *pgoir.Profile, relaxed bool, verbose boo // If so, CanInline saves copies of fn.Body and fn.Dcl in fn.Inl. // fn and fn.Body will already have been typechecked. func CanInline(fn *ir.Func, profile *pgoir.Profile) { + db, _, _, _, _, _, _ = base.CFGOSwitch() if fn.Nname == nil { base.Fatalf("CanInline no nname %+v", fn) } @@ -261,7 +271,7 @@ func CanInline(fn *ir.Func, profile *pgoir.Profile) { relaxed := inlheur.Enabled() // Compute the inline budget for this func. - budget := inlineBudget(fn, profile, relaxed, base.Debug.PGODebug > 0) + budget := inlineBudget(fn, profile, relaxed, *db > 0) // At this point in the game the function we're looking at may // have "stale" autos, vars that still appear in the Dcl list, but @@ -910,6 +920,7 @@ var InlineCall = func(callerfn *ir.Func, call *ir.CallExpr, fn *ir.Func, inlInde // - the score assigned to this specific callsite // - whether the inlined function is "hot" according to PGO. func inlineCostOK(n *ir.CallExpr, caller, callee *ir.Func, bigCaller, closureCalledOnce bool) (bool, int32, int32, bool) { + db, _, _, _, _, _, h = base.CFGOSwitch() maxCost := int32(inlineMaxBudget) if bigCaller { @@ -953,7 +964,7 @@ func inlineCostOK(n *ir.CallExpr, caller, callee *ir.Func, bigCaller, closureCal // Hot if bigCaller { - if base.Debug.PGODebug > 0 { + if *db > 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, metric, false @@ -963,12 +974,12 @@ func inlineCostOK(n *ir.CallExpr, caller, callee *ir.Func, bigCaller, closureCal return false, inlineHotMaxBudget, metric, false } - if !base.PGOHash.MatchPosWithInfo(n.Pos(), "inline", nil) { + if !(*h).MatchPosWithInfo(n.Pos(), "inline", nil) { // De-selected by PGO Hash. return false, maxCost, metric, false } - if base.Debug.PGODebug > 0 { + if *db > 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/inline/interleaved/interleaved.go b/src/cmd/compile/internal/inline/interleaved/interleaved.go index 954cc306..c49096fe 100644 --- a/src/cmd/compile/internal/inline/interleaved/interleaved.go +++ b/src/cmd/compile/internal/inline/interleaved/interleaved.go @@ -17,10 +17,13 @@ import ( "fmt" ) +var dv, il *int + // DevirtualizeAndInlinePackage interleaves devirtualization and inlining on // all functions within pkg. func DevirtualizeAndInlinePackage(pkg *ir.Package, profile *pgoir.Profile) { - if profile != nil && base.Debug.PGODevirtualize > 0 { + _, _, il, _, _, dv, _ = base.CFGOSwitch() + if profile != nil && *dv > 0 { // TODO(mdempsky): Integrate into DevirtualizeAndInlineFunc below. ir.VisitFuncsBottomUp(typecheck.Target.Funcs, func(list []*ir.Func, recursive bool) { for _, fn := range list { @@ -35,7 +38,7 @@ func DevirtualizeAndInlinePackage(pkg *ir.Package, profile *pgoir.Profile) { } var inlProfile *pgoir.Profile // copy of profile for inlining - if base.Debug.PGOInline != 0 { + if *il != 0 { inlProfile = profile } diff --git a/src/cmd/compile/internal/pgoir/irgraph.go b/src/cmd/compile/internal/pgoir/irgraph.go index 914a4da8..7a8266a4 100644 --- a/src/cmd/compile/internal/pgoir/irgraph.go +++ b/src/cmd/compile/internal/pgoir/irgraph.go @@ -83,6 +83,8 @@ type IRNode struct { OutEdges map[pgo.NamedCallEdge]*IREdge } +var db *int + // Name returns the symbol name of this function. func (i *IRNode) Name() string { if i.AST != nil { @@ -132,28 +134,34 @@ func New(profileFile string) (*Profile, error) { return nil, fmt.Errorf("error processing profile header: %w", err) } - var base *pgo.Profile + var pbase *pgo.Profile + var mode string + if base.ENABLE_CFGO { + mode = "CFGO" + } else { + mode = "PGO" + } if isSerialized { - base, err = pgo.FromSerialized(r) + pbase, err = pgo.FromSerialized(r) if err != nil { - return nil, fmt.Errorf("error processing serialized PGO profile: %w", err) + return nil, fmt.Errorf("error processing serialized %s profile: %w", mode, err) } } else { - base, err = pgo.FromPProf(r) + pbase, err = pgo.FromPProf(r) if err != nil { - return nil, fmt.Errorf("error processing pprof PGO profile: %w", err) + return nil, fmt.Errorf("error processing pprof %s profile: %w", mode, err) } } - if base.TotalWeight == 0 { + if pbase.TotalWeight == 0 { return nil, nil // accept but ignore profile with no samples. } // Create package-level call graph with weights from profile and IR. - wg := createIRGraph(base.NamedEdgeMap) + wg := createIRGraph(pbase.NamedEdgeMap) return &Profile{ - Profile: base, + Profile: pbase, WeightedCG: wg, }, nil } @@ -293,6 +301,7 @@ var PostLookupCleanup = func() { // calls inside inlined call bodies. If we did add that, we'd need edges from // inlined bodies as well. func addIndirectEdges(g *IRGraph, namedEdgeMap pgo.NamedEdgeMap) { + db, _, _, _, _, _, _ = base.CFGOSwitch() // g.IRNodes is populated with the set of functions in the local // package build by VisitIR. We want to filter for local functions // below, but we also add unknown callees to IRNodes as we go. So make @@ -340,12 +349,12 @@ func addIndirectEdges(g *IRGraph, namedEdgeMap pgo.NamedEdgeMap) { // devirtualization. Instantiation of generic functions // will likely need to be done at the devirtualization // site, if at all. - if base.Debug.PGODebug >= 3 { + if *db >= 3 { fmt.Printf("addIndirectEdges: %s attempting export data lookup\n", key.CalleeName) } fn, err := LookupFunc(key.CalleeName) if err == nil { - if base.Debug.PGODebug >= 3 { + if *db >= 3 { fmt.Printf("addIndirectEdges: %s found in export data\n", key.CalleeName) } calleeNode = &IRNode{AST: fn} diff --git a/src/cmd/compile/internal/ssa/layout.go b/src/cmd/compile/internal/ssa/layout.go index e4a8c6ff..492ca53b 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 00000000..d5cbd157 --- /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 1f467647..38b4bdd8 100644 --- a/src/cmd/dist/build.go +++ b/src/cmd/dist/build.go @@ -1545,7 +1545,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 3b9f27e9..9ef8365e 100644 --- a/src/cmd/go/internal/cfg/cfg.go +++ b/src/cmd/go/internal/cfg/cfg.go @@ -85,6 +85,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 04fdadef..212f83bf 100644 --- a/src/cmd/go/internal/list/list.go +++ b/src/cmd/go/internal/list/list.go @@ -752,7 +752,7 @@ func runList(ctx context.Context, cmd *base.Command, args []string) { } } - if *listTest || (cfg.BuildPGO == "auto" && len(cmdline) > 1) { + if *listTest || ((cfg.BuildPGO == "auto"|| cfg.BuildCFGO == "auto") && len(cmdline) > 1) { all := pkgs if !*listDeps { all = loadPackageList(pkgs) diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go index 15f6b2e8..2729b4d5 100644 --- a/src/cmd/go/internal/load/pkg.go +++ b/src/cmd/go/internal/load/pkg.go @@ -52,6 +52,11 @@ import ( "golang.org/x/mod/module" ) +var ( + ENABLE_CFGO = true + mode string +) + // A Package describes a single package found in a directory. type Package struct { PackagePublic // visible in 'go list' @@ -238,6 +243,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 @@ -2973,10 +2979,16 @@ func setPGOProfilePath(pkgs []*Package) { return } + if ENABLE_CFGO { + mode = "-cfgo" + } else { + mode = "-pgo" + } + if cfg.BuildTrimpath { - appendBuildSetting(p.Internal.BuildInfo, "-pgo", filepath.Base(file)) + appendBuildSetting(p.Internal.BuildInfo, mode, filepath.Base(file)) } else { - appendBuildSetting(p.Internal.BuildInfo, "-pgo", file) + appendBuildSetting(p.Internal.BuildInfo, mode, file) } // Adding -pgo breaks the sort order in BuildInfo.Settings. Restore it. slices.SortFunc(p.Internal.BuildInfo.Settings, func(x, y debug.BuildSetting) int { @@ -2984,7 +2996,25 @@ func setPGOProfilePath(pkgs []*Package) { }) } - switch cfg.BuildPGO { + if !(cfg.BuildCFGO == "off" || cfg.BuildCFGO == "auto") { + cfg.BuildPGO = "off" + } else if cfg.BuildCFGO == "off" { + ENABLE_CFGO = false + } else if cfg.BuildPGO != "off" { + cfg.BuildCFGO = "off" + ENABLE_CFGO = false + } + + var build string + if ENABLE_CFGO { + build = cfg.BuildCFGO + mode = "CFGO" + } else { + build = cfg.BuildPGO + mode = "PGO" + } + + switch build { case "off": return @@ -3021,7 +3051,7 @@ 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 != "" { + if p.Internal.PGOProfile != "" || p.Internal.CFGOProfile != "" { panic("setPGOProfilePath: already have profile") } p1 := new(Package) @@ -3037,7 +3067,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 { @@ -3053,13 +3087,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", mode, 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) } } @@ -3110,6 +3148,9 @@ func PackageErrors(pkgs []*Package, report func(*Package)) { if pkg.Internal.PGOProfile != "" { key += " pgo:" + pkg.Internal.PGOProfile } + if pkg.Internal.CFGOProfile != "" { + key += " cfgo:" + pkg.Internal.CFGOProfile + } if seen[key] && !reported[key] { reported[key] = true base.Errorf("internal error: duplicate loads of %s", pkg.ImportPath) diff --git a/src/cmd/go/internal/load/test.go b/src/cmd/go/internal/load/test.go index ddd14a03..c2d2fe0b 100644 --- a/src/cmd/go/internal/load/test.go +++ b/src/cmd/go/internal/load/test.go @@ -221,6 +221,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 @@ -259,6 +260,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 { @@ -290,6 +292,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, }, } @@ -492,6 +495,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. @@ -520,6 +524,9 @@ func recompileForTest(pmain, preal, ptest, pxtest *Package) *PackageError { if preal.Internal.PGOProfile != "" && p.Internal.PGOProfile == "" { split() } + if preal.Internal.CFGOProfile != "" && p.Internal.CFGOProfile == "" { + split() + } } // Do search to find cycle. diff --git a/src/cmd/go/internal/work/action.go b/src/cmd/go/internal/work/action.go index 44bb9f8c..ee96f6e0 100644 --- a/src/cmd/go/internal/work/action.go +++ b/src/cmd/go/internal/work/action.go @@ -468,9 +468,15 @@ func (ba *buildActor) Act(b *Builder, ctx context.Context, a *Action) error { // pgoActionID computes the action ID for a preprocess PGO action. func (b *Builder) pgoActionID(input string) cache.ActionID { - h := cache.NewHash("preprocess PGO profile " + input) + var mode string + if load.ENABLE_CFGO { + mode = "CFGO" + } else { + mode = "PGO" + } + h := cache.NewHash("preprocess "+ mode + " profile " + input) - fmt.Fprintf(h, "preprocess PGO profile\n") + fmt.Fprintf(h, "preprocess %s profile\n", mode) fmt.Fprintf(h, "preprofile %s\n", b.toolID("preprofile")) fmt.Fprintf(h, "input %q\n", b.fileHash(input)) @@ -576,6 +582,20 @@ func (b *Builder) CompileAction(mode, depMode BuildMode, p *load.Package) *Actio a.Deps = append(a.Deps, pgoAction) } + if p.Internal.CFGOProfile != "" { + cfgoAction := b.cacheAction("preprocess CFGO profile "+p.Internal.CFGOProfile, nil, func() *Action { + a := &Action{ + Mode: "preprocess CFGO profile", + Actor: &pgoActor{input: p.Internal.CFGOProfile}, + Objdir: b.NewObjdir(), + } + a.Target = filepath.Join(a.Objdir, "cfgo.preprofile") + + return a + }) + a.Deps = append(a.Deps, cfgoAction) + } + if p.Standard { switch p.ImportPath { case "builtin", "unsafe": diff --git a/src/cmd/go/internal/work/build.go b/src/cmd/go/internal/work/build.go index 3508d51f..0df5166e 100644 --- a/src/cmd/go/internal/work/build.go +++ b/src/cmd/go/internal/work/build.go @@ -348,6 +348,7 @@ func AddBuildFlags(cmd *base.Command, mask BuildFlagMask) { cmd.Flag.BoolVar(&cfg.BuildLinkshared, "linkshared", false, "") cmd.Flag.BoolVar(&cfg.BuildMSan, "msan", 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.Var((*tagsFlag)(&cfg.BuildContext.BuildTags), "tags", "") diff --git a/src/cmd/go/internal/work/buildid.go b/src/cmd/go/internal/work/buildid.go index cab722c2..9325681f 100644 --- a/src/cmd/go/internal/work/buildid.go +++ b/src/cmd/go/internal/work/buildid.go @@ -555,7 +555,7 @@ func (b *Builder) useCache(a *Action, actionHash cache.ActionID, target string, // Check to see if the action output is cached. if file, _, err := cache.GetFile(c, actionHash); err == nil { - if a.Mode == "preprocess PGO profile" { + if a.Mode == "preprocess PGO profile" || a.Mode == "preprocess CFGO profile" { // Preprocessed PGO profiles don't embed a build ID, so // skip the build ID lookup. // TODO(prattmic): better would be to add a build ID to the format. diff --git a/src/cmd/go/internal/work/exec.go b/src/cmd/go/internal/work/exec.go index 7b073165..131db30a 100644 --- a/src/cmd/go/internal/work/exec.go +++ b/src/cmd/go/internal/work/exec.go @@ -407,6 +407,9 @@ func (b *Builder) buildActionID(a *Action) cache.ActionID { if a1.Mode == "preprocess PGO profile" { fmt.Fprintf(h, "pgofile %s\n", b.fileHash(a1.built)) } + if a1.Mode == "preprocess CFGO profile" { + fmt.Fprintf(h, "cfgofile %s\n", b.fileHash(a1.built)) + } } return h.Sum() @@ -879,6 +882,18 @@ OverlayLoop: pgoProfile = a1.built } + // Find CFGO profile if needed. + var cfgoProfile string + for _, a1 := range a.Deps { + if a1.Mode != "preprocess CFGO profile" { + continue + } + if cfgoProfile != "" { + return fmt.Errorf("action contains multiple CFGO profile dependencies") + } + cfgoProfile = a1.built + } + if p.Internal.BuildInfo != nil && cfg.ModulesEnabled { prog := modload.ModInfoProg(p.Internal.BuildInfo.String(), cfg.BuildToolchainName == "gccgo") if len(prog) > 0 { @@ -891,7 +906,7 @@ OverlayLoop: // Compile Go. objpkg := objdir + "_pkg_.a" - ofile, out, err := BuildToolchain.gc(b, a, objpkg, icfg.Bytes(), embedcfg, symabis, len(sfiles) > 0, pgoProfile, gofiles) + ofile, out, err := BuildToolchain.gc(b, a, objpkg, icfg.Bytes(), embedcfg, symabis, len(sfiles) > 0, pgoProfile, cfgoProfile, gofiles) if err := sh.reportCmd("", "", out, err); err != nil { return err } @@ -2047,7 +2062,7 @@ func mkAbs(dir, f string) string { type toolchain interface { // gc runs the compiler in a specific directory on a set of files // and returns the name of the generated output file. - gc(b *Builder, a *Action, archive string, importcfg, embedcfg []byte, symabis string, asmhdr bool, pgoProfile string, gofiles []string) (ofile string, out []byte, err error) + gc(b *Builder, a *Action, archive string, importcfg, embedcfg []byte, symabis string, asmhdr bool, pgoProfile string, cfgoProfile string, gofiles []string) (ofile string, out []byte, err error) // cc runs the toolchain's C compiler in a directory on a C file // to produce an output file. cc(b *Builder, a *Action, ofile, cfile string) error @@ -2087,7 +2102,7 @@ func (noToolchain) linker() string { return "" } -func (noToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg []byte, symabis string, asmhdr bool, pgoProfile string, gofiles []string) (ofile string, out []byte, err error) { +func (noToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg []byte, symabis string, asmhdr bool, pgoProfile string, cfgoProfile string, gofiles []string) (ofile string, out []byte, err error) { return "", nil, noCompiler() } @@ -3236,7 +3251,7 @@ func (b *Builder) swigDoIntSize(objdir string) (intsize string, err error) { p := load.GoFilesPackage(context.TODO(), load.PackageOpts{}, srcs) - if _, _, e := BuildToolchain.gc(b, &Action{Mode: "swigDoIntSize", Package: p, Objdir: objdir}, "", nil, nil, "", false, "", srcs); e != nil { + if _, _, e := BuildToolchain.gc(b, &Action{Mode: "swigDoIntSize", Package: p, Objdir: objdir}, "", nil, nil, "", false, "", "", srcs); e != nil { return "32", nil } return "64", nil diff --git a/src/cmd/go/internal/work/gc.go b/src/cmd/go/internal/work/gc.go index 3a173efe..d2da69f4 100644 --- a/src/cmd/go/internal/work/gc.go +++ b/src/cmd/go/internal/work/gc.go @@ -54,7 +54,7 @@ func pkgPath(a *Action) string { return ppath } -func (gcToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg []byte, symabis string, asmhdr bool, pgoProfile string, gofiles []string) (ofile string, output []byte, err error) { +func (gcToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg []byte, symabis string, asmhdr bool, pgoProfile string, cfgoProfile string, gofiles []string) (ofile string, output []byte, err error) { p := a.Package sh := b.Shell(a) objdir := a.Objdir @@ -119,6 +119,9 @@ func (gcToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg if pgoProfile != "" { defaultGcFlags = append(defaultGcFlags, "-pgoprofile="+pgoProfile) } + if cfgoProfile != "" { + defaultGcFlags = append(defaultGcFlags, "-cfgoprofile="+cfgoProfile) + } if symabis != "" { defaultGcFlags = append(defaultGcFlags, "-symabis", symabis) } diff --git a/src/cmd/go/internal/work/gccgo.go b/src/cmd/go/internal/work/gccgo.go index bdd76f63..6fad2224 100644 --- a/src/cmd/go/internal/work/gccgo.go +++ b/src/cmd/go/internal/work/gccgo.go @@ -60,7 +60,7 @@ func checkGccgoBin() { base.Exit() } -func (tools gccgoToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg []byte, symabis string, asmhdr bool, pgoProfile string, gofiles []string) (ofile string, output []byte, err error) { +func (tools gccgoToolchain) gc(b *Builder, a *Action, archive string, importcfg, embedcfg []byte, symabis string, asmhdr bool, pgoProfile string, cfgoProfile string, gofiles []string) (ofile string, output []byte, err error) { p := a.Package sh := b.Shell(a) objdir := a.Objdir -- Gitee