cmd/compile: simplify closure name

Currently, a closure in a function is usually named after the
outer function, usually in the form of pkg.outer.funcN. When the
containing function is inlined, we attach the inlined caller's
name to the closure name, so this may become things like
callerpkg.caller.pkg.outer.funcN. With multiple levels of
inlining, this name can get pretty long and clutter.

This CL change the compiler to use the simple, pre-inlining name
for closures. That is, the closure is always named pkg.outer.funcN
where outer is the containing function in the source code. This
name is not changed during inlining. With inlining, there may be
multiple copies of the closure, all with the same name. They are
likely to be compiled identically, although technically it is
possible for the compiler to optimize them differently based on
the context. So we'll use a content hash to distinguish and
deduplicate them.

With the content-addressable symbol mechanism, the linker is
capable of handling multiple symbols with the same name, and use
the content hash to distinguish and deduplicate them. A
complication is that the compiler is not able to handle multiple
symbols with the same name when compiling a package. So we give
them temporarily unique suffixes during the compilation (based
on the inline call stack), and trim the suffix in the object file
and DWARF generation. So their linker symbols remain simple.

One caveat is nested closure (i.e. a closure within a closure).
Previously, a nested closure is named as topLevelFunc.funcN.M where
topLevelFunc.funcN is the outer closure. When the outer closure is
inlined, and the inlined caller is not a closure, it is named as
caller.topLevelFunc.funcN.funcM (note the extra "func"). This is
arguably a bug in the current code, as it decides whether to
include the "func" word based on whether the physical containing
function is a closure or not, not the source-level function. This
CL removes the "caller" part from the name, but does not address
the extra "func" word. So when the outer closure is inlined, the
inner closure will be named topLevelFunc.funcN.funcM, which
differs from the original topLevelFunc.funcN.M. This is not too
bad in that the name won't get too long, and still match the
source.

Fixes #60324.

Change-Id: Ia69c35a8f9b1a3b2c27db1a0959c1316be8b1f81
Reviewed-on: https://go-review.googlesource.com/c/go/+/770200
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
Commit-Queue: Cherry Mui <cherryyz@google.com>
Reviewed-by: David Chase <drchase@google.com>
TryBot-Bypass: Cherry Mui <cherryyz@google.com>
Reviewed-by: Alessandro Arzilli <alessandro.arzilli@gmail.com>
This commit is contained in:
Cherry Mui 2026-04-19 10:56:38 -04:00
parent 1456da550a
commit 409f784bea
18 changed files with 272 additions and 73 deletions

View file

