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 <khr@golang.org>
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: David Chase <drchase@google.com>
Auto-Submit: Keith Randall <khr@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
thepudds 2025-02-12 18:39:08 -05:00 committed by Gopher Robot
parent 1635aed941
commit 326e5e1b7a
3 changed files with 271 additions and 0 deletions

View File

@ -29,6 +29,7 @@ type DebugFlags struct {
DumpPtrs int `help:"show Node pointers values in dump output"` DumpPtrs int `help:"show Node pointers values in dump output"`
DwarfInl int `help:"print information about DWARF inlined function creation"` DwarfInl int `help:"print information about DWARF inlined function creation"`
EscapeMutationsCalls int `help:"print extra escape analysis diagnostics about mutations and calls" concurrent:"ok"` 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"` Export int `help:"print export data"`
FIPSHash string `help:"hash value for FIPS debugging" concurrent:"ok"` 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"` Fmahash string `help:"hash value for use in debugging platform-dependent multiply-add use" concurrent:"ok"`

View File

@ -141,16 +141,27 @@ func dataWord(conv *ir.ConvExpr, init *ir.Nodes) ir.Node {
isInteger = sc.IsInteger() isInteger = sc.IsInteger()
isBool = sc.IsBoolean() 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. // Try a bunch of cases to avoid an allocation.
var value ir.Node var value ir.Node
switch { switch {
case fromType.Size() == 0: case fromType.Size() == 0:
// n is zero-sized. Use zerobase. // 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. cheapExpr(n, init) // Evaluate n for side-effects. See issue 19246.
value = ir.NewLinksymExpr(base.Pos, ir.Syms.Zerobase, types.Types[types.TUINTPTR]) value = ir.NewLinksymExpr(base.Pos, ir.Syms.Zerobase, types.Types[types.TUINTPTR])
case isBool || fromType.Size() == 1 && isInteger: case isBool || fromType.Size() == 1 && isInteger:
// n is a bool/byte. Use staticuint64s[n * 8] on little-endian // n is a bool/byte. Use staticuint64s[n * 8] on little-endian
// and staticuint64s[n * 8 + 7] on big-endian. // and staticuint64s[n * 8 + 7] on big-endian.
diagnose("using global for single-byte interface value", n)
n = cheapExpr(n, init) n = cheapExpr(n, init)
n = soleComponent(init, n) n = soleComponent(init, n)
// byteindex widens n so that the multiplication doesn't overflow. // 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 value = xe
case n.Op() == ir.ONAME && n.(*ir.Name).Class == ir.PEXTERN && n.(*ir.Name).Readonly(): case n.Op() == ir.ONAME && n.(*ir.Name).Class == ir.PEXTERN && n.(*ir.Name).Readonly():
// n is a readonly global; use it directly. // n is a readonly global; use it directly.
diagnose("using global for interface value", n)
value = n value = n
case conv.Esc() == ir.EscNone && fromType.Size() <= 1024: case conv.Esc() == ir.EscNone && fromType.Size() <= 1024:
// n does not escape. Use a stack temporary initialized to n. // 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) value = typecheck.TempAt(base.Pos, ir.CurFunc, fromType)
init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, value, n))) init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, value, n)))
} }

257
test/escape_iface_data.go Normal file
View File

@ -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
}