if n == nil || fn == nil {
return
}
- if n.Op() != ir.OMAKESLICE {
- // TODO(thepudds): we handle more cases later in our CL stack.
+ if n.Op() != ir.OMAKESLICE && n.Op() != ir.OCONVIFACE {
+ return
+ }
+ if base.Flag.Cfg.CoverageInfo != nil {
+ // Avoid altering coverage results.
return
}
*r = lit
}
}
+ case ir.OCONVIFACE:
+ // Check if we can replace a non-constant expression in an interface conversion with
+ // a literal to avoid heap allocating the underlying interface value.
+ conv := n.(*ir.ConvExpr)
+ if conv.X.Op() != ir.OLITERAL && !conv.X.Type().IsInterface() {
+ v := ro.StaticValue(conv.X)
+ if v != nil && v.Op() == ir.OLITERAL && ir.ValidTypeForConst(conv.X.Type(), v.Val()) {
+ if base.Debug.EscapeDebug >= 3 {
+ base.WarnfAt(n.Pos(), "rewriting OCONVIFACE value from %v (%v) to %v (%v)", conv.X, conv.X.Type(), v, v.Type())
+ }
+ v := v.(*ir.BasicLit)
+ conv.X = ir.NewBasicLit(conv.X.Pos(), conv.X.Type(), v.Val())
+ typecheck.Expr(conv)
+ }
+ }
}
}
{1, `Sprintf("%x %x")`, func() { _ = Sprintf("%x %x", 7, 112) }},
{1, `Sprintf("%g")`, func() { _ = Sprintf("%g", float32(3.14159)) }},
{0, `Fprintf(buf, "%s")`, func() { mallocBuf.Reset(); Fprintf(&mallocBuf, "%s", "hello") }},
+ {0, `Fprintf(buf, "%s")`, func() { mallocBuf.Reset(); s := "hello"; Fprintf(&mallocBuf, "%s", s) }},
+ {1, `Fprintf(buf, "%s")`, func() { mallocBuf.Reset(); s := "hello"; Fprintf(&mallocBuf, "%s", noliteral(s)) }},
{0, `Fprintf(buf, "%x")`, func() { mallocBuf.Reset(); Fprintf(&mallocBuf, "%x", 7) }},
{0, `Fprintf(buf, "%x")`, func() { mallocBuf.Reset(); Fprintf(&mallocBuf, "%x", 1<<16) }},
- {1, `Fprintf(buf, "%x")`, func() { mallocBuf.Reset(); i := 1 << 16; Fprintf(&mallocBuf, "%x", i) }}, // not constant
+ {0, `Fprintf(buf, "%x")`, func() { mallocBuf.Reset(); i := 1 << 16; Fprintf(&mallocBuf, "%x", i) }},
+ {1, `Fprintf(buf, "%x")`, func() { mallocBuf.Reset(); i := 1 << 16; Fprintf(&mallocBuf, "%x", noliteral(i)) }},
{4, `Fprintf(buf, "%v")`, func() { mallocBuf.Reset(); s := []int{1, 2}; Fprintf(&mallocBuf, "%v", s) }},
{1, `Fprintf(buf, "%v")`, func() { mallocBuf.Reset(); type P struct{ x, y int }; Fprintf(&mallocBuf, "%v", P{1, 2}) }},
+ {1, `Fprintf(buf, "%v")`, func() { mallocBuf.Reset(); type P struct{ x, y int }; Fprintf(&mallocBuf, "%v", noliteral(P{1, 2})) }},
{2, `Fprintf(buf, "%80000s")`, func() { mallocBuf.Reset(); Fprintf(&mallocBuf, "%80000s", "hello") }}, // large buffer (>64KB)
// If the interface value doesn't need to allocate, amortized allocation overhead should be zero.
{0, `Fprintf(buf, "%x %x %x")`, func() {
}
for _, mt := range mallocTest {
mallocs := testing.AllocsPerRun(100, mt.fn)
- if got, max := mallocs, float64(mt.count); got > max {
- t.Errorf("%s: got %v allocs, want <=%v", mt.desc, got, max)
+ if got, max := mallocs, float64(mt.count); got != max {
+ t.Errorf("%s: got %v allocs, want %v", mt.desc, got, max)
}
}
}
t.Fatalf("Appendln allocated a new slice")
}
}
+
+// noliteral prevents escape analysis from recognizing a literal value.
+//
+//go:noinline
+func noliteral[T any](t T) T {
+ return t
+}
t.Run("2 pairs", func(t *testing.T) {
s := "abc"
i := 2000
- wantAllocs(t, 2, func() {
+ wantAllocs(t, 0, func() {
dl.Info("hello",
"n", i,
"s", s,
l := New(DiscardHandler)
s := "abc"
i := 2000
- wantAllocs(t, 2, func() {
+ wantAllocs(t, 0, func() {
l.Log(ctx, LevelInfo, "hello",
"n", i,
"s", s,
s := "abc"
i := 2000
d := time.Second
- wantAllocs(t, 10, func() {
+ wantAllocs(t, 1, func() {
dl.Info("hello",
"n", i, "s", s, "d", d,
"n", i, "s", s, "d", d,
func g29000() {
x := 1
- f29000(2, x) // ERROR "x escapes to heap"
+ f29000(2, x) // ERROR "1 escapes to heap"
}
// Issue 28369: taking an address of a parameter and converting it into a uintptr causes an
j := 0
var v int
var ok bool
- var x interface{} = i // ERROR "i does not escape"
- var y interface{} = j // ERROR "j does not escape"
+ var x interface{} = i // ERROR "0 does not escape"
+ var y interface{} = j // ERROR "0 does not escape"
*(&v) = x.(int)
*(&v), *(&ok) = y.(int)
i := 0
j := 0
var ok bool
- var x interface{} = i // ERROR "i does not escape"
- var y interface{} = j // ERROR "j does not escape"
+ var x interface{} = i // ERROR "0 does not escape"
+ var y interface{} = j // ERROR "0 does not escape"
sink = x.(int) // ERROR "x.\(int\) escapes to heap"
sink, *(&ok) = y.(int) // ERROR "autotmp_.* escapes to heap"
func string2() {
v := "abc"
- sink = v
+ sink = v // ERROR "using global for interface value"
}
func string3() {
func string4() {
v := ""
- sink = v
+ sink = v // ERROR "using global for interface value"
}
func string5() {
func string6() {
var a any
- v := "abc" // ERROR "using stack temporary for interface value"
- a = v
+ v := "abc"
+ a = v // ERROR "using global for interface value"
_ = a
}
func string8() {
v0 := "abc"
v := v0
- string7(v)
+ string7(v) // ERROR "using global for interface value"
}
func string9() {
f := func() {
string7(v)
}
- f()
+ f() // ERROR "using global for interface value"
}
func string10() {
}
f2()
}
- f()
+ f() // ERROR "using global for interface value"
}
func string11() {
v0 := "abc"
v := v0
defer func() {
- string7(v)
+ string7(v) // ERROR "using global for interface value"
}()
}
func integer2() {
v := 42
- sink = v
+ sink = v // ERROR "using global for interface value"
}
func integer3() {
func integer4a() {
v := 0
- sink = v
+ sink = v // ERROR "using global for interface value"
}
func integer4b() {
func integer6() {
var a any
- v := 42 // ERROR "using stack temporary for interface value"
- a = v
+ v := 42
+ a = v // ERROR "using global for interface value"
_ = a
}
func named2a() {
v := MyInt(0)
- sink = v
+ sink = v // ERROR "using global for interface value"
}
func named2b() {
v := MyInt(42)
- escapes(v)
+ escapes(v) // ERROR "using global for interface value"
}
-// Unfortunate: we currently require matching types, which we could relax.
func named2c() {
v := 42
- sink = MyInt(v)
+ sink = MyInt(v) // ERROR "using global for interface value"
}
-// Unfortunate: we currently require matching types, which we could relax.
func named2d() {
v := 42
- escapes(MyInt(v))
+ escapes(MyInt(v)) // ERROR "using global for interface value"
}
func named3a() {
sink = MyInt(42) // ERROR "using global for interface value"
func named4a() {
v := MyInt(0)
- sink = v
+ sink = v // ERROR "using global for interface value"
}
func named4b() {
v := MyInt(0)
- escapes(v)
+ escapes(v) // ERROR "using global for interface value"
}
func named4c() {
v := 0
- sink = MyInt(v)
+ sink = MyInt(v) // ERROR "using global for interface value"
}
func named4d() {
v := 0
- escapes(MyInt(v))
+ escapes(MyInt(v)) // ERROR "using global for interface value"
}
func named5() {
func named6() {
var a any
- v := MyInt(42) // ERROR "using stack temporary for interface value"
- a = v
+ v := MyInt(42)
+ a = v // ERROR "using global for interface value"
_ = a
}
func emptyStruct3(v struct{}) { // ERROR "using global for zero-sized interface value"
sink = v
}
+
+// Some light emulation of conditional debug printing (such as in #53465).
+
+func Printf(format string, args ...any) {
+ for _, arg := range args {
+ sink = arg
+ }
+}
+
+var enabled = true
+
+func debugf(format string, args ...interface{}) {
+ if enabled {
+ Printf(format, args...)
+ }
+}
+
+//go:noinline
+func debugf2(format string, args ...interface{}) {
+ if enabled {
+ Printf(format, args...)
+ }
+}
+
+func f1() {
+ v := 1000
+ debugf("hello %d", v) // ERROR "using global for interface value"
+}
+
+func f2() {
+ v := 1000
+ debugf2("hello %d", v) // ERROR "using global for interface value"
+}
+
+//go:noinline
+func f3(i int) {
+ debugf("hello %d", i)
+}
+
+func f4() {
+ f3(1000)
+}
a := int32(1) // ERROR "moved to heap: a"
b := "cat"
c := &a
- FooI(a, b, c) // ERROR "a escapes to heap" "b escapes to heap" "... argument does not escape"
+ FooI(a, b, c) // ERROR "a escapes to heap" ".cat. escapes to heap" "... argument does not escape"
}
func FooJ(args ...interface{}) *int32 { // ERROR "leaking param: args to result ~r0 level=1"
a := int32(1)
b := "cat"
c := &a
- FooJ(a, b, c) // ERROR "a does not escape" "b does not escape" "... argument does not escape"
+ FooJ(a, b, c) // ERROR "a does not escape" ".cat. does not escape" "... argument does not escape"
}
func TFooJ2() {
a := int32(1) // ERROR "moved to heap: a"
b := "cat"
c := &a
- isink = FooJ(a, b, c) // ERROR "a escapes to heap" "b escapes to heap" "... argument does not escape"
+ isink = FooJ(a, b, c) // ERROR "a escapes to heap" ".cat. escapes to heap" "... argument does not escape"
}
type fakeSlice struct {
a := int32(1) // ERROR "moved to heap: a"
b := "cat"
c := &a
- fs := fakeSlice{3, &[4]interface{}{a, b, c, nil}} // ERROR "a escapes to heap" "b escapes to heap" "&\[4\]interface {}{...} does not escape"
+ fs := fakeSlice{3, &[4]interface{}{a, b, c, nil}} // ERROR "a escapes to heap" ".cat. escapes to heap" "&\[4\]interface {}{...} does not escape"
isink = FooK(fs)
}
a := int32(1) // ERROR "moved to heap: a"
b := "cat"
c := &a
- s := []interface{}{a, b, c} // ERROR "a escapes to heap" "b escapes to heap" "\[\]interface {}{...} does not escape"
+ s := []interface{}{a, b, c} // ERROR "a escapes to heap" ".cat. escapes to heap" "\[\]interface {}{...} does not escape"
isink = FooL(s)
}
func bar() { // ERROR "can inline bar"
value := 10
- debugf("value is %d", value) // ERROR "inlining call to debugf" "value does not escape" "\.\.\. argument does not escape"
+ debugf("value is %d", value) // ERROR "inlining call to debugf" "10 does not escape" "\.\.\. argument does not escape"
}