@ -324,9 +324,9 @@ func CanInline(fn *ir.Func, profile *pgoir.Profile) {
// function is inlinable.
func noteInlinableFunc(n *ir.Name, fn *ir.Func, cost int32) {
if base.Flag.LowerM > 1 {
fmt.Printf("%v: can inline %v with cost %d as: %v { %v }\n", ir.Line(fn), n, cost, fn.Type(), fn.Body)
fmt.Printf("%v: can inline %v with cost %d as: %v { %v }\n", ir.Line(fn), n.DiagName(), cost, fn.Type(), fn.Body)
} else if base.Flag.LowerM != 0 {
fmt.Printf("%v: can inline %v\n", ir.Line(fn), n)
fmt.Printf("%v: can inline %v\n", ir.Line(fn), n.DiagName())
}
// JSON optimization log output.
if logopt.Enabled() {
@ -1227,9 +1227,9 @@ func mkinlcall(callerfn *ir.Func, n *ir.CallExpr, fn *ir.Func, bigCaller, closur
if base.Flag.LowerM != 0 {
if buildcfg.Experiment.NewInliner {
fmt.Printf("%v: inlining call to %v with score %d\n",
ir.Line(n), fn, score)
ir.Line(n), fn.Nname.DiagName(), score)
} else {
fmt.Printf("%v: inlining call to %v\n", ir.Line(n), fn)
fmt.Printf("%v: inlining call to %v\n", ir.Line(n), fn.Nname.DiagName())
}
}
if base.Flag.LowerM > 2 {
@ -1239,7 +1239,7 @@ func mkinlcall(callerfn *ir.Func, n *ir.CallExpr, fn *ir.Func, bigCaller, closur
res := InlineCall(callerfn, n, fn, inlIndex)
if res == nil {
base.FatalfAt(n.Pos(), "inlining call to %v failed", fn)
base.FatalfAt(n.Pos(), "inlining call to %v failed", fn.Nname.DiagName())
}
if base.Flag.LowerM > 2 {

View file

@ -7,11 +7,13 @@ package ir
import (
"cmd/compile/internal/base"
"cmd/compile/internal/types"
"cmd/internal/hash"
"cmd/internal/obj"
"cmd/internal/objabi"
"cmd/internal/src"
"encoding/base64"
"fmt"
"strings"
"io"
"unicode/utf8"
)
@ -430,10 +432,9 @@ func ClosureDebugRuntimeCheck(clo *ClosureExpr) {
var globClosgen int32
// closureName generates a new unique name for a closure within outerfn at pos.
func closureName(outerfn *Func, pos src.XPos, why Op) *types.Sym {
if outerfn.OClosure != nil && outerfn.OClosure.Func.RangeParent != nil {
outerfn = outerfn.OClosure.Func.RangeParent
}
// gen is an optional counter for the closure name. If it is 0, the counter
// will be computed based on outerfn.
func closureName(outerfn *Func, pos src.XPos, why Op, gen int) *types.Sym {
pkg := types.LocalPkg
outer := "glob."
var suffix string = "."
@ -451,7 +452,6 @@ func closureName(outerfn *Func, pos src.XPos, why Op) *types.Sym {
case ODEFER:
suffix = ".deferwrap"
}
gen := &globClosgen
// There may be multiple functions named "_". In those
// cases, we can't use their individual Closgens as it
@ -459,30 +459,57 @@ func closureName(outerfn *Func, pos src.XPos, why Op) *types.Sym {
if !IsBlank(outerfn.Nname) {
pkg = outerfn.Sym().Pkg
outer = FuncName(outerfn)
}
switch why {
case OCLOSURE:
gen = &outerfn.funcLitGen
case ORANGE:
gen = &outerfn.rangeLitGen
default:
gen = &outerfn.goDeferGen
// If this closure was created due to inlining, find the original
// outer function's name for the closure (#60324).
var inlHash string
if inlIndex := base.Ctxt.InnermostPos(pos).Base().InliningIndex(); inlIndex >= 0 {
// The compiler doesn't like multiple symbols with the same
// name. We make a unique suffix temporarily for the
// compiler, and strip it during object file writing, so
// it will not be the linker symbol name. For linking,
// we use a content hash to disambiguate instead.
// We choose the suffix as a hash of the inline call stack.
h := hash.New32()
io.WriteString(h, outer)
base.Ctxt.InlTree.AllParents(inlIndex, func(call obj.InlinedCall) {
io.WriteString(h, call.Name+":"+call.Pos.LineNumber()+":"+call.Pos.ColumnNumber())
})
inlHash = base64.StdEncoding.EncodeToString(h.Sum(nil)[:8])
outer = base.Ctxt.InlTree.InlinedFuncName(inlIndex)
if pkgPath := base.Ctxt.InlTree.InlinedFuncPkg(inlIndex); pkgPath != "" {
pkg = types.NewPkg(pkgPath, "")
}
}
// If this closure was created due to inlining, then incorporate any
// inlined functions' names into the closure's linker symbol name
// too (#60324).
if inlIndex := base.Ctxt.InnermostPos(pos).Base().InliningIndex(); inlIndex >= 0 {
names := []string{outer}
base.Ctxt.InlTree.AllParents(inlIndex, func(call obj.InlinedCall) {
names = append(names, call.Name)
})
outer = strings.Join(names, ".")
if gen == 0 {
p := &globClosgen
if !IsBlank(outerfn.Nname) {
switch why {
case OCLOSURE:
p = &outerfn.funcLitGen
case ORANGE:
p = &outerfn.rangeLitGen
default:
p = &outerfn.goDeferGen
}
}
*p++
gen = int(*p)
}
*gen++
return pkg.Lookup(fmt.Sprintf("%s%s%d", outer, suffix, *gen))
name := fmt.Sprintf("%s%s%d", outer, suffix, gen)
if inlHash != "" {
// Attach the inline hash (see the comment above).
// If it already has a hash, trim it, so we don't include
// two hashes for nested closures. The new hash should be
// enough to disambiguate.
name = obj.TrimInlineHash(name) + "#" + inlHash + "#"
}
return pkg.Lookup(name)
}
// NewClosureFunc creates a new Func to represent a function literal
@ -500,14 +527,19 @@ func closureName(outerfn *Func, pos src.XPos, why Op) *types.Sym {
// why is the reason we're generating this Func. It can be OCLOSURE
// (for a normal function literal) or OGO or ODEFER (for wrapping a
// call expression that has parameters or results).
func NewClosureFunc(fpos, cpos src.XPos, why Op, typ *types.Type, outerfn *Func, pkg *Package) *Func {
//
// gen is an optional counter for the closure name. If it is 0,
// the counter will be computed based on outerfn.
func NewClosureFunc(fpos, cpos src.XPos, why Op, typ *types.Type, outerfn *Func, pkg *Package, gen int) *Func {
if outerfn == nil {
base.FatalfAt(fpos, "outerfn is nil")
}
fn := NewFunc(fpos, fpos, closureName(outerfn, cpos, why), typ)
fn := NewFunc(fpos, fpos, closureName(outerfn, cpos, why, gen), typ)
fn.SetDupok(outerfn.Dupok()) // if the outer function is dupok, so is the closure
fn.Linksym().Set(obj.AttrContentAddressable, true)
clo := &ClosureExpr{Func: fn}
clo.op = OCLOSURE
clo.pos = cpos

View file

@ -167,6 +167,10 @@ func (*Name) CanBeNtype() {}
func (*Name) CanBeAnSSASym() {}
func (*Name) CanBeAnSSAAux() {}
// DiagName returns the symbol name for diagnostics.
// XXX should it be part of the formatter?
func (n *Name) DiagName() string { return obj.TrimInlineHash(fmt.Sprint(n.Sym())) }
// Pragma returns the PragmaFlag for p, which must be for an OTYPE.
func (n *Name) Pragma() PragmaFlag { return n.pragma }

View file

@ -100,6 +100,11 @@ type reader struct {
dict *readerDict
// funcLitGen is a counter for closure names.
funcLitGen int
// rangeLitGen is a counter for range func closure names.
rangeLitGen int
// TODO(mdempsky): The state below is all specific to reading
// function bodies. It probably makes sense to split it out
// separately so that it doesn't take up space in every reader
@ -3307,8 +3312,17 @@ func (r *reader) inlClosureFunc(origPos src.XPos, sig *types.Type, why ir.Op) *i
curfn = r.curfn
}
var gen int
if why == ir.ORANGE {
r.rangeLitGen++
gen = r.rangeLitGen
} else {
r.funcLitGen++
gen = r.funcLitGen
}
// TODO(mdempsky): Remove hard-coding of typecheck.Target.
return ir.NewClosureFunc(origPos, r.inlPos(origPos), why, sig, curfn, typecheck.Target)
return ir.NewClosureFunc(origPos, r.inlPos(origPos), why, sig, curfn, typecheck.Target, gen)
}
func (r *reader) exprList() []ir.Node {

View file

@ -224,7 +224,7 @@ func normalizeGoDeferCall(pos src.XPos, op ir.Op, call ir.Node, init *ir.Nodes)
}
// Create a new wrapper function without parameters or results.
wrapperFn := ir.NewClosureFunc(pos, pos, op, types.NewSignature(nil, nil, nil), ir.CurFunc, Target)
wrapperFn := ir.NewClosureFunc(pos, pos, op, types.NewSignature(nil, nil, nil), ir.CurFunc, Target, 0)
wrapperFn.DeclareParams(true)
wrapperFn.SetWrapper(true)

View file

@ -0,0 +1,80 @@
# Test closure naming with inlining.
go build -gcflags=-S -o x.exe x.go
# original closure name should appear
stderr 'adder\.func1'
# inlining should not cause the closure to have longer names
! stderr 'F1\.adder\.func1'
! stderr 'F2\.adder\.func1'
# the counter should match the original one, regardless of
# how many closures are in the inlined caller
! stderr 'adder\.func2'
! stderr 'adder\.func3'
! stderr 'adder\.func4'
# user closure counter and range func counter should not
# interfere
stderr 'ranger\.func1'
stderr 'ranger-range1'
stderr 'ranger\.func2'
go tool nm x.exe
# same applies to the nm output
stdout 'adder\.func1'
! stdout 'F1\.adder\.func1'
! stdout 'F2\.adder\.func1'
! stdout 'adder\.func2'
! stdout 'adder\.func3'
! stdout 'adder\.func4'
stdout 'ranger\.func1'
stdout 'ranger-range1'
stdout 'ranger\.func2'
# the inline hash should not make into the final binary
! stdout 'adder\.func1#'
-- x.go --
package main
// keep prevents closures being optimized out
//
//go:noinline
func keep(x func()) func() { return x }
func adder(n int) func(int) int {
return func(x int) int { return x + n }
}
//go:noinline
func F1() func(int) int {
// adder is inlined into F1, along with its closure.
// The closure should still be named addr.func1.
return adder(1)
}
//go:noinline
func F2() func(int) int {
keep(func() {})
keep(func() {})
keep(func() {})
// Closures in the outer function should not change
// the counter. It should still be addr.func1.
return adder(2)
}
//go:noinline
func iter(yield func(int) bool) {
_ = yield(1) && yield(2) && yield(3)
}
func ranger() func(int) int {
keep(func() {}) // func1
s := 0
for x := range iter { // range1
s += x
}
return func(x int) int { return s + x } // func2
}
func main() {
x := 1
F1()(F2()(ranger()(x)))
}

View file

@ -363,7 +363,7 @@ func (ctxt *Link) populateDWARF(curfn Func, s *LSym) {
panic("bad startPos")
}
fnstate := &dwarf.FnState{
Name: s.Name,
Name: TrimInlineHash(s.Name),
Info: info,
Loc: loc,
Ranges: ranges,
@ -435,7 +435,7 @@ func (ctxt *Link) DwarfAbstractFunc(curfn Func, s *LSym) {
scopes, _ := ctxt.DebugInfo(ctxt, s, absfn, curfn)
dwctxt := dwCtxt{ctxt}
fnstate := dwarf.FnState{
Name: s.Name,
Name: TrimInlineHash(s.Name),
Info: absfn,
Absfn: absfn,
StartPos: ctxt.InnermostPos(curfn.Pos()),

View file

@ -89,6 +89,14 @@ func (tree *InlTree) InlinedFunction(inlIndex int) *LSym {
return tree.nodes[inlIndex].Func
}
func (tree *InlTree) InlinedFuncName(inlIndex int) string {
return tree.nodes[inlIndex].Name
}
func (tree *InlTree) InlinedFuncPkg(inlIndex int) string {
return tree.nodes[inlIndex].Func.Pkg
}
func (tree *InlTree) CallPos(inlIndex int) src.XPos {
return tree.nodes[inlIndex].Pos
}

View file

@ -14,6 +14,7 @@ import (
"cmd/internal/objabi"
"cmd/internal/sys"
"cmp"
"encoding/base64"
"encoding/binary"
"fmt"
"internal/abi"
@ -276,6 +277,22 @@ func (w *writer) writeFile(ctxt *Link, file *FileInfo) {
}
}
var inlHashLen = base64.StdEncoding.EncodedLen(8)
// TrimInlineHash strips the content hash of inlined call stacks from a symbol name.
func TrimInlineHash(name string) string {
// The inline hash is in the form of #NNNN#, where NNNN is
// the base64 encoding of a 8-byte hash value.
a, b, ok := strings.Cut(name, "#")
if !ok {
return name
}
if len(b) < inlHashLen+1 || b[inlHashLen] != '#' {
return name
}
return a + b[inlHashLen+1:]
}
func (w *writer) StringTable() {
w.AddString("")
for _, p := range w.ctxt.Imports {
@ -300,7 +317,7 @@ func (w *writer) StringTable() {
if strings.HasPrefix(s.Name, `"".`) {
w.ctxt.Diag("unqualified symbol name: %v", s.Name)
}
w.AddString(s.Name)
w.AddString(TrimInlineHash(s.Name))
})
// All filenames are in the postable.
@ -373,7 +390,8 @@ func (w *writer) Sym(s *LSym) {
name = filepath.ToSlash(name)
}
align := uint32(s.Align)
if s.ContentAddressable() && s.Size != 0 && align == 0 {
if s.ContentAddressable() && s.Size != 0 && align == 0 && s.Type != objabi.STEXT && s.Type != objabi.STEXTFIPS {
// The linker applies a default alignment for text symbols, so they are okay.
// TODO: Check that alignment is set for all symbols.
w.ctxt.Diag("%s: is content-addressable but alignment is not set (size is %d)", s.Name, s.Size)
}
@ -381,7 +399,7 @@ func (w *writer) Sym(s *LSym) {
w.ctxt.Diag("%s: symbol too large (%d bytes > %d bytes)", s.Name, s.Size, MaxSymSize)
}
o := &w.tmpSym
o.SetName(name, w.Writer)
o.SetName(TrimInlineHash(name), w.Writer)
o.SetABI(abi)
o.SetType(uint8(s.Type))
o.SetFlag(flag)
@ -416,10 +434,16 @@ func (w *writer) Hash(s *LSym) {
// Some of these conditions are duplicated in cmd/link/internal/ld.(*Link).symtab.
// TODO: instead of duplicating them, have the compiler decide where symbols go.
func contentHashSection(s *LSym) byte {
name := s.Name
if s.Type == objabi.STEXT {
return 't'
}
if s.Type == objabi.STEXTFIPS {
return 'f'
}
if s.IsPcdata() {
return 'P'
}
name := s.Name
if strings.HasPrefix(name, "gcargs.") ||
strings.HasPrefix(name, "gclocals.") ||
strings.HasPrefix(name, "gclocals·") ||
@ -480,6 +504,14 @@ func (w *writer) contentHash(s *LSym) goobj.HashType {
tmp[8] = contentHashSection(s)
h.Write(tmp[:9])
if s.Type == objabi.STEXT || s.Type == objabi.STEXTFIPS {
// We don't want to combine irrelevant functions.
// Include the name to distinguish, without the temporary
// inline hash, so different inlined copies do get a chance
// to be combined.
io.WriteString(h, TrimInlineHash(s.Name))
}
// The compiler trims trailing zeros _sometimes_. We just do
// it always.
h.Write(bytes.TrimRight(s.P, "\x00"))
@ -492,9 +524,14 @@ func (w *writer) contentHash(s *LSym) goobj.HashType {
h.Write(tmp[:])
rs := r.Sym
if rs == nil {
fmt.Printf("symbol: %s\n", s)
fmt.Printf("relocation: %#v\n", r)
panic("nil symbol target in relocation")
// marker relocation
io.WriteString(h, "nil symbol")
continue
}
if rs == s {
io.WriteString(h, "self symbol")
continue
// TODO: mutual recursion?
}
switch rs.PkgIdx {
case goobj.PkgIdxHashed64:

View file

@ -7,6 +7,8 @@
package src
import "fmt"
// XPos is a more compact representation of Pos.
type XPos struct {
index int32
@ -93,6 +95,14 @@ func (p XPos) LineNumber() string {
return p.lico.lineNumber()
}
// ColumnNumber returns a string for the column number, "?" if it is not known.
func (p XPos) ColumnNumber() string {
if !p.IsKnown() {
return "?"
}
return fmt.Sprintf("%d", p.lico.Col())
}
// FileIndex returns a smallish non-negative integer corresponding to the
// file for this source position. Smallish is relative; it can be thousands
// large, but not millions.

View file

@ -1046,7 +1046,7 @@ func AddMachoSym(ldr *loader.Loader, s loader.Sym) {
// When dynamically linking, all non-local variables and plugin-exported
// symbols need to be exported.
func machoShouldExport(ctxt *Link, ldr *loader.Loader, s loader.Sym) bool {
if !ctxt.DynlinkingGo() || ldr.AttrLocal(s) {
if !ctxt.DynlinkingGo() || ldr.AttrLocal(s) || (ldr.IsContentHashed(s) && ldr.SymType(s).IsText()) {
return false
}
if ctxt.BuildMode == BuildModePlugin && strings.HasPrefix(ldr.SymExtname(s), objabi.PathToPrefix(*flagPluginPath)) {

View file

@ -937,7 +937,8 @@ func (f *peFile) writeSymbols(ctxt *Link) {
}
}
class := IMAGE_SYM_CLASS_EXTERNAL
if ldr.IsFileLocal(s) || ldr.AttrVisibilityHidden(s) || ldr.AttrLocal(s) {
if ldr.IsFileLocal(s) || ldr.AttrVisibilityHidden(s) || ldr.AttrLocal(s) ||
(ldr.IsContentHashed(s) && ldr.SymType(s).IsText()) {
class = IMAGE_SYM_CLASS_STATIC
}
f.writeSymbol(ctxt.Out, ldr, s, name, value, sect, peSymType, uint8(class))

View file

@ -110,9 +110,12 @@ func putelfsym(ctxt *Link, x loader.Sym, typ elf.SymType, curbind elf.SymBind) {
// One pass for each binding: elf.STB_LOCAL, elf.STB_GLOBAL,
// maybe one day elf.STB_WEAK.
bind := elf.STB_GLOBAL
if ldr.IsFileLocal(x) && !isStaticTmp(sname) || ldr.AttrVisibilityHidden(x) || ldr.AttrLocal(x) {
if ldr.IsFileLocal(x) && !isStaticTmp(sname) || ldr.AttrVisibilityHidden(x) || ldr.AttrLocal(x) ||
(ldr.IsContentHashed(x) && ldr.SymType(x).IsText()) {
// Static tmp is package local, but a package can be shared among multiple DSOs.
// They need to have a single view of the static tmp that are writable.
// Content-hashed text symbols may have the same name. Mark them local to avoid
// collision.
bind = elf.STB_LOCAL
}

View file

@ -829,7 +829,7 @@ func (f *xcoffFile) writeSymbolFunc(ctxt *Link, x loader.Sym) []xcoffSym {
Nnumaux: 2,
}
if ldr.IsFileLocal(x) || ldr.AttrVisibilityHidden(x) || ldr.AttrLocal(x) {
if ldr.IsFileLocal(x) || ldr.AttrVisibilityHidden(x) || ldr.AttrLocal(x) || ldr.IsContentHashed(x) {
s.Nsclass = C_HIDEXT
}

View file

@ -816,6 +816,16 @@ func (l *Loader) SymVersion(i Sym) int {
return abiToVer(r.Sym(li).ABI(), r.version)
}
func (l *Loader) IsContentHashed(i Sym) bool {
if l.IsExternal(i) {
return false
}
r, li := l.toLocal(i)
start := uint32(r.ndef + r.nhashed64def)
end := start + uint32(r.nhasheddef)
return start <= li && li < end
}
func (l *Loader) IsFileLocal(i Sym) bool {
return l.SymVersion(i) >= sym.SymVerStatic
}

View file

@ -110,7 +110,7 @@ func main() {
y := func(x int) int { // ERROR "func literal does not escape" "can inline main.func13.1"
return x + 2
}
y, sink = func() (func(int) int, int) { // ERROR "can inline main.func13.2" "can inline main.main.func13.func35"
y, sink = func() (func(int) int, int) { // ERROR "can inline main.func13.2" "can inline main.func13.func2"
return func(x int) int { // ERROR "can inline main.func13.2" "func literal escapes to heap"
return x + 1
}, 42
@ -118,7 +118,7 @@ func main() {
if y(40) != 41 {
ppanic("y(40) != 41")
}
}() // ERROR "func literal does not escape" "inlining call to main.func13" "inlining call to main.main.func13.func35"
}() // ERROR "func literal does not escape" "inlining call to main.func13$" "inlining call to main.func13.func2"
}
{
@ -191,17 +191,17 @@ func main() {
{
x := 42
if z := func(y int) int { // ERROR "can inline main.func22"
return func() int { // ERROR "can inline main.func22.1" "can inline main.main.func22.func40"
return func() int { // ERROR "can inline main.func22.1" "can inline main.func22.func1"
return x + y
}() // ERROR "inlining call to main.func22.1"
}(1); z != 43 { // ERROR "inlining call to main.func22" "inlining call to main.main.func22.func40"
}(1); z != 43 { // ERROR "inlining call to main.func22$" "inlining call to main.func22.func1"
ppanic("z != 43")
}
if z := func(y int) int { // ERROR "func literal does not escape" "can inline main.func23"
return func() int { // ERROR "can inline main.func23.1" "can inline main.main.func23.func41"
return func() int { // ERROR "can inline main.func23.1" "can inline main.func23.func1"
return x + y
}() // ERROR "inlining call to main.func23.1"
}; z(1) != 43 { // ERROR "inlining call to main.func23" "inlining call to main.main.func23.func41"
}; z(1) != 43 { // ERROR "inlining call to main.func23$" "inlining call to main.func23.func1"
_ = z // prevent simple deadcode elimination after inlining
ppanic("z(1) != 43")
}
@ -210,10 +210,10 @@ func main() {
{
a := 1
func() { // ERROR "can inline main.func24"
func() { // ERROR "can inline main.func24" "can inline main.main.func24.func42"
func() { // ERROR "can inline main.func24.1" "can inline main.func24.func1"
a = 2
}() // ERROR "inlining call to main.func24"
}() // ERROR "inlining call to main.func24" "inlining call to main.main.func24.func42"
}() // ERROR "inlining call to main.func24.1"
}() // ERROR "inlining call to main.func24$" "inlining call to main.func24.func1"
if a != 2 {
ppanic("a != 2")
}
@ -222,13 +222,13 @@ func main() {
{
b := 2
func(b int) { // ERROR "can inline main.func25"
func() { // ERROR "can inline main.func25.1" "can inline main.main.func25.func43"
func() { // ERROR "can inline main.func25.1" "can inline main.func25.func1"
b = 3
}() // ERROR "inlining call to main.func25.1"
if b != 3 {
ppanic("b != 3")
}
}(b) // ERROR "inlining call to main.func25" "inlining call to main.main.func25.func43"
}(b) // ERROR "inlining call to main.func25"
if b != 2 {
ppanic("b != 2")
}
@ -258,13 +258,13 @@ func main() {
// revisit those. E.g., func34 and func36 are constructed by the inliner.
if r := func(x int) int { // ERROR "can inline main.func27"
b := 3
return func(y int) int { // ERROR "can inline main.func27.1" "can inline main.main.func27.func45"
return func(y int) int { // ERROR "can inline main.func27.1" "can inline main.func27.func1"
c := 5
return func(z int) int { // ERROR "can inline main.func27.1.1" "can inline main.main.func27.func45.1" "can inline main.func27.main.func27.1.2" "can inline main.main.func27.main.main.func27.func45.func48"
return func(z int) int { // ERROR "can inline main.func27.1.1" "can inline main.func27.func1.1" "can inline main.func27.func1.func1"
return a*x + b*y + c*z
}(10) // ERROR "inlining call to main.func27.1.1"
}(100) // ERROR "inlining call to main.func27.1" "inlining call to main.func27.main.func27.1.2"
}(1000); r != 2350 { // ERROR "inlining call to main.func27" "inlining call to main.main.func27.func45" "inlining call to main.main.func27.main.main.func27.func45.func48"
}(100) // ERROR "inlining call to main.func27.1$" "inlining call to main.func27.1.1"
}(1000); r != 2350 { // ERROR "inlining call to main.func27$" "inlining call to main.func27.func1$" "inlining call to main.func27.func1.func1"
ppanic("r != 2350")
}
}
@ -273,16 +273,16 @@ func main() {
a := 2
if r := func(x int) int { // ERROR "can inline main.func28"
b := 3
return func(y int) int { // ERROR "can inline main.func28.1" "can inline main.main.func28.func46"
return func(y int) int { // ERROR "can inline main.func28.1" "can inline main.func28.func1"
c := 5
func(z int) { // ERROR "can inline main.func28.1.1" "can inline main.func28.main.func28.1.2" "can inline main.main.func28.func46.1" "can inline main.main.func28.main.main.func28.func46.func49"
func(z int) { // ERROR "can inline main.func28.1.1" "can inline main.func28.func1.1" "can inline main.func28.func1.func1"
a = a * x
b = b * y
c = c * z
}(10) // ERROR "inlining call to main.func28.1.1"
return a + c
}(100) + b // ERROR "inlining call to main.func28.1" "inlining call to main.func28.main.func28.1.2"
}(1000); r != 2350 { // ERROR "inlining call to main.func28" "inlining call to main.main.func28.func46" "inlining call to main.main.func28.main.main.func28.func46.func49"
}(100) + b // ERROR "inlining call to main.func28.1$" "inlining call to main.func28.1.1"
}(1000); r != 2350 { // ERROR "inlining call to main.func28$" "inlining call to main.func28.func1$" "inlining call to main.func28.func1.func1"
ppanic("r != 2350")
}
if a != 2000 {

View file

@ -7,26 +7,26 @@
package codegen
func main() {
// amd64:"LEAQ command-line-arguments\\.main\\.f\\.g\\.h\\.func3"
// amd64:"LEAQ command-line-arguments\\.h\\.func1"
f(1)()
// amd64:"LEAQ command-line-arguments\\.main\\.g\\.h\\.func2"
// amd64:"LEAQ command-line-arguments\\.h\\.func1"
g(2)()
// amd64:"LEAQ command-line-arguments\\.main\\.h\\.func1"
// amd64:"LEAQ command-line-arguments\\.h\\.func1"
h(3)()
// amd64:"LEAQ command-line-arguments\\.main\\.f\\.g\\.h\\.func4"
// amd64:"LEAQ command-line-arguments\\.h\\.func1"
f(4)()
}
func f(x int) func() {
// amd64:"LEAQ command-line-arguments\\.f\\.g\\.h\\.func1"
// amd64:"LEAQ command-line-arguments\\.h\\.func1"
return g(x)
}
func g(x int) func() {
// amd64:"LEAQ command-line-arguments\\.g\\.h\\.func1"
// amd64:"LEAQ command-line-arguments\\.h\\.func1"
return h(x)
}

View file

@ -119,13 +119,13 @@ func r(z int) int {
return x + z
}
bar := func(x int) int { // ERROR "func literal does not escape" "can inline r.func2"
return x + func(y int) int { // ERROR "can inline r.func2.1" "can inline r.r.func2.func3"
return x + func(y int) int { // ERROR "can inline r.func2.1" "can inline r.func2.func1"
return 2*y + x*z
}(x) // ERROR "inlining call to r.func2.1"
}
_, _ = foo, bar // prevent simple deadcode elimination after inlining
return foo(42) + bar(42) // ERROR "inlining call to r.func1" "inlining call to r.func2" "inlining call to r.r.func2.func3"
return foo(42) + bar(42) // ERROR "inlining call to r.func1" "inlining call to r.func2$" "inlining call to r.func2.func1"
}
func s0(x int) int { // ERROR "can inline s0"