From 326e5e1b7a9f421db972fed0a6e79a1c9601d0ae Mon Sep 17 00:00:00 2001 From: thepudds Date: Wed, 12 Feb 2025 18:39:08 -0500 Subject: [PATCH] cmd/compile/internal/escape: additional constant and zero value tests and logging This adds additional logging for the work that walk does to reduce how often an interface conversion results in an allocation. Also, as part of #71359, we will be updating how escape analysis and walk handle basic literals, composite literals, and zero values, so add some tests that uses this new logging. By the end of our CL stack, we address all of these tests. Updates #71359 Change-Id: I43fde8343d9aacaec1e05360417908014a86c8bd Reviewed-on: https://go-review.googlesource.com/c/go/+/649076 Reviewed-by: Keith Randall Reviewed-by: Keith Randall Reviewed-by: David Chase Auto-Submit: Keith Randall LUCI-TryBot-Result: Go LUCI --- src/cmd/compile/internal/base/debug.go | 1 + src/cmd/compile/internal/walk/convert.go | 13 ++ test/escape_iface_data.go | 257 +++++++++++++++++++++++ 3 files changed, 271 insertions(+) create mode 100644 test/escape_iface_data.go 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 +} -- 2.51.0