diff --git a/src/cmd/compile/internal/base/debug.go b/src/cmd/compile/internal/base/debug.go index 7bcbcb3e2c..10393e773c 100644 --- a/src/cmd/compile/internal/base/debug.go +++ b/src/cmd/compile/internal/base/debug.go @@ -29,6 +29,7 @@ type DebugFlags struct { DumpPtrs int `help:"show Node pointers values in dump output"` DwarfInl int `help:"print information about DWARF inlined function creation"` EscapeMutationsCalls int `help:"print extra escape analysis diagnostics about mutations and calls" concurrent:"ok"` + EscapeDebug int `help:"print information about escape analysis and resulting optimizations" concurrent:"ok"` Export int `help:"print export data"` FIPSHash string `help:"hash value for FIPS debugging" concurrent:"ok"` Fmahash string `help:"hash value for use in debugging platform-dependent multiply-add use" concurrent:"ok"` diff --git a/src/cmd/compile/internal/walk/convert.go b/src/cmd/compile/internal/walk/convert.go index fc1e4c84e7..4c443f71b9 100644 --- a/src/cmd/compile/internal/walk/convert.go +++ b/src/cmd/compile/internal/walk/convert.go @@ -141,16 +141,27 @@ func dataWord(conv *ir.ConvExpr, init *ir.Nodes) ir.Node { isInteger = sc.IsInteger() isBool = sc.IsBoolean() } + + diagnose := func(msg string, n ir.Node) { + if base.Debug.EscapeDebug > 0 { + // This output is most useful with -gcflags=-W=2 or similar because + // it often prints a temp variable name. + base.WarnfAt(n.Pos(), "convert: %s: %v", msg, n) + } + } + // Try a bunch of cases to avoid an allocation. var value ir.Node switch { case fromType.Size() == 0: // n is zero-sized. Use zerobase. + diagnose("using global for zero-sized interface value", n) cheapExpr(n, init) // Evaluate n for side-effects. See issue 19246. value = ir.NewLinksymExpr(base.Pos, ir.Syms.Zerobase, types.Types[types.TUINTPTR]) case isBool || fromType.Size() == 1 && isInteger: // n is a bool/byte. Use staticuint64s[n * 8] on little-endian // and staticuint64s[n * 8 + 7] on big-endian. + diagnose("using global for single-byte interface value", n) n = cheapExpr(n, init) n = soleComponent(init, n) // byteindex widens n so that the multiplication doesn't overflow. @@ -166,9 +177,11 @@ func dataWord(conv *ir.ConvExpr, init *ir.Nodes) ir.Node { value = xe case n.Op() == ir.ONAME && n.(*ir.Name).Class == ir.PEXTERN && n.(*ir.Name).Readonly(): // n is a readonly global; use it directly. + diagnose("using global for interface value", n) value = n case conv.Esc() == ir.EscNone && fromType.Size() <= 1024: // n does not escape. Use a stack temporary initialized to n. + diagnose("using stack temporary for interface value", n) value = typecheck.TempAt(base.Pos, ir.CurFunc, fromType) init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, value, n))) } diff --git a/test/escape_iface_data.go b/test/escape_iface_data.go new file mode 100644 index 0000000000..556be2067c --- /dev/null +++ b/test/escape_iface_data.go @@ -0,0 +1,257 @@ +// errorcheck -0 -d=escapedebug=1 + +// Copyright 2024 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. + +// Test the data word used for interface conversions +// that might otherwise allocate. + +package dataword + +var sink interface{} + +func string1() { + sink = "abc" // ERROR "using global for interface value" +} + +func string2() { + v := "abc" + sink = v +} + +func string3() { + sink = "" // ERROR "using global for interface value" +} + +func string4() { + v := "" + sink = v +} + +func string5() { + var a any = "abc" // ERROR "using global for interface value" + _ = a +} + +func string6() { + var a any + v := "abc" // ERROR "using stack temporary for interface value" + a = v + _ = a +} + +// string7 can be inlined. +func string7(v string) { + sink = v +} + +func string8() { + v0 := "abc" + v := v0 + string7(v) +} + +func string9() { + v0 := "abc" + v := v0 + f := func() { + string7(v) + } + f() +} + +func string10() { + v0 := "abc" + v := v0 + f := func() { + f2 := func() { + string7(v) + } + f2() + } + f() +} + +func string11() { + v0 := "abc" + v := v0 + defer func() { + string7(v) + }() +} + +func integer1() { + sink = 42 // ERROR "using global for interface value" +} + +func integer2() { + v := 42 + sink = v +} + +func integer3() { + sink = 0 // ERROR "using global for interface value" +} + +func integer4a() { + v := 0 + sink = v +} + +func integer4b() { + v := uint8(0) + sink = v // ERROR "using global for single-byte interface value" +} + +func integer5() { + var a any = 42 // ERROR "using global for interface value" + _ = a +} + +func integer6() { + var a any + v := 42 // ERROR "using stack temporary for interface value" + a = v + _ = a +} + +func integer7(v int) { + sink = v +} + +type M interface{ M() } + +type MyInt int + +func (m MyInt) M() {} + +func escapes(m M) { + sink = m +} + +func named1a() { + sink = MyInt(42) // ERROR "using global for interface value" +} + +func named1b() { + escapes(MyInt(42)) // ERROR "using global for interface value" +} + +func named2a() { + v := MyInt(0) + sink = v +} + +func named2b() { + v := MyInt(42) + escapes(v) +} + +// Unfortunate: we currently require matching types, which we could relax. +func named2c() { + v := 42 + sink = MyInt(v) +} + +// Unfortunate: we currently require matching types, which we could relax. +func named2d() { + v := 42 + escapes(MyInt(v)) +} +func named3a() { + sink = MyInt(42) // ERROR "using global for interface value" +} + +func named3b() { + escapes(MyInt(0)) // ERROR "using global for interface value" +} + +func named4a() { + v := MyInt(0) + sink = v +} + +func named4b() { + v := MyInt(0) + escapes(v) +} + +func named4c() { + v := 0 + sink = MyInt(v) +} + +func named4d() { + v := 0 + escapes(MyInt(v)) +} + +func named5() { + var a any = MyInt(42) // ERROR "using global for interface value" + _ = a +} + +func named6() { + var a any + v := MyInt(42) // ERROR "using stack temporary for interface value" + a = v + _ = a +} + +func named7a(v MyInt) { + sink = v +} + +func named7b(v MyInt) { + escapes(v) +} + +type S struct{ a, b int64 } + +func struct1() { + sink = S{1, 1} +} + +func struct2() { + v := S{1, 1} + sink = v +} + +func struct3() { + sink = S{} +} + +func struct4() { + v := S{} + sink = v +} + +func struct5() { + var a any = S{1, 1} // ERROR "using stack temporary for interface value" + _ = a +} + +func struct6() { + var a any + v := S{1, 1} + a = v // ERROR "using stack temporary for interface value" + _ = a +} + +func struct7(v S) { + sink = v +} + +func emptyStruct1() { + sink = struct{}{} // ERROR "using global for zero-sized interface value" +} + +func emptyStruct2() { + v := struct{}{} + sink = v // ERROR "using global for zero-sized interface value" +} + +func emptyStruct3(v struct{}) { // ERROR "using global for zero-sized interface value" + sink = v +}