From 99827cff5efb2c3492bc71d20a2f30c09eb6e798 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Mon, 17 Feb 2025 17:45:02 +0100 Subject: [PATCH 01/74] cmd/compile: improve concrete type analysis for devirtualization Change-Id: Iebc7737713d73690f861955a01ae6f7fa25e32ab --- .../internal/devirtualize/devirtualize.go | 221 ++++++- src/cmd/compile/internal/noder/reader.go | 1 + src/crypto/sha256/sha256_test.go | 14 + src/runtime/pprof/pprof_test.go | 3 + ...scape_iface_with_devirt_type_assertions.go | 609 ++++++++++++++++++ 5 files changed, 840 insertions(+), 8 deletions(-) create mode 100644 test/escape_iface_with_devirt_type_assertions.go diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 372d05809401ff..854154087c4f79 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -18,6 +18,8 @@ import ( "cmd/compile/internal/types" ) +const go125ImprovedConcreteTypeAnalysis = true + // StaticCall devirtualizes the given call if possible when the concrete callee // is available statically. func StaticCall(call *ir.CallExpr) { @@ -40,15 +42,31 @@ func StaticCall(call *ir.CallExpr) { } sel := call.Fun.(*ir.SelectorExpr) - r := ir.StaticValue(sel.X) - if r.Op() != ir.OCONVIFACE { - return - } - recv := r.(*ir.ConvExpr) + var typ *types.Type + if go125ImprovedConcreteTypeAnalysis { + typ = concreteType(sel.X) + if typ == nil { + return + } - typ := recv.X.Type() - if typ.IsInterface() { - return + // Don't try to devirtualize calls that we statically know that would have failed at runtime. + // This can happen in such case: any(0).(interface {A()}).A(), this typechecks without + // any errors, but will cause a runtime panic. We statically know that int(0) does not + // implement that interface, thus we skip the devirtualization, as it is not possible + // to make a type assertion from interface{A()} to int (int does not implement interface{A()}). + if !typecheck.Implements(typ, sel.X.Type()) { + return + } + } else { + r := ir.StaticValue(sel.X) + if r.Op() != ir.OCONVIFACE { + return + } + recv := r.(*ir.ConvExpr) + typ = recv.X.Type() + if typ.IsInterface() { + return + } } // If typ is a shape type, then it was a type argument originally @@ -138,3 +156,190 @@ func StaticCall(call *ir.CallExpr) { // Desugar OCALLMETH, if we created one (#57309). typecheck.FixMethodCall(call) } + +func concreteType(n ir.Node) *types.Type { + return concreteType1(n, make(map[*ir.Name]*types.Type)) +} + +func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Type { + for { + switch n1 := n.(type) { + case *ir.ConvExpr: + if n1.Op() == ir.OCONVNOP && types.Identical(n1.Type(), n1.X.Type()) { + n = n1.X + continue + } + if n1.Op() == ir.OCONVIFACE { + n = n1.X + continue + } + case *ir.InlinedCallExpr: + if n1.Op() == ir.OINLCALL { + n = n1.SingleResult() + continue + } + case *ir.ParenExpr: + n = n1.X + continue + case *ir.TypeAssertExpr: + n = n1.X + continue + case *ir.CallExpr: + if n1.Fun != nil { + results := n1.Fun.Type().Results() + if len(results) == 1 { + retTyp := results[0].Type + if !retTyp.IsInterface() { + return retTyp + } + } + } + return nil + } + + if !n.Type().IsInterface() { + return n.Type() + } + + return concreteType2(n, analyzed) + } +} + +func concreteType2(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Type { + if n.Op() != ir.ONAME { + return nil + } + + name := n.(*ir.Name).Canonical() + if name.Class != ir.PAUTO { + return nil + } + + if name.Op() != ir.ONAME { + base.Fatalf("reassigned %v", name) + } + + if name.Addrtaken() { + return nil // conservatively assume it's reassigned with a different type indirectly + } + + if typ, ok := analyzed[name]; ok { + return typ + } + + // For now set the Type to nil, as we don't know it yet, we will update + // it at the end of this function, if we find a concrete type. + // This is not ideal, as in-process concreteType1 calls (that this function also + // executes) will get a nil (from the map lookup above), where we could determine the type. + analyzed[name] = nil + + // isName reports whether n is a reference to name. + isName := func(x ir.Node) bool { + if x == nil { + return false + } + n, ok := ir.OuterValue(x).(*ir.Name) + return ok && n.Canonical() == name + } + + var typ *types.Type + + handleType := func(t *types.Type) bool { + if t == nil || t.IsInterface() { + typ = nil + return true + } + + if typ == nil || types.Identical(typ, t) { + typ = t + return false + } + + // Different type. + typ = nil + return true + } + + handleNode := func(n ir.Node) bool { + if n == nil { + return false + } + return handleType(concreteType1(n, analyzed)) + } + + var do func(n ir.Node) bool + do = func(n ir.Node) bool { + switch n.Op() { + case ir.OAS: + n := n.(*ir.AssignStmt) + if isName(n.X) { + return handleNode(n.Y) + } + case ir.OAS2: + n := n.(*ir.AssignListStmt) + for i, p := range n.Lhs { + if isName(p) { + return handleNode(n.Rhs[i]) + } + } + case ir.OAS2DOTTYPE: + n := n.(*ir.AssignListStmt) + for _, p := range n.Lhs { + if isName(p) { + return handleNode(n.Rhs[0]) + } + } + case ir.OAS2FUNC: + n := n.(*ir.AssignListStmt) + for i, p := range n.Lhs { + if isName(p) { + rhs := n.Rhs[0] + for { + if r, ok := rhs.(*ir.ParenExpr); ok { + rhs = r.X + continue + } + break + } + if call, ok := rhs.(*ir.CallExpr); ok { + retTyp := call.Fun.Type().Results()[i].Type + if !retTyp.IsInterface() { + return handleType(retTyp) + } + } + typ = nil + return true + } + } + case ir.OAS2MAPR, ir.OAS2RECV, ir.OSELRECV2: + n := n.(*ir.AssignListStmt) + for _, p := range n.Lhs { + if isName(p) { + return handleType(n.Rhs[0].Type()) + } + } + case ir.OADDR: + n := n.(*ir.AddrExpr) + if isName(n.X) { + base.FatalfAt(n.Pos(), "%v not marked addrtaken", name) + } + case ir.ORANGE: + n := n.(*ir.RangeStmt) + if isName(n.Key) { + return handleNode(n.Key) + } + if isName(n.Value) { + return handleNode(n.Value) + } + case ir.OCLOSURE: + n := n.(*ir.ClosureExpr) + if ir.Any(n.Func, do) { + return true + } + } + return false + } + ir.Any(name.Curfn, do) + analyzed[name] = typ + return typ +} diff --git a/src/cmd/compile/internal/noder/reader.go b/src/cmd/compile/internal/noder/reader.go index eca66487fa26da..bdfef70f216527 100644 --- a/src/cmd/compile/internal/noder/reader.go +++ b/src/cmd/compile/internal/noder/reader.go @@ -2941,6 +2941,7 @@ func (r *reader) multiExpr() []ir.Node { as.Def = true for i := range results { tmp := r.temp(pos, r.typ()) + tmp.Defn = as as.PtrInit().Append(ir.NewDecl(pos, ir.ODCL, tmp)) as.Lhs.Append(tmp) diff --git a/src/crypto/sha256/sha256_test.go b/src/crypto/sha256/sha256_test.go index b3b4e77f578e60..1117c9903c3b6d 100644 --- a/src/crypto/sha256/sha256_test.go +++ b/src/crypto/sha256/sha256_test.go @@ -406,3 +406,17 @@ func BenchmarkHash1K(b *testing.B) { func BenchmarkHash8K(b *testing.B) { benchmarkSize(b, 8192) } + +func TestAllocatonsWithTypeAsserts(t *testing.T) { + cryptotest.SkipTestAllocations(t) + allocs := testing.AllocsPerRun(100, func() { + h := New() + h.Write([]byte{1, 2, 3}) + marshaled, _ := h.(encoding.BinaryMarshaler).MarshalBinary() + marshaled, _ = h.(encoding.BinaryAppender).AppendBinary(marshaled[:0]) + h.(encoding.BinaryUnmarshaler).UnmarshalBinary(marshaled) + }) + if allocs != 0 { + t.Fatalf("allocs = %v; want = 0", allocs) + } +} diff --git a/src/runtime/pprof/pprof_test.go b/src/runtime/pprof/pprof_test.go index bba66ba48fea35..a6a9fa27cd74cb 100644 --- a/src/runtime/pprof/pprof_test.go +++ b/src/runtime/pprof/pprof_test.go @@ -342,6 +342,9 @@ type inlineWrapperInterface interface { type inlineWrapper struct { } +// TODO: test fails if this method inlines. +// +//go:noinline func (h inlineWrapper) dump(pcs []uintptr) { dumpCallers(pcs) } diff --git a/test/escape_iface_with_devirt_type_assertions.go b/test/escape_iface_with_devirt_type_assertions.go new file mode 100644 index 00000000000000..d72d19a2a9ed86 --- /dev/null +++ b/test/escape_iface_with_devirt_type_assertions.go @@ -0,0 +1,609 @@ +// errorcheck -0 -m + +// Copyright 2025 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. + +package escape + +type M interface{ M() } + +type A interface{ A() } + +type C interface{ C() } + +type Impl struct{} + +func (*Impl) M() {} // ERROR "can inline" + +func (*Impl) A() {} // ERROR "can inline" + +type Impl2 struct{} + +func (*Impl2) M() {} // ERROR "can inline" + +func (*Impl2) A() {} // ERROR "can inline" + +type CImpl struct{} + +func (CImpl) C() {} // ERROR "can inline" + +func t() { + var a M = &Impl{} // ERROR "&Impl{} does not escape" + + a.(M).M() // ERROR "devirtualizing a.\(M\).M" "inlining call" + a.(A).A() // ERROR "devirtualizing a.\(A\).A" "inlining call" + a.(*Impl).M() // ERROR "inlining call" + a.(*Impl).A() // ERROR "inlining call" + + v := a.(M) + v.M() // ERROR "devirtualizing v.M" "inlining call" + v.(A).A() // ERROR "devirtualizing v.\(A\).A" "inlining call" + v.(*Impl).A() // ERROR "inlining call" + v.(*Impl).M() // ERROR "inlining call" + + v2 := a.(A) + v2.A() // ERROR "devirtualizing v2.A" "inlining call" + v2.(M).M() // ERROR "devirtualizing v2.\(M\).M" "inlining call" + v2.(*Impl).A() // ERROR "inlining call" + v2.(*Impl).M() // ERROR "inlining call" + + a.(M).(A).A() // ERROR "devirtualizing a.\(M\).\(A\).A" "inlining call" + a.(A).(M).M() // ERROR "devirtualizing a.\(A\).\(M\).M" "inlining call" + + a.(M).(A).(*Impl).A() // ERROR "inlining call" + a.(A).(M).(*Impl).M() // ERROR "inlining call" + + any(a).(M).M() // ERROR "devirtualizing" "inlining call" + any(a).(A).A() // ERROR "devirtualizing" "inlining call" + any(a).(M).(any).(A).A() // ERROR "devirtualizing" "inlining call" + + c := any(a) + c.(A).A() // ERROR "devirtualizing" "inlining call" + c.(M).M() // ERROR "devirtualizing" "inlining call" + + { + var a C = &CImpl{} // ERROR "does not escape" + a.(any).(C).C() // ERROR "devirtualizing" "inlining" + a.(any).(*CImpl).C() // ERROR "inlining" + } +} + +func t2() { + { + var a M = &Impl{} // ERROR "does not escape" + if v, ok := a.(M); ok { + v.M() // ERROR "devirtualizing" "inlining call" + } + } + { + var a M = &Impl{} // ERROR "does not escape" + if v, ok := a.(A); ok { + v.A() // ERROR "devirtualizing" "inlining call" + } + } + { + var a M = &Impl{} // ERROR "does not escape" + v, ok := a.(M) + if ok { + v.M() // ERROR "devirtualizing" "inlining call" + } + } + { + var a M = &Impl{} // ERROR "does not escape" + v, ok := a.(A) + if ok { + v.A() // ERROR "devirtualizing" "inlining call" + } + } + { + var a M = &Impl{} // ERROR "does not escape" + v, ok := a.(*Impl) + if ok { + v.A() // ERROR "inlining" + v.M() // ERROR "inlining" + } + } + { + var a M = &Impl{} // ERROR "does not escape" + v, _ := a.(M) + v.M() // ERROR "devirtualizing" "inlining call" + } + { + var a M = &Impl{} // ERROR "does not escape" + v, _ := a.(A) + v.A() // ERROR "devirtualizing" "inlining call" + } + { + var a M = &Impl{} // ERROR "does not escape" + v, _ := a.(*Impl) + v.A() // ERROR "inlining" + v.M() // ERROR "inlining" + } + { + a := newM() // ERROR "does not escape" "inlining call" + callA(a) // ERROR "devirtualizing" "inlining call" + callIfA(a) // ERROR "devirtualizing" "inlining call" + } + { + var a M = &Impl{} // ERROR "does not escape" + // Note the !ok condition, devirtualizing here is fine. + if v, ok := a.(M); !ok { + v.M() // ERROR "devirtualizing" "inlining call" + } + } +} + +func newM() M { // ERROR "can inline" + return &Impl{} // ERROR "escapes" +} + +func callA(m M) { // ERROR "can inline" "leaking param" + m.(A).A() +} + +func callIfA(m M) { // ERROR "can inline" "leaking param" + if v, ok := m.(A); ok { + v.A() + } +} + +//go:noinline +func newImplNoInline() *Impl { + return &Impl{} // ERROR "escapes" +} + +func t3() { + { + var a A = newImplNoInline() + if v, ok := a.(M); ok { + v.M() // ERROR "devirtualizing" "inlining call" + } + } + { + m := make(map[*Impl]struct{}) // ERROR "does not escape" + for v := range m { + var v A = v + v.A() // ERROR "devirtualizing" "inlining call" + if v, ok := v.(M); ok { + v.M() // ERROR "devirtualizing" "inlining call" + } + } + } + { + m := make(map[int]*Impl) // ERROR "does not escape" + for _, v := range m { + var v A = v + v.A() // ERROR "devirtualizing" "inlining call" + if v, ok := v.(M); ok { + v.M() // ERROR "devirtualizing" "inlining call" + } + } + } + { + m := make(map[int]*Impl) // ERROR "does not escape" + var v A = m[0] + v.A() // ERROR "devirtualizing" "inlining call" + if v, ok := v.(M); ok { + v.M() // ERROR "devirtualizing" "inlining call" + } + } + { + m := make(chan *Impl) + var v A = <-m + v.A() // ERROR "devirtualizing" "inlining call" + if v, ok := v.(M); ok { + v.M() // ERROR "devirtualizing" "inlining call" + } + } + { + m := make(chan *Impl) + var v A + var ok bool + if v, ok = <-m; ok { + v.A() // ERROR "devirtualizing" "inlining call" + v.(M).M() // ERROR "devirtualizing" "inlining call" + } + select { + case <-m: + v.A() // ERROR "devirtualizing" "inlining call" + v.(M).M() // ERROR "devirtualizing" "inlining call" + case v = <-m: + v.A() // ERROR "devirtualizing" "inlining call" + v.(M).M() // ERROR "devirtualizing" "inlining call" + case v, ok = <-m: + v.A() // ERROR "devirtualizing" "inlining call" + v.(M).M() // ERROR "devirtualizing" "inlining call" + } + } +} + +//go:noinline +func newImpl2ret2() (string, *Impl2) { + return "str", &Impl2{} // ERROR "escapes" +} + +//go:noinline +func newImpl2() *Impl2 { + return &Impl2{} // ERROR "escapes" +} + +func t5() { + { + var a A + a = &Impl{} // ERROR "escapes" + a = &Impl2{} // ERROR "escapes" + a.A() + } + { + a := A(&Impl{}) // ERROR "escapes" + a = &Impl2{} // ERROR "escapes" + a.A() + } + { + a := A(&Impl{}) // ERROR "escapes" + a.A() + a = &Impl2{} // ERROR "escapes" + } + { + a := A(&Impl{}) // ERROR "escapes" + a = &Impl2{} // ERROR "escapes" + var asAny any = a + asAny.(A).A() + } + { + a := A(&Impl{}) // ERROR "escapes" + var asAny any = a + asAny = &Impl2{} // ERROR "escapes" + asAny.(A).A() + } + { + a := A(&Impl{}) // ERROR "escapes" + var asAny any = a + asAny.(A).A() + asAny = &Impl2{} // ERROR "escapes" + a.A() // ERROR "devirtualizing" "inlining call" + } + { + var a A + a = &Impl{} // ERROR "escapes" + a = newImpl2() + a.A() + } + { + var a A + a = &Impl{} // ERROR "escapes" + _, a = newImpl2ret2() + a.A() + } + { + var a A + a = &Impl{} // ERROR "escapes" + m := make(map[int]*Impl2) // ERROR "does not escape" + a = m[0] + a.A() + } + { + var a A + a = &Impl{} // ERROR "escapes" + m := make(chan *Impl2) + a = <-m + a.A() + } +} + +func t6() { + { + m := make(map[int]*Impl) // ERROR "does not escape" + var a A + a, _ = m[0] + if v, ok := a.(M); ok { + v.M() // ERROR "devirtualizing" "inlining call" + } + } + { + m := make(map[int]*Impl) // ERROR "does not escape" + var a A + var ok bool + if a, ok = m[0]; ok { + if v, ok := a.(M); ok { + v.M() // ERROR "devirtualizing" "inlining call" + } + } + } + { + m := make(chan *Impl) + var a A + a, _ = <-m + if v, ok := a.(M); ok { + v.M() // ERROR "devirtualizing" "inlining call" + } + } + { + m := make(chan *Impl) + var a A + var ok bool + if a, ok = <-m; ok { + if v, ok := a.(M); ok { + v.M() // ERROR "devirtualizing" "inlining call" + } + } + } +} + +var ( + globalImpl = &Impl{} + globalImpl2 = &Impl2{} + globalA A = &Impl{} + globalM M = &Impl{} +) + +func t7() { + { + var a A = &Impl{} // ERROR "does not escape" + a = globalImpl + a.A() // ERROR "devirtualizing" "inlining call" + } + { + var a A = &Impl{} // ERROR "does not escape" + a = A(globalImpl) + a.A() // ERROR "devirtualizing" "inlining call" + } + { + var a A = &Impl{} // ERROR "does not escape" + a = M(globalImpl).(A) + a.A() // ERROR "devirtualizing" "inlining call" + } + { + var a A = &Impl{} // ERROR "escapes" + a = globalImpl2 + a.A() + } + { + var a A = &Impl{} // ERROR "escapes" + a = globalA + a.A() + } + { + var a A = &Impl{} // ERROR "escapes" + a = globalM.(A) + a.A() + } + { + var a A = &Impl{} // ERROR "does not escape" + for _, v := range []*Impl{&Impl{}} { // ERROR "does not escape" + a = v + } + + k, v := &Impl{}, &Impl{} // ERROR "escapes" + for k, v := range map[*Impl]*Impl{k: v} { // ERROR "does not escape" + a = k + a = v + } + + a.A() // ERROR "devirtualizing" "inlining call" + a.(A).A() // ERROR "devirtualizing""inlining call" + a.(M).M() // ERROR "devirtualizing""inlining call" + + var m M = a.(M) + m.M() // ERROR "devirtualizing""inlining call" + m.(A).A() // ERROR "devirtualizing""inlining call" + } + { + var a A = &Impl{} // ERROR "escapes" + var impl2 = &Impl2{} // ERROR "escapes" + for _, v := range []*Impl2{impl2} { // ERROR "does not escape" + a = v + } + a.A() + } + { + var a A = &Impl{} // ERROR "escapes" + k, v := &Impl2{}, &Impl2{} // ERROR "escapes" + for k, _ := range map[*Impl2]*Impl2{k: v} { // ERROR "does not escape" + a = k + } + a.A() + } + { + var a A = &Impl{} // ERROR "escapes" + k, v := &Impl2{}, &Impl2{} // ERROR "escapes" + for _, v := range map[*Impl2]*Impl2{k: v} { // ERROR "does not escape" + a = v + } + a.A() + } +} + +func t8() { + { + var a A = &Impl{} // ERROR "escapes" + a = a + a.A() + } + { + var a A = &Impl{} // ERROR "escapes" + var asAny any = a + asAny = asAny + asAny.(A).A() + } + { + var a A = &Impl{} // ERROR "escapes" + var asAny any = a + asAny = asAny + a = asAny.(A) + asAny = a + asAny.(A).A() + asAny.(M).M() + } + { + var a A = &Impl{} // ERROR "escapes" + var asAny A = a + a = asAny.(A) + a.A() + } +} + +func t9() { + var a interface { + M + A + } = &Impl{} // ERROR "does not escape" + + { + var b A = a + b.A() // ERROR "devirtualizing" "inlining call" + b.(M).M() // ERROR "devirtualizing" "inlining call" + } + { + var b M = a + b.M() // ERROR "devirtualizing" "inlining call" + b.(A).A() // ERROR "devirtualizing" "inlining call" + } + { + var b A = a.(M).(A) + b.A() // ERROR "devirtualizing" "inlining call" + b.(M).M() // ERROR "devirtualizing" "inlining call" + } + { + var b M = a.(A).(M) + b.M() // ERROR "devirtualizing" "inlining call" + b.(A).A() // ERROR "devirtualizing" "inlining call" + } + + if v, ok := a.(A); ok { + v.A() // ERROR "devirtualizing" "inlining call" + } + + if v, ok := a.(M); ok { + v.M() // ERROR "devirtualizing" "inlining call" + } + + { + var c A = a + + if v, ok := c.(A); ok { + v.A() // ERROR "devirtualizing" "inlining call" + } + + c = &Impl{} // ERROR "does not escape" + + if v, ok := c.(M); ok { + v.M() // ERROR "devirtualizing" "inlining call" + } + + if v, ok := c.(interface { + A + M + }); ok { + v.M() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing" "inlining call" + } + } +} + +func t10() { + var a A + defer func() { // ERROR "func literal does not escape" "can inline" + a = &Impl{} // ERROR "escapes" + }() + a = &Impl{} // ERROR "does not escape" + a.A() // ERROR "devirtualizing" "inlining call" +} + +func t11() { + var a A + defer func() { // ERROR "func literal does not escape" "can inline" + a = &Impl2{} // ERROR "escapes" + }() + a = &Impl{} // ERROR "escapes" + a.A() +} + +func t12() { + var a A + func() { // ERROR "func literal does not escape" + // defer so that it does not lnline. + defer func() {}() // ERROR "can inline" "func literal does not escape" + a = &Impl{} // ERROR "escapes" + }() + a = &Impl{} // ERROR "does not escape" + a.A() // ERROR "devirtualizing" "inlining call" +} + +func t13() { + var a A + func() { // ERROR "func literal does not escape" + // defer so that it does not lnline. + defer func() {}() // ERROR "can inline" "func literal does not escape" + a = &Impl2{} // ERROR "escapes" + }() + a = &Impl{} // ERROR "escapes" + a.A() +} + +var global = "1" + +func t14() { + var a A + a = &Impl{} // ERROR "does not escape" + c := func() { // ERROR "can inline" "func literal does not escape" + a = &Impl{} // ERROR "escapes" + } + if global == "1" { + c = func() { // ERROR "can inline" "func literal does not escape" + a = &Impl{} // ERROR "escapes" + } + } + a.A() // ERROR "devirtualizing" "inlining call" + c() +} + +func t15() { + var a A + a = &Impl{} // ERROR "escapes" + c := func() { // ERROR "can inline" "func literal does not escape" + a = &Impl2{} // ERROR "escapes" + } + if global == "1" { + c = func() { // ERROR "can inline" "func literal does not escape" + a = &Impl{} // ERROR "escapes" + } + } + a.A() + c() +} + +type implWrapper Impl + +func (implWrapper) A() {} // ERROR "can inline" + +//go:noinline +func t16() { + i := &Impl{} // ERROR "does not escape" + var a A = (*implWrapper)(i) + a.A() // ERROR "devirtualizing a.A to \*implWrapper" "inlining call" +} + +func testInvalidAsserts() { + any(0).(interface{ A() }).A() // ERROR "escapes" + { + var a M = &Impl{} // ERROR "escapes" + a.(C).C() // this will panic + a.(any).(C).C() // this will panic + } + { + var a C = &CImpl{} // ERROR "escapes" + a.(M).M() // this will panic + a.(any).(M).M() // this will panic + } + { + var a C = &CImpl{} // ERROR "does not escape" + + // this will panic + a.(M).(*Impl).M() // ERROR "inlining" + + // this will panic + a.(any).(M).(*Impl).M() // ERROR "inlining" + } +} From 0335f864961b0232fab96886e3831a1fe64c58a7 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Mon, 17 Feb 2025 19:16:11 +0100 Subject: [PATCH 02/74] handle ranges properly Change-Id: I5a519d6e6351013ca701221d6d1fc0b3bf62d481 --- .../internal/devirtualize/devirtualize.go | 36 ++++- ...scape_iface_with_devirt_type_assertions.go | 149 ++++++++++++++++++ 2 files changed, 181 insertions(+), 4 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 854154087c4f79..a8ac9482c0ae7a 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -315,6 +315,7 @@ func concreteType2(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Type { n := n.(*ir.AssignListStmt) for _, p := range n.Lhs { if isName(p) { + // TODO: the type can be a map? Same with recv (a chan?) return handleType(n.Rhs[0].Type()) } } @@ -325,11 +326,38 @@ func concreteType2(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Type { } case ir.ORANGE: n := n.(*ir.RangeStmt) - if isName(n.Key) { - return handleNode(n.Key) + xTyp := n.X.Type() + + // range over an array pointer + if xTyp.IsPtr() && xTyp.Elem().IsArray() { + xTyp = xTyp.Elem() } - if isName(n.Value) { - return handleNode(n.Value) + + if xTyp.IsArray() || xTyp.IsSlice() { + if isName(n.Key) { + // This is an index, int has no methods, so nothing + // to devirtualize. + typ = nil + return true + } + if isName(n.Value) { + return handleType(xTyp.Elem()) + } + } else if xTyp.IsChan() { + if isName(n.Key) { + return handleType(xTyp.Elem()) + } + base.Assertf(n.Value == nil, "n.Value != nil in range over chan") + } else if xTyp.IsMap() { + if isName(n.Key) { + return handleType(xTyp.Key()) + } + if isName(n.Value) { + return handleType(xTyp.Elem()) + } + } else { + typ = nil + return true } case ir.OCLOSURE: n := n.(*ir.ClosureExpr) diff --git a/test/escape_iface_with_devirt_type_assertions.go b/test/escape_iface_with_devirt_type_assertions.go index d72d19a2a9ed86..bf93bf5764da43 100644 --- a/test/escape_iface_with_devirt_type_assertions.go +++ b/test/escape_iface_with_devirt_type_assertions.go @@ -218,6 +218,155 @@ func t3() { } } +func rangeDevirt() { + { + var v A + m := make(map[*Impl]struct{}) // ERROR "does not escape" + v = &Impl{} // ERROR "does not escape" + for v = range m { + } + v.A() // ERROR "devirtualizing" "inlining call" + } + { + var v A + m := make(map[*Impl]*Impl) // ERROR "does not escape" + v = &Impl{} // ERROR "does not escape" + for v = range m { + } + v.A() // ERROR "devirtualizing" "inlining call" + } + { + var v A + m := make(map[*Impl]*Impl) // ERROR "does not escape" + v = &Impl{} // ERROR "does not escape" + for _, v = range m { + } + v.A() // ERROR "devirtualizing" "inlining call" + } + { + var v A + m := make(chan *Impl) + v = &Impl{} // ERROR "does not escape" + for v = range m { + } + v.A() // ERROR "devirtualizing" "inlining call" + } + { + var v A + m := []*Impl{} // ERROR "does not escape" + v = &Impl{} // ERROR "does not escape" + for _, v = range m { + } + v.A() // ERROR "devirtualizing" "inlining call" + } + { + var v A + v = &Impl{} // ERROR "does not escape" + impl := &Impl{} // ERROR "does not escape" + i := 0 + for v = impl; i < 10; i++ { + } + v.A() // ERROR "devirtualizing" "inlining call" + } + { + var v A + v = &Impl{} // ERROR "does not escape" + impl := &Impl{} // ERROR "does not escape" + i := 0 + for v = impl; i < 10; i++ { + } + v.A() // ERROR "devirtualizing" "inlining call" + } + { + var v A + m := [1]*Impl{&Impl{}} // ERROR "does not escape" + v = &Impl{} // ERROR "does not escape" + for _, v = range m { + } + v.A() // ERROR "devirtualizing" "inlining call" + } + { + var v A + m := [1]*Impl{&Impl{}} // ERROR "does not escape" + v = &Impl{} // ERROR "does not escape" + for _, v = range &m { + } + v.A() // ERROR "devirtualizing" "inlining call" + } +} + +func rangeNoDevirt() { + { + var v A = &Impl2{} // ERROR "escapes" + m := make(map[*Impl]struct{}) // ERROR "does not escape" + for v = range m { + } + v.A() + } + { + var v A = &Impl2{} // ERROR "escapes" + m := make(map[*Impl]*Impl) // ERROR "does not escape" + for v = range m { + } + v.A() + } + { + var v A = &Impl2{} // ERROR "escapes" + m := make(map[*Impl]*Impl) // ERROR "does not escape" + for _, v = range m { + } + v.A() + } + { + var v A = &Impl2{} // ERROR "escapes" + m := make(chan *Impl) + for v = range m { + } + v.A() + } + { + var v A = &Impl2{} // ERROR "escapes" + m := []*Impl{} // ERROR "does not escape" + for _, v = range m { + } + v.A() + } + { + var v A + v = &Impl2{} // ERROR "escapes" + impl := &Impl{} // ERROR "escapes" + i := 0 + for v = impl; i < 10; i++ { + } + v.A() + } + { + var v A + v = &Impl2{} // ERROR "escapes" + impl := &Impl{} // ERROR "escapes" + i := 0 + for v = impl; i < 10; i++ { + } + v.A() + } + { + var v A + m := [1]*Impl{&Impl{}} // ERROR "escapes" + v = &Impl2{} // ERROR "escapes" + for _, v = range m { + } + v.A() + } + { + var v A + m := [1]*Impl{&Impl{}} // ERROR "escapes" + v = &Impl2{} // ERROR "escapes" + for _, v = range &m { + } + v.A() + } +} + //go:noinline func newImpl2ret2() (string, *Impl2) { return "str", &Impl2{} // ERROR "escapes" From 84fbab41a050c932177b0cda1a4831636694979f Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Mon, 17 Feb 2025 19:22:57 +0100 Subject: [PATCH 03/74] add test case Change-Id: I94f6d88c532b217af61c8b415f31f8d72f0b8e3a --- ...scape_iface_with_devirt_type_assertions.go | 315 +++++++++--------- 1 file changed, 162 insertions(+), 153 deletions(-) diff --git a/test/escape_iface_with_devirt_type_assertions.go b/test/escape_iface_with_devirt_type_assertions.go index bf93bf5764da43..8c212167bbd4f1 100644 --- a/test/escape_iface_with_devirt_type_assertions.go +++ b/test/escape_iface_with_devirt_type_assertions.go @@ -218,155 +218,6 @@ func t3() { } } -func rangeDevirt() { - { - var v A - m := make(map[*Impl]struct{}) // ERROR "does not escape" - v = &Impl{} // ERROR "does not escape" - for v = range m { - } - v.A() // ERROR "devirtualizing" "inlining call" - } - { - var v A - m := make(map[*Impl]*Impl) // ERROR "does not escape" - v = &Impl{} // ERROR "does not escape" - for v = range m { - } - v.A() // ERROR "devirtualizing" "inlining call" - } - { - var v A - m := make(map[*Impl]*Impl) // ERROR "does not escape" - v = &Impl{} // ERROR "does not escape" - for _, v = range m { - } - v.A() // ERROR "devirtualizing" "inlining call" - } - { - var v A - m := make(chan *Impl) - v = &Impl{} // ERROR "does not escape" - for v = range m { - } - v.A() // ERROR "devirtualizing" "inlining call" - } - { - var v A - m := []*Impl{} // ERROR "does not escape" - v = &Impl{} // ERROR "does not escape" - for _, v = range m { - } - v.A() // ERROR "devirtualizing" "inlining call" - } - { - var v A - v = &Impl{} // ERROR "does not escape" - impl := &Impl{} // ERROR "does not escape" - i := 0 - for v = impl; i < 10; i++ { - } - v.A() // ERROR "devirtualizing" "inlining call" - } - { - var v A - v = &Impl{} // ERROR "does not escape" - impl := &Impl{} // ERROR "does not escape" - i := 0 - for v = impl; i < 10; i++ { - } - v.A() // ERROR "devirtualizing" "inlining call" - } - { - var v A - m := [1]*Impl{&Impl{}} // ERROR "does not escape" - v = &Impl{} // ERROR "does not escape" - for _, v = range m { - } - v.A() // ERROR "devirtualizing" "inlining call" - } - { - var v A - m := [1]*Impl{&Impl{}} // ERROR "does not escape" - v = &Impl{} // ERROR "does not escape" - for _, v = range &m { - } - v.A() // ERROR "devirtualizing" "inlining call" - } -} - -func rangeNoDevirt() { - { - var v A = &Impl2{} // ERROR "escapes" - m := make(map[*Impl]struct{}) // ERROR "does not escape" - for v = range m { - } - v.A() - } - { - var v A = &Impl2{} // ERROR "escapes" - m := make(map[*Impl]*Impl) // ERROR "does not escape" - for v = range m { - } - v.A() - } - { - var v A = &Impl2{} // ERROR "escapes" - m := make(map[*Impl]*Impl) // ERROR "does not escape" - for _, v = range m { - } - v.A() - } - { - var v A = &Impl2{} // ERROR "escapes" - m := make(chan *Impl) - for v = range m { - } - v.A() - } - { - var v A = &Impl2{} // ERROR "escapes" - m := []*Impl{} // ERROR "does not escape" - for _, v = range m { - } - v.A() - } - { - var v A - v = &Impl2{} // ERROR "escapes" - impl := &Impl{} // ERROR "escapes" - i := 0 - for v = impl; i < 10; i++ { - } - v.A() - } - { - var v A - v = &Impl2{} // ERROR "escapes" - impl := &Impl{} // ERROR "escapes" - i := 0 - for v = impl; i < 10; i++ { - } - v.A() - } - { - var v A - m := [1]*Impl{&Impl{}} // ERROR "escapes" - v = &Impl2{} // ERROR "escapes" - for _, v = range m { - } - v.A() - } - { - var v A - m := [1]*Impl{&Impl{}} // ERROR "escapes" - v = &Impl2{} // ERROR "escapes" - for _, v = range &m { - } - v.A() - } -} - //go:noinline func newImpl2ret2() (string, *Impl2) { return "str", &Impl2{} // ERROR "escapes" @@ -728,10 +579,168 @@ type implWrapper Impl func (implWrapper) A() {} // ERROR "can inline" //go:noinline -func t16() { - i := &Impl{} // ERROR "does not escape" - var a A = (*implWrapper)(i) - a.A() // ERROR "devirtualizing a.A to \*implWrapper" "inlining call" +func devirtWrapperType() { + { + i := &Impl{} // ERROR "does not escape" + // This is an OCONVNOP, so we have to be carefull, not to devirtualize it to Impl.A. + var a A = (*implWrapper)(i) + a.A() // ERROR "devirtualizing a.A to \*implWrapper" "inlining call" + } + { + i := Impl{} + // This is an OCONVNOP, so we have to be carefull, not to devirtualize it to Impl.A. + var a A = (implWrapper)(i) // ERROR "does not escape" + a.A() // ERROR "devirtualizing a.A to implWrapper" "inlining call" + } +} + +func rangeDevirt() { + { + var v A + m := make(map[*Impl]struct{}) // ERROR "does not escape" + v = &Impl{} // ERROR "does not escape" + for v = range m { + } + v.A() // ERROR "devirtualizing" "inlining call" + } + { + var v A + m := make(map[*Impl]*Impl) // ERROR "does not escape" + v = &Impl{} // ERROR "does not escape" + for v = range m { + } + v.A() // ERROR "devirtualizing" "inlining call" + } + { + var v A + m := make(map[*Impl]*Impl) // ERROR "does not escape" + v = &Impl{} // ERROR "does not escape" + for _, v = range m { + } + v.A() // ERROR "devirtualizing" "inlining call" + } + { + var v A + m := make(chan *Impl) + v = &Impl{} // ERROR "does not escape" + for v = range m { + } + v.A() // ERROR "devirtualizing" "inlining call" + } + { + var v A + m := []*Impl{} // ERROR "does not escape" + v = &Impl{} // ERROR "does not escape" + for _, v = range m { + } + v.A() // ERROR "devirtualizing" "inlining call" + } + { + var v A + v = &Impl{} // ERROR "does not escape" + impl := &Impl{} // ERROR "does not escape" + i := 0 + for v = impl; i < 10; i++ { + } + v.A() // ERROR "devirtualizing" "inlining call" + } + { + var v A + v = &Impl{} // ERROR "does not escape" + impl := &Impl{} // ERROR "does not escape" + i := 0 + for v = impl; i < 10; i++ { + } + v.A() // ERROR "devirtualizing" "inlining call" + } + { + var v A + m := [1]*Impl{&Impl{}} // ERROR "does not escape" + v = &Impl{} // ERROR "does not escape" + for _, v = range m { + } + v.A() // ERROR "devirtualizing" "inlining call" + } + { + var v A + m := [1]*Impl{&Impl{}} // ERROR "does not escape" + v = &Impl{} // ERROR "does not escape" + for _, v = range &m { + } + v.A() // ERROR "devirtualizing" "inlining call" + } +} + +func rangeNoDevirt() { + { + var v A = &Impl2{} // ERROR "escapes" + m := make(map[*Impl]struct{}) // ERROR "does not escape" + for v = range m { + } + v.A() + } + { + var v A = &Impl2{} // ERROR "escapes" + m := make(map[*Impl]*Impl) // ERROR "does not escape" + for v = range m { + } + v.A() + } + { + var v A = &Impl2{} // ERROR "escapes" + m := make(map[*Impl]*Impl) // ERROR "does not escape" + for _, v = range m { + } + v.A() + } + { + var v A = &Impl2{} // ERROR "escapes" + m := make(chan *Impl) + for v = range m { + } + v.A() + } + { + var v A = &Impl2{} // ERROR "escapes" + m := []*Impl{} // ERROR "does not escape" + for _, v = range m { + } + v.A() + } + { + var v A + v = &Impl2{} // ERROR "escapes" + impl := &Impl{} // ERROR "escapes" + i := 0 + for v = impl; i < 10; i++ { + } + v.A() + } + { + var v A + v = &Impl2{} // ERROR "escapes" + impl := &Impl{} // ERROR "escapes" + i := 0 + for v = impl; i < 10; i++ { + } + v.A() + } + { + var v A + m := [1]*Impl{&Impl{}} // ERROR "escapes" + v = &Impl2{} // ERROR "escapes" + for _, v = range m { + } + v.A() + } + { + var v A + m := [1]*Impl{&Impl{}} // ERROR "escapes" + v = &Impl2{} // ERROR "escapes" + for _, v = range &m { + } + v.A() + } } func testInvalidAsserts() { From 6062d076d5f6552e9455ef9756264249b4366c0d Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Mon, 17 Feb 2025 19:54:30 +0100 Subject: [PATCH 04/74] rework tests for chans Change-Id: Ibcc31325235e75e2f5b8e9775ef48b8576047c82 --- ...scape_iface_with_devirt_type_assertions.go | 161 ++++++++++++------ 1 file changed, 106 insertions(+), 55 deletions(-) diff --git a/test/escape_iface_with_devirt_type_assertions.go b/test/escape_iface_with_devirt_type_assertions.go index 8c212167bbd4f1..197b155fb4d74f 100644 --- a/test/escape_iface_with_devirt_type_assertions.go +++ b/test/escape_iface_with_devirt_type_assertions.go @@ -28,7 +28,7 @@ type CImpl struct{} func (CImpl) C() {} // ERROR "can inline" -func t() { +func typeAsserts() { var a M = &Impl{} // ERROR "&Impl{} does not escape" a.(M).M() // ERROR "devirtualizing a.\(M\).M" "inlining call" @@ -69,7 +69,7 @@ func t() { } } -func t2() { +func typeAssertsWithOkReturn() { { var a M = &Impl{} // ERROR "does not escape" if v, ok := a.(M); ok { @@ -188,34 +188,6 @@ func t3() { v.M() // ERROR "devirtualizing" "inlining call" } } - { - m := make(chan *Impl) - var v A = <-m - v.A() // ERROR "devirtualizing" "inlining call" - if v, ok := v.(M); ok { - v.M() // ERROR "devirtualizing" "inlining call" - } - } - { - m := make(chan *Impl) - var v A - var ok bool - if v, ok = <-m; ok { - v.A() // ERROR "devirtualizing" "inlining call" - v.(M).M() // ERROR "devirtualizing" "inlining call" - } - select { - case <-m: - v.A() // ERROR "devirtualizing" "inlining call" - v.(M).M() // ERROR "devirtualizing" "inlining call" - case v = <-m: - v.A() // ERROR "devirtualizing" "inlining call" - v.(M).M() // ERROR "devirtualizing" "inlining call" - case v, ok = <-m: - v.A() // ERROR "devirtualizing" "inlining call" - v.(M).M() // ERROR "devirtualizing" "inlining call" - } - } } //go:noinline @@ -283,13 +255,6 @@ func t5() { a = m[0] a.A() } - { - var a A - a = &Impl{} // ERROR "escapes" - m := make(chan *Impl2) - a = <-m - a.A() - } } func t6() { @@ -311,24 +276,6 @@ func t6() { } } } - { - m := make(chan *Impl) - var a A - a, _ = <-m - if v, ok := a.(M); ok { - v.M() // ERROR "devirtualizing" "inlining call" - } - } - { - m := make(chan *Impl) - var a A - var ok bool - if a, ok = <-m; ok { - if v, ok := a.(M); ok { - v.M() // ERROR "devirtualizing" "inlining call" - } - } - } } var ( @@ -594,6 +541,110 @@ func devirtWrapperType() { } } +func chanDevirt() { + { + m := make(chan *Impl) + var v A = <-m + v.A() // ERROR "devirtualizing" "inlining call" + } + { + m := make(chan *Impl) + var v A + v = <-m + v.A() // ERROR "devirtualizing" "inlining call" + } + { + m := make(chan *Impl) + var v A + v, _ = <-m + v.A() // ERROR "devirtualizing" "inlining call" + } + { + m := make(chan *Impl) + var v A + var ok bool + if v, ok = <-m; ok { + v.A() // ERROR "devirtualizing" "inlining call" + } + v.A() // ERROR "devirtualizing" "inlining call" + } + { + m := make(chan *Impl) + var v A + var ok bool + if v, ok = <-m; ok { + v.A() // ERROR "devirtualizing" "inlining call" + } + select { + case <-m: + v.A() // ERROR "devirtualizing" "inlining call" + case v = <-m: + v.A() // ERROR "devirtualizing" "inlining call" + case v, ok = <-m: + v.A() // ERROR "devirtualizing" "inlining call" + } + } +} + +func chanNoDevirt() { + { + m := make(chan *Impl) + var v A = <-m + v = &Impl2{} // ERROR "escapes" + v.A() + } + { + m := make(chan *Impl) + var v A + v = <-m + v = &Impl2{} // ERROR "escapes" + v.A() + } + { + m := make(chan *Impl) + var v A + v, _ = <-m + v = &Impl2{} // ERROR "escapes" + v.A() + } + { + m := make(chan *Impl) + var v A + var ok bool + if v, ok = <-m; ok { + v.A() + } + v = &Impl2{} // ERROR "escapes" + v.A() + } + { + m := make(chan *Impl) + var v A = &Impl2{} // ERROR "escapes" + var ok bool + if v, ok = <-m; ok { + v.A() + } + } + { + m := make(chan *Impl) + var v A = &Impl2{} // ERROR "escapes" + select { + case v = <-m: + v.A() + } + v.A() + } + { + m := make(chan *Impl) + var v A = &Impl2{} // ERROR "escapes" + select { + case v, _ = <-m: + v.A() + } + v.A() + } +} + func rangeDevirt() { { var v A From 0409ba192e27e9ce3b180665c934ce9cf354cbda Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Mon, 17 Feb 2025 20:04:14 +0100 Subject: [PATCH 05/74] improve map tests Change-Id: I92ef963674619ec1ce0c36240127d8aa3308808d --- ...scape_iface_with_devirt_type_assertions.go | 147 ++++++++++-------- 1 file changed, 78 insertions(+), 69 deletions(-) diff --git a/test/escape_iface_with_devirt_type_assertions.go b/test/escape_iface_with_devirt_type_assertions.go index 197b155fb4d74f..4b20e330084888 100644 --- a/test/escape_iface_with_devirt_type_assertions.go +++ b/test/escape_iface_with_devirt_type_assertions.go @@ -160,34 +160,6 @@ func t3() { v.M() // ERROR "devirtualizing" "inlining call" } } - { - m := make(map[*Impl]struct{}) // ERROR "does not escape" - for v := range m { - var v A = v - v.A() // ERROR "devirtualizing" "inlining call" - if v, ok := v.(M); ok { - v.M() // ERROR "devirtualizing" "inlining call" - } - } - } - { - m := make(map[int]*Impl) // ERROR "does not escape" - for _, v := range m { - var v A = v - v.A() // ERROR "devirtualizing" "inlining call" - if v, ok := v.(M); ok { - v.M() // ERROR "devirtualizing" "inlining call" - } - } - } - { - m := make(map[int]*Impl) // ERROR "does not escape" - var v A = m[0] - v.A() // ERROR "devirtualizing" "inlining call" - if v, ok := v.(M); ok { - v.M() // ERROR "devirtualizing" "inlining call" - } - } } //go:noinline @@ -248,34 +220,6 @@ func t5() { _, a = newImpl2ret2() a.A() } - { - var a A - a = &Impl{} // ERROR "escapes" - m := make(map[int]*Impl2) // ERROR "does not escape" - a = m[0] - a.A() - } -} - -func t6() { - { - m := make(map[int]*Impl) // ERROR "does not escape" - var a A - a, _ = m[0] - if v, ok := a.(M); ok { - v.M() // ERROR "devirtualizing" "inlining call" - } - } - { - m := make(map[int]*Impl) // ERROR "does not escape" - var a A - var ok bool - if a, ok = m[0]; ok { - if v, ok := a.(M); ok { - v.M() // ERROR "devirtualizing" "inlining call" - } - } - } } var ( @@ -521,23 +465,68 @@ func t15() { c() } -type implWrapper Impl - -func (implWrapper) A() {} // ERROR "can inline" +func mapsDevirt() { + { + m := make(map[int]*Impl) // ERROR "does not escape" + var v A = m[0] + v.A() // ERROR "devirtualizing" "inlining call" + v.(M).M() // ERROR "devirtualizing" "inlining call" + } + { + m := make(map[int]*Impl) // ERROR "does not escape" + var v A = m[0] + v.A() // ERROR "devirtualizing" "inlining call" + v.(M).M() // ERROR "devirtualizing" "inlining call" + } + { + m := make(map[int]*Impl) // ERROR "does not escape" + var v A + var ok bool + if v, ok = m[0]; ok { + v.A() // ERROR "devirtualizing" "inlining call" + } + v.A() // ERROR "devirtualizing" "inlining call" + } + { + m := make(map[int]*Impl) // ERROR "does not escape" + var v A + v, _ = m[0] + v.A() // ERROR "devirtualizing" "inlining call" + } +} -//go:noinline -func devirtWrapperType() { +func mapsNoDevirt() { { - i := &Impl{} // ERROR "does not escape" - // This is an OCONVNOP, so we have to be carefull, not to devirtualize it to Impl.A. - var a A = (*implWrapper)(i) - a.A() // ERROR "devirtualizing a.A to \*implWrapper" "inlining call" + m := make(map[int]*Impl) // ERROR "does not escape" + var v A = m[0] + v.A() + v = &Impl2{} // ERROR "escapes" + v.(M).M() } { - i := Impl{} - // This is an OCONVNOP, so we have to be carefull, not to devirtualize it to Impl.A. - var a A = (implWrapper)(i) // ERROR "does not escape" - a.A() // ERROR "devirtualizing a.A to implWrapper" "inlining call" + m := make(map[int]*Impl) // ERROR "does not escape" + var v A = m[0] + v.A() + v = &Impl2{} // ERROR "escapes" + v.(M).M() + } + { + m := make(map[int]*Impl) // ERROR "does not escape" + var v A + var ok bool + if v, ok = m[0]; ok { + v.A() + } + v = &Impl2{} // ERROR "escapes" + v.A() + } + { + m := make(map[int]*Impl) // ERROR "does not escape" + var v A + v, _ = m[0] + v.A() + v = &Impl2{} // ERROR "escapes" + v.A() } } @@ -794,6 +783,26 @@ func rangeNoDevirt() { } } +type implWrapper Impl + +func (implWrapper) A() {} // ERROR "can inline" + +//go:noinline +func devirtWrapperType() { + { + i := &Impl{} // ERROR "does not escape" + // This is an OCONVNOP, so we have to be carefull, not to devirtualize it to Impl.A. + var a A = (*implWrapper)(i) + a.A() // ERROR "devirtualizing a.A to \*implWrapper" "inlining call" + } + { + i := Impl{} + // This is an OCONVNOP, so we have to be carefull, not to devirtualize it to Impl.A. + var a A = (implWrapper)(i) // ERROR "does not escape" + a.A() // ERROR "devirtualizing a.A to implWrapper" "inlining call" + } +} + func testInvalidAsserts() { any(0).(interface{ A() }).A() // ERROR "escapes" { From bb5b91be18de4d955b64f9c2eef1aef51cc9b20e Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Mon, 17 Feb 2025 20:22:09 +0100 Subject: [PATCH 06/74] update tests Change-Id: I3a9b26b5d87e0be64c42e0d8e07cc6be7a3d4a24 --- .../internal/devirtualize/devirtualize.go | 3 + ...scape_iface_with_devirt_type_assertions.go | 231 +++++++----------- 2 files changed, 92 insertions(+), 142 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index a8ac9482c0ae7a..e8f92ddb0bee4b 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -182,6 +182,9 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Type { n = n1.X continue case *ir.TypeAssertExpr: + if !n.Type().IsInterface() { + return n.Type() // asserting a static type + } n = n1.X continue case *ir.CallExpr: diff --git a/test/escape_iface_with_devirt_type_assertions.go b/test/escape_iface_with_devirt_type_assertions.go index 4b20e330084888..131510cad54888 100644 --- a/test/escape_iface_with_devirt_type_assertions.go +++ b/test/escape_iface_with_devirt_type_assertions.go @@ -132,6 +132,12 @@ func typeAssertsWithOkReturn() { v.M() // ERROR "devirtualizing" "inlining call" } } + { + var a A = newImplNoInline() + if v, ok := a.(M); ok { + v.M() // ERROR "devirtualizing" "inlining call" + } + } } func newM() M { // ERROR "can inline" @@ -153,15 +159,6 @@ func newImplNoInline() *Impl { return &Impl{} // ERROR "escapes" } -func t3() { - { - var a A = newImplNoInline() - if v, ok := a.(M); ok { - v.M() // ERROR "devirtualizing" "inlining call" - } - } -} - //go:noinline func newImpl2ret2() (string, *Impl2) { return "str", &Impl2{} // ERROR "escapes" @@ -172,7 +169,7 @@ func newImpl2() *Impl2 { return &Impl2{} // ERROR "escapes" } -func t5() { +func differentTypeAssign() { { var a A a = &Impl{} // ERROR "escapes" @@ -222,120 +219,7 @@ func t5() { } } -var ( - globalImpl = &Impl{} - globalImpl2 = &Impl2{} - globalA A = &Impl{} - globalM M = &Impl{} -) - -func t7() { - { - var a A = &Impl{} // ERROR "does not escape" - a = globalImpl - a.A() // ERROR "devirtualizing" "inlining call" - } - { - var a A = &Impl{} // ERROR "does not escape" - a = A(globalImpl) - a.A() // ERROR "devirtualizing" "inlining call" - } - { - var a A = &Impl{} // ERROR "does not escape" - a = M(globalImpl).(A) - a.A() // ERROR "devirtualizing" "inlining call" - } - { - var a A = &Impl{} // ERROR "escapes" - a = globalImpl2 - a.A() - } - { - var a A = &Impl{} // ERROR "escapes" - a = globalA - a.A() - } - { - var a A = &Impl{} // ERROR "escapes" - a = globalM.(A) - a.A() - } - { - var a A = &Impl{} // ERROR "does not escape" - for _, v := range []*Impl{&Impl{}} { // ERROR "does not escape" - a = v - } - - k, v := &Impl{}, &Impl{} // ERROR "escapes" - for k, v := range map[*Impl]*Impl{k: v} { // ERROR "does not escape" - a = k - a = v - } - - a.A() // ERROR "devirtualizing" "inlining call" - a.(A).A() // ERROR "devirtualizing""inlining call" - a.(M).M() // ERROR "devirtualizing""inlining call" - - var m M = a.(M) - m.M() // ERROR "devirtualizing""inlining call" - m.(A).A() // ERROR "devirtualizing""inlining call" - } - { - var a A = &Impl{} // ERROR "escapes" - var impl2 = &Impl2{} // ERROR "escapes" - for _, v := range []*Impl2{impl2} { // ERROR "does not escape" - a = v - } - a.A() - } - { - var a A = &Impl{} // ERROR "escapes" - k, v := &Impl2{}, &Impl2{} // ERROR "escapes" - for k, _ := range map[*Impl2]*Impl2{k: v} { // ERROR "does not escape" - a = k - } - a.A() - } - { - var a A = &Impl{} // ERROR "escapes" - k, v := &Impl2{}, &Impl2{} // ERROR "escapes" - for _, v := range map[*Impl2]*Impl2{k: v} { // ERROR "does not escape" - a = v - } - a.A() - } -} - -func t8() { - { - var a A = &Impl{} // ERROR "escapes" - a = a - a.A() - } - { - var a A = &Impl{} // ERROR "escapes" - var asAny any = a - asAny = asAny - asAny.(A).A() - } - { - var a A = &Impl{} // ERROR "escapes" - var asAny any = a - asAny = asAny - a = asAny.(A) - asAny = a - asAny.(A).A() - asAny.(M).M() - } - { - var a A = &Impl{} // ERROR "escapes" - var asAny A = a - a = asAny.(A) - a.A() - } -} - -func t9() { +func longDevirtTest() { var a interface { M A @@ -393,7 +277,7 @@ func t9() { } } -func t10() { +func deferDevirt() { var a A defer func() { // ERROR "func literal does not escape" "can inline" a = &Impl{} // ERROR "escapes" @@ -402,7 +286,7 @@ func t10() { a.A() // ERROR "devirtualizing" "inlining call" } -func t11() { +func deferNoDevirt() { var a A defer func() { // ERROR "func literal does not escape" "can inline" a = &Impl2{} // ERROR "escapes" @@ -411,7 +295,7 @@ func t11() { a.A() } -func t12() { +func closureDevirt() { var a A func() { // ERROR "func literal does not escape" // defer so that it does not lnline. @@ -422,7 +306,7 @@ func t12() { a.A() // ERROR "devirtualizing" "inlining call" } -func t13() { +func closureNoDevirt() { var a A func() { // ERROR "func literal does not escape" // defer so that it does not lnline. @@ -435,7 +319,7 @@ func t13() { var global = "1" -func t14() { +func closureDevirt2() { var a A a = &Impl{} // ERROR "does not escape" c := func() { // ERROR "can inline" "func literal does not escape" @@ -450,7 +334,7 @@ func t14() { c() } -func t15() { +func closureNoDevirt2() { var a A a = &Impl{} // ERROR "escapes" c := func() { // ERROR "can inline" "func literal does not escape" @@ -465,13 +349,54 @@ func t15() { c() } -func mapsDevirt() { +var ( + globalImpl = &Impl{} + globalImpl2 = &Impl2{} + globalA A = &Impl{} + globalM M = &Impl{} +) + +func globals() { { - m := make(map[int]*Impl) // ERROR "does not escape" - var v A = m[0] - v.A() // ERROR "devirtualizing" "inlining call" - v.(M).M() // ERROR "devirtualizing" "inlining call" + var a A = &Impl{} // ERROR "does not escape" + a = globalImpl + a.A() // ERROR "devirtualizing" "inlining call" } + { + var a A = &Impl{} // ERROR "does not escape" + a = A(globalImpl) + a.A() // ERROR "devirtualizing" "inlining call" + } + { + var a A = &Impl{} // ERROR "does not escape" + a = M(globalImpl).(A) + a.A() // ERROR "devirtualizing" "inlining call" + } + { + var a A = &Impl{} // ERROR "does not escape" + a = globalA.(*Impl) + a.A() // ERROR "devirtualizing" "inlining call" + a = globalM.(*Impl) + a.A() // ERROR "devirtualizing" "inlining call" + } + { + var a A = &Impl{} // ERROR "escapes" + a = globalImpl2 + a.A() + } + { + var a A = &Impl{} // ERROR "escapes" + a = globalA + a.A() + } + { + var a A = &Impl{} // ERROR "escapes" + a = globalM.(A) + a.A() + } +} + +func mapsDevirt() { { m := make(map[int]*Impl) // ERROR "does not escape" var v A = m[0] @@ -503,13 +428,6 @@ func mapsNoDevirt() { v = &Impl2{} // ERROR "escapes" v.(M).M() } - { - m := make(map[int]*Impl) // ERROR "does not escape" - var v A = m[0] - v.A() - v = &Impl2{} // ERROR "escapes" - v.(M).M() - } { m := make(map[int]*Impl) // ERROR "does not escape" var v A @@ -803,6 +721,35 @@ func devirtWrapperType() { } } +func selfAssigns() { + { + var a A = &Impl{} // ERROR "escapes" + a = a + a.A() + } + { + var a A = &Impl{} // ERROR "escapes" + var asAny any = a + asAny = asAny + asAny.(A).A() + } + { + var a A = &Impl{} // ERROR "escapes" + var asAny any = a + asAny = asAny + a = asAny.(A) + asAny = a + asAny.(A).A() + asAny.(M).M() + } + { + var a A = &Impl{} // ERROR "escapes" + var asAny A = a + a = asAny.(A) + a.A() + } +} + func testInvalidAsserts() { any(0).(interface{ A() }).A() // ERROR "escapes" { From 20f7c25f1c56a28d74dff9df36008c2ca9b923a3 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Mon, 17 Feb 2025 20:33:05 +0100 Subject: [PATCH 07/74] update Change-Id: I65936baa27e237c82587876a60651e2e649846a2 --- src/cmd/compile/internal/base/debug.go | 1 + src/cmd/compile/internal/devirtualize/devirtualize.go | 4 ++++ test/escape_iface_with_devirt_type_assertions.go | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/cmd/compile/internal/base/debug.go b/src/cmd/compile/internal/base/debug.go index d42e11b2fad881..3a7cc41c43d99f 100644 --- a/src/cmd/compile/internal/base/debug.go +++ b/src/cmd/compile/internal/base/debug.go @@ -75,6 +75,7 @@ type DebugFlags struct { WrapGlobalMapDbg int `help:"debug trace output for global map init wrapping"` WrapGlobalMapCtl int `help:"global map init wrap control (0 => default, 1 => off, 2 => stress mode, no size cutoff)"` ZeroCopy int `help:"enable zero-copy string->[]byte conversions" concurrent:"ok"` + Testing int `help:"testing" concurrent:"ok"` ConcurrentOk bool // true if only concurrentOk flags seen } diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index e8f92ddb0bee4b..3dfe4a9d25bfe6 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -319,6 +319,10 @@ func concreteType2(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Type { for _, p := range n.Lhs { if isName(p) { // TODO: the type can be a map? Same with recv (a chan?) + // TODO: we do not reach here, fix + if base.Debug.Testing != 0 { + base.Warn("%v %v", n.Rhs[0].Type(), n.Rhs[0].Type().Kind()) + } return handleType(n.Rhs[0].Type()) } } diff --git a/test/escape_iface_with_devirt_type_assertions.go b/test/escape_iface_with_devirt_type_assertions.go index 131510cad54888..a61a2447728297 100644 --- a/test/escape_iface_with_devirt_type_assertions.go +++ b/test/escape_iface_with_devirt_type_assertions.go @@ -1,4 +1,4 @@ -// errorcheck -0 -m +// errorcheck -0 -m -d=testing=2 // Copyright 2025 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style From 5fa0efc6430ac5b8c9476418dac688edeef1f5d1 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 18 Feb 2025 08:26:49 +0100 Subject: [PATCH 08/74] add two tests and comment --- .../internal/devirtualize/devirtualize.go | 4 ++- ...scape_iface_with_devirt_type_assertions.go | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 3dfe4a9d25bfe6..9068a2b7e76411 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -183,7 +183,9 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Type { continue case *ir.TypeAssertExpr: if !n.Type().IsInterface() { - return n.Type() // asserting a static type + // Asserting to a static type, take use of that as this will + // cause a runtime panic, if not satisfied at runtime. + return n.Type() } n = n1.X continue diff --git a/test/escape_iface_with_devirt_type_assertions.go b/test/escape_iface_with_devirt_type_assertions.go index a61a2447728297..378e5fbca151ca 100644 --- a/test/escape_iface_with_devirt_type_assertions.go +++ b/test/escape_iface_with_devirt_type_assertions.go @@ -219,6 +219,14 @@ func differentTypeAssign() { } } +func assignWithTypeAssert() { + var i1 A = &Impl{} // ERROR "does not escape" + var i2 A = &Impl2{} // ERROR "does not escape" + i1 = i2.(*Impl) // this will panic + i1.A() // ERROR "devirtualizing i1\.A to \*Impl" "inlining call" + i2.A() // ERROR "devirtualizing i2\.A to \*Impl2" "inlining call" +} + func longDevirtTest() { var a interface { M @@ -750,6 +758,27 @@ func selfAssigns() { } } +func addrTaken() { + { + var a A = &Impl{} // ERROR "escapes" + var ptrA = &a + a.A() + _ = ptrA + } + { + var a A = &Impl{} // ERROR "escapes" + var ptrA = &a + *ptrA = &Impl{} // ERROR "escapes" + a.A() + } + { + var a A = &Impl{} // ERROR "escapes" + var ptrA = &a + *ptrA = &Impl2{} // ERROR "escapes" + a.A() + } +} + func testInvalidAsserts() { any(0).(interface{ A() }).A() // ERROR "escapes" { From 2202ceec571aaf5994acddd4f105fb2278d4bf99 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 18 Feb 2025 12:48:17 +0100 Subject: [PATCH 09/74] more tests, debug messages --- .../internal/devirtualize/devirtualize.go | 68 +++++-- ...scape_iface_with_devirt_type_assertions.go | 173 +++++++++++++++++- 2 files changed, 215 insertions(+), 26 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 9068a2b7e76411..c2e1cc445d829f 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -16,6 +16,7 @@ import ( "cmd/compile/internal/ir" "cmd/compile/internal/typecheck" "cmd/compile/internal/types" + "cmd/internal/src" ) const go125ImprovedConcreteTypeAnalysis = true @@ -157,12 +158,33 @@ func StaticCall(call *ir.CallExpr) { typecheck.FixMethodCall(call) } -func concreteType(n ir.Node) *types.Type { +const concreteTypeDebug = false + +// concreteType determines the concrete type of n, following OCONVIFACEs and type asserts. +// Returns nil when the concrete type could not be determined, or when there are multiple +// (different) types assigned to an interface. +func concreteType(n ir.Node) (typ *types.Type) { return concreteType1(n, make(map[*ir.Name]*types.Type)) } -func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Type { +func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type) (typ *types.Type) { + nn := n // copy for debug messages + + if concreteTypeDebug { + defer func() { + if typ == nil { + base.WarnfAt(n.Pos(), "%v concrete type not found", nn) + } else { + base.WarnfAt(n.Pos(), "%v found concrete type %v", nn, typ) + } + }() + } + for { + if concreteTypeDebug { + base.WarnfAt(n.Pos(), "%v analyzing concrete type of %v", nn, n) + } + switch n1 := n.(type) { case *ir.ConvExpr: if n1.Op() == ir.OCONVNOP && types.Identical(n1.Type(), n1.X.Type()) { @@ -184,7 +206,7 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Type { case *ir.TypeAssertExpr: if !n.Type().IsInterface() { // Asserting to a static type, take use of that as this will - // cause a runtime panic, if not satisfied at runtime. + // cause a runtime panic, if not satisfied. return n.Type() } n = n1.X @@ -238,6 +260,10 @@ func concreteType2(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Type { // executes) will get a nil (from the map lookup above), where we could determine the type. analyzed[name] = nil + if concreteTypeDebug { + base.WarnfAt(name.Pos(), "analyzing assignements to %v", name) + } + // isName reports whether n is a reference to name. isName := func(x ir.Node) bool { if x == nil { @@ -249,12 +275,19 @@ func concreteType2(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Type { var typ *types.Type - handleType := func(t *types.Type) bool { + handleType := func(pos src.XPos, t *types.Type) bool { if t == nil || t.IsInterface() { + if concreteTypeDebug { + base.WarnfAt(pos, "%v assigned with a non concrete type", name) + } typ = nil return true } + if concreteTypeDebug { + base.WarnfAt(pos, "%v assigned with a concrete type %v", name, t) + } + if typ == nil || types.Identical(typ, t) { typ = t return false @@ -269,7 +302,10 @@ func concreteType2(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Type { if n == nil { return false } - return handleType(concreteType1(n, analyzed)) + if concreteTypeDebug { + base.WarnfAt(n.Pos(), "%v found assignement %v = %v, analyzing the RHS node", name, name, n) + } + return handleType(n.Pos(), concreteType1(n, analyzed)) } var do func(n ir.Node) bool @@ -309,7 +345,7 @@ func concreteType2(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Type { if call, ok := rhs.(*ir.CallExpr); ok { retTyp := call.Fun.Type().Results()[i].Type if !retTyp.IsInterface() { - return handleType(retTyp) + return handleType(n.Pos(), retTyp) } } typ = nil @@ -320,12 +356,7 @@ func concreteType2(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Type { n := n.(*ir.AssignListStmt) for _, p := range n.Lhs { if isName(p) { - // TODO: the type can be a map? Same with recv (a chan?) - // TODO: we do not reach here, fix - if base.Debug.Testing != 0 { - base.Warn("%v %v", n.Rhs[0].Type(), n.Rhs[0].Type().Kind()) - } - return handleType(n.Rhs[0].Type()) + return handleType(n.Pos(), n.Rhs[0].Type()) } } case ir.OADDR: @@ -344,27 +375,27 @@ func concreteType2(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Type { if xTyp.IsArray() || xTyp.IsSlice() { if isName(n.Key) { - // This is an index, int has no methods, so nothing - // to devirtualize. + // This is an index, int has no methods, so nothing to devirtualize. typ = nil return true } if isName(n.Value) { - return handleType(xTyp.Elem()) + return handleType(n.Pos(), xTyp.Elem()) } } else if xTyp.IsChan() { if isName(n.Key) { - return handleType(xTyp.Elem()) + return handleType(n.Pos(), xTyp.Elem()) } base.Assertf(n.Value == nil, "n.Value != nil in range over chan") } else if xTyp.IsMap() { if isName(n.Key) { - return handleType(xTyp.Key()) + return handleType(n.Pos(), xTyp.Key()) } if isName(n.Value) { - return handleType(xTyp.Elem()) + return handleType(n.Pos(), xTyp.Elem()) } } else { + // unknown type typ = nil return true } @@ -376,6 +407,7 @@ func concreteType2(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Type { } return false } + ir.Any(name.Curfn, do) analyzed[name] = typ return typ diff --git a/test/escape_iface_with_devirt_type_assertions.go b/test/escape_iface_with_devirt_type_assertions.go index 378e5fbca151ca..673aa7eb56fbf4 100644 --- a/test/escape_iface_with_devirt_type_assertions.go +++ b/test/escape_iface_with_devirt_type_assertions.go @@ -1,4 +1,4 @@ -// errorcheck -0 -m -d=testing=2 +// errorcheck -0 -m -d=testing=1 // Copyright 2025 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style @@ -220,11 +220,55 @@ func differentTypeAssign() { } func assignWithTypeAssert() { - var i1 A = &Impl{} // ERROR "does not escape" - var i2 A = &Impl2{} // ERROR "does not escape" - i1 = i2.(*Impl) // this will panic - i1.A() // ERROR "devirtualizing i1\.A to \*Impl" "inlining call" - i2.A() // ERROR "devirtualizing i2\.A to \*Impl2" "inlining call" + { + var i1 A = &Impl{} // ERROR "does not escape" + var i2 A = &Impl2{} // ERROR "does not escape" + i1 = i2.(*Impl) // this will panic + i1.A() // ERROR "devirtualizing i1\.A to \*Impl" "inlining call" + i2.A() // ERROR "devirtualizing i2\.A to \*Impl2" "inlining call" + } + { + var i1 A = &Impl{} // ERROR "does not escape" + var i2 A = &Impl2{} // ERROR "does not escape" + i1, _ = i2.(*Impl) // i1 is going to be nil + i1.A() // ERROR "devirtualizing i1\.A to \*Impl" "inlining call" + i2.A() // ERROR "devirtualizing i2\.A to \*Impl2" "inlining call" + } +} + +func nilIface() { + // TODO: these cases can be devirtualized. + { + var v A = &Impl{} // ERROR "escapes" + v = nil + v.A() + } + { + var v A = &Impl{} // ERROR "escapes" + v.A() + v = nil + } + { + var nilIface A + var v A = &Impl{} // ERROR "escapes" + v.A() + v = nilIface + } + { + var nilIface A + var v A = &Impl{} // ERROR "escapes" + v = nilIface + v.A() + } + { + var v A + v.A() // ERROR "devirtualizing v\.A to \*Impl" "inlining call" + v = &Impl{} // ERROR "does not escape" + } + { + var v A + v.A() + } } func longDevirtTest() { @@ -448,12 +492,33 @@ func mapsNoDevirt() { } { m := make(map[int]*Impl) // ERROR "does not escape" - var v A + var v A = &Impl{} // ERROR "escapes" v, _ = m[0] - v.A() v = &Impl2{} // ERROR "escapes" v.A() } + + { + m := make(map[int]A) // ERROR "does not escape" + var v A = &Impl{} // ERROR "escapes" + v = m[0] + v.A() + } + { + m := make(map[int]A) // ERROR "does not escape" + var v A = &Impl{} // ERROR "escapes" + var ok bool + if v, ok = m[0]; ok { + v.A() + } + v.A() + } + { + m := make(map[int]A) // ERROR "does not escape" + var v A = &Impl{} // ERROR "escapes" + v, _ = m[0] + v.A() + } } func chanDevirt() { @@ -558,6 +623,45 @@ func chanNoDevirt() { } v.A() } + + { + m := make(chan A) + var v A = &Impl{} // ERROR "escapes" + v = <-m + v.A() + } + { + m := make(chan A) + var v A = &Impl{} // ERROR "escapes" + v, _ = <-m + v.A() + } + { + m := make(chan A) + var v A = &Impl{} // ERROR "escapes" + var ok bool + if v, ok = <-m; ok { + v.A() + } + } + { + m := make(chan A) + var v A = &Impl{} // ERROR "escapes" + select { + case v = <-m: + v.A() + } + v.A() + } + { + m := make(chan A) + var v A = &Impl{} // ERROR "escapes" + select { + case v, _ = <-m: + v.A() + } + v.A() + } } func rangeDevirt() { @@ -707,6 +811,59 @@ func rangeNoDevirt() { } v.A() } + + { + var v A = &Impl{} // ERROR "escapes" + m := make(map[A]struct{}) // ERROR "does not escape" + for v = range m { + } + v.A() + } + { + var v A = &Impl{} // ERROR "escapes" + m := make(map[A]A) // ERROR "does not escape" + for v = range m { + } + v.A() + } + { + var v A = &Impl{} // ERROR "escapes" + m := make(map[A]A) // ERROR "does not escape" + for _, v = range m { + } + v.A() + } + { + var v A = &Impl{} // ERROR "escapes" + m := make(chan A) + for v = range m { + } + v.A() + } + { + var v A = &Impl{} // ERROR "escapes" + m := []A{} // ERROR "does not escape" + for _, v = range m { + } + v.A() + } + + { + var v A + m := [1]A{&Impl{}} // ERROR "escapes" + v = &Impl{} // ERROR "escapes" + for _, v = range m { + } + v.A() + } + { + var v A + m := [1]A{&Impl{}} // ERROR "escapes" + v = &Impl{} // ERROR "escapes" + for _, v = range &m { + } + v.A() + } } type implWrapper Impl From 5af407672ae49f83ce0d3f8c33c517214ef566d6 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 18 Feb 2025 12:50:15 +0100 Subject: [PATCH 10/74] remove testing todo --- src/cmd/compile/internal/base/debug.go | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cmd/compile/internal/base/debug.go b/src/cmd/compile/internal/base/debug.go index 3a7cc41c43d99f..d42e11b2fad881 100644 --- a/src/cmd/compile/internal/base/debug.go +++ b/src/cmd/compile/internal/base/debug.go @@ -75,7 +75,6 @@ type DebugFlags struct { WrapGlobalMapDbg int `help:"debug trace output for global map init wrapping"` WrapGlobalMapCtl int `help:"global map init wrap control (0 => default, 1 => off, 2 => stress mode, no size cutoff)"` ZeroCopy int `help:"enable zero-copy string->[]byte conversions" concurrent:"ok"` - Testing int `help:"testing" concurrent:"ok"` ConcurrentOk bool // true if only concurrentOk flags seen } From 0c16a936f12c1cadbb0493ffe1ae4c92b4ac78f6 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 18 Feb 2025 12:53:12 +0100 Subject: [PATCH 11/74] rename test file --- ...iface_with_devirt_type_assertions.go => devirtualization.go} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename test/{escape_iface_with_devirt_type_assertions.go => devirtualization.go} (99%) diff --git a/test/escape_iface_with_devirt_type_assertions.go b/test/devirtualization.go similarity index 99% rename from test/escape_iface_with_devirt_type_assertions.go rename to test/devirtualization.go index 673aa7eb56fbf4..10699c2304d802 100644 --- a/test/escape_iface_with_devirt_type_assertions.go +++ b/test/devirtualization.go @@ -1,4 +1,4 @@ -// errorcheck -0 -m -d=testing=1 +// errorcheck -0 -m // Copyright 2025 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style From 762df05fa335571a73ed834dd3fdc8c3a0132da7 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 18 Feb 2025 13:04:42 +0100 Subject: [PATCH 12/74] typos --- src/cmd/compile/internal/devirtualize/devirtualize.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index c2e1cc445d829f..f2d52b89d0a716 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -261,7 +261,7 @@ func concreteType2(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Type { analyzed[name] = nil if concreteTypeDebug { - base.WarnfAt(name.Pos(), "analyzing assignements to %v", name) + base.WarnfAt(name.Pos(), "analyzing assignments to %v", name) } // isName reports whether n is a reference to name. @@ -303,7 +303,7 @@ func concreteType2(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Type { return false } if concreteTypeDebug { - base.WarnfAt(n.Pos(), "%v found assignement %v = %v, analyzing the RHS node", name, name, n) + base.WarnfAt(n.Pos(), "%v found assignment %v = %v, analyzing the RHS node", name, name, n) } return handleType(n.Pos(), concreteType1(n, analyzed)) } From 50ba79a72b18df4edee18730063c073531cde2a4 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 18 Feb 2025 13:31:09 +0100 Subject: [PATCH 13/74] update --- src/cmd/compile/internal/devirtualize/devirtualize.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index f2d52b89d0a716..d4c06426e8f604 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -118,8 +118,7 @@ func StaticCall(call *ir.CallExpr) { return } - dt := ir.NewTypeAssertExpr(sel.Pos(), sel.X, nil) - dt.SetType(typ) + dt := ir.NewTypeAssertExpr(sel.Pos(), sel.X, typ) x := typecheck.XDotMethod(sel.Pos(), dt, sel.Sel, true) switch x.Op() { case ir.ODOTMETH: @@ -160,6 +159,9 @@ func StaticCall(call *ir.CallExpr) { const concreteTypeDebug = false +// TODO: add run test that makes sure this work properly with the nil cases. +// TODO: if there is a possibility of a nil, then do not devirtualize. + // concreteType determines the concrete type of n, following OCONVIFACEs and type asserts. // Returns nil when the concrete type could not be determined, or when there are multiple // (different) types assigned to an interface. From 0cc39ff34621a6650cd9e07637ca1c2ecbd638fe Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 18 Feb 2025 14:46:21 +0100 Subject: [PATCH 14/74] add nil checks for devirtualized calls --- .../internal/devirtualize/devirtualize.go | 17 ++++++ src/cmd/compile/internal/ir/expr.go | 2 + src/cmd/compile/internal/ssagen/ssa.go | 13 +++++ test/devirtualization_nil_panics.go | 54 +++++++++++++++++++ 4 files changed, 86 insertions(+) create mode 100644 test/devirtualization_nil_panics.go diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index d4c06426e8f604..44a290b322efed 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -119,6 +119,23 @@ func StaticCall(call *ir.CallExpr) { } dt := ir.NewTypeAssertExpr(sel.Pos(), sel.X, typ) + + if go125ImprovedConcreteTypeAnalysis { + // Mark this type assertion as beeing added from the devirtualizer. + // It is necessary for cases like: + // + // var v Iface + // v.A() + // v = &Impl{} + // + // Here in the devirtualizer, we determine the concrete type of v as beeing an *Impl, but + // in can still be a nil, which we have not detected, it is not a huge problem as + // the v.(*Impl).A() call that we make here would also have failed, but with a different + // panic "A is nil, not *Impl", where previously we would have a nil panic. + // We fix this in the SSA, by introducing an additional nilcheck on the itab. + dt.Devirtualized = true + } + x := typecheck.XDotMethod(sel.Pos(), dt, sel.Sel, true) switch x.Op() { case ir.ODOTMETH: diff --git a/src/cmd/compile/internal/ir/expr.go b/src/cmd/compile/internal/ir/expr.go index 5bd26fc14562f6..0d946d4218556b 100644 --- a/src/cmd/compile/internal/ir/expr.go +++ b/src/cmd/compile/internal/ir/expr.go @@ -677,6 +677,8 @@ type TypeAssertExpr struct { // An internal/abi.TypeAssert descriptor to pass to the runtime. Descriptor *obj.LSym + + Devirtualized bool } func NewTypeAssertExpr(pos src.XPos, x Node, typ *types.Type) *TypeAssertExpr { diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index 07269e65f2fdda..cf454ac4f0268c 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -5642,6 +5642,19 @@ func (s *state) dottype(n *ir.TypeAssertExpr, commaok bool) (res, resok *ssa.Val if n.ITab != nil { targetItab = s.expr(n.ITab) } + + // For devirualized calls, add a special nil check on the itab, see the + // devirtualize package for more info why it is needed. + if n.Devirtualized { + typs := s.f.Config.Types + iface = s.newValue2( + ssa.OpIMake, + iface.Type, + s.nilCheck(s.newValue1(ssa.OpITab, typs.BytePtr, iface)), + s.newValue1(ssa.OpIData, typs.BytePtr, iface), + ) + } + return s.dottype1(n.Pos(), n.X.Type(), n.Type(), iface, nil, target, targetItab, commaok, n.Descriptor) } diff --git a/test/devirtualization_nil_panics.go b/test/devirtualization_nil_panics.go new file mode 100644 index 00000000000000..eca06da84afdad --- /dev/null +++ b/test/devirtualization_nil_panics.go @@ -0,0 +1,54 @@ +// run + +// Copyright 2025 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. + +package main + +import ( + "fmt" + "strings" +) + +type A interface{ A() } + +type Impl struct{} + +func (*Impl) A() {} + +func main() { + shouldNilPanic(func() { + var v A + v.A() + v = &Impl{} + }) + shouldNilPanic(func() { + var v A + defer func() { + v = &Impl{} + }() + v.A() + }) + shouldNilPanic(func() { + var v A + f := func() { + v = &Impl{} + } + v.A() + f() + }) +} + +func shouldNilPanic(f func()) { + defer func() { + p := recover() + if p == nil { + panic("no nil deref panic") + } + if !strings.Contains(fmt.Sprintf("%s", p), "invalid memory address or nil pointer dereference") { + panic(p) + } + }() + f() +} From 53aad3619ff9a5453e555008b373ce2804ac965e Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 18 Feb 2025 14:48:50 +0100 Subject: [PATCH 15/74] reword comment --- src/cmd/compile/internal/devirtualize/devirtualize.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 44a290b322efed..115a5e7e525a6a 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -129,7 +129,7 @@ func StaticCall(call *ir.CallExpr) { // v = &Impl{} // // Here in the devirtualizer, we determine the concrete type of v as beeing an *Impl, but - // in can still be a nil, which we have not detected, it is not a huge problem as + // in can still be a nil, but we have not detected that. It is not a huge problem as // the v.(*Impl).A() call that we make here would also have failed, but with a different // panic "A is nil, not *Impl", where previously we would have a nil panic. // We fix this in the SSA, by introducing an additional nilcheck on the itab. From e887901a3b2f3a2ba83b370d2a4ffdd9739b3082 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 18 Feb 2025 14:51:38 +0100 Subject: [PATCH 16/74] remove todos --- src/cmd/compile/internal/devirtualize/devirtualize.go | 3 --- test/devirtualization.go | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 115a5e7e525a6a..ae2cb4bf26006d 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -176,9 +176,6 @@ func StaticCall(call *ir.CallExpr) { const concreteTypeDebug = false -// TODO: add run test that makes sure this work properly with the nil cases. -// TODO: if there is a possibility of a nil, then do not devirtualize. - // concreteType determines the concrete type of n, following OCONVIFACEs and type asserts. // Returns nil when the concrete type could not be determined, or when there are multiple // (different) types assigned to an interface. diff --git a/test/devirtualization.go b/test/devirtualization.go index 10699c2304d802..d3287c2dd267f3 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -237,7 +237,7 @@ func assignWithTypeAssert() { } func nilIface() { - // TODO: these cases can be devirtualized. + // TODO: these cases can also be devirtualized. { var v A = &Impl{} // ERROR "escapes" v = nil From c9bf31009b2979533a401169bf94445a40934437 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 18 Feb 2025 15:00:57 +0100 Subject: [PATCH 17/74] rename func --- src/cmd/compile/internal/devirtualize/devirtualize.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index ae2cb4bf26006d..8fe035bdafee0e 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -244,11 +244,11 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type) (typ *types.Typ return n.Type() } - return concreteType2(n, analyzed) + return analyzeAssignments(n, analyzed) } } -func concreteType2(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Type { +func analyzeAssignments(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Type { if n.Op() != ir.ONAME { return nil } From 4995c4c7f5a8f56a72a54a8929913bac2b6358b8 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 18 Feb 2025 15:08:14 +0100 Subject: [PATCH 18/74] update comment --- src/cmd/compile/internal/devirtualize/devirtualize.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 8fe035bdafee0e..3c4dce6cad1cf1 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -54,7 +54,7 @@ func StaticCall(call *ir.CallExpr) { // This can happen in such case: any(0).(interface {A()}).A(), this typechecks without // any errors, but will cause a runtime panic. We statically know that int(0) does not // implement that interface, thus we skip the devirtualization, as it is not possible - // to make a type assertion from interface{A()} to int (int does not implement interface{A()}). + // to make an assertion: any(0).(interface{A()}).(int) (int does not implement interface{A()}). if !typecheck.Implements(typ, sel.X.Type()) { return } From 91f92975e405389e225a109d256abaef5774ba6a Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 18 Feb 2025 15:36:22 +0100 Subject: [PATCH 19/74] keep proper line in nil panic --- src/cmd/compile/internal/devirtualize/devirtualize.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 3c4dce6cad1cf1..d9a87f663fb3cb 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -129,11 +129,12 @@ func StaticCall(call *ir.CallExpr) { // v = &Impl{} // // Here in the devirtualizer, we determine the concrete type of v as beeing an *Impl, but - // in can still be a nil, but we have not detected that. It is not a huge problem as - // the v.(*Impl).A() call that we make here would also have failed, but with a different - // panic "A is nil, not *Impl", where previously we would have a nil panic. + // in can still be a nil interface, but we have not detected that. It is not a huge problem as + // the v.(*Impl) type assertion that we make here would also have failed, but with a different + // panic "A is nil, not *Impl", where previously we would get a nil panic. // We fix this in the SSA, by introducing an additional nilcheck on the itab. dt.Devirtualized = true + dt.SetPos(call.Pos()) // keep proper line numbers in the nil panic (for "v.\nA()") } x := typecheck.XDotMethod(sel.Pos(), dt, sel.Sel, true) From d449a95c0d9d5f996e42cef4f2364746fdd01411 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 18 Feb 2025 15:46:35 +0100 Subject: [PATCH 20/74] update --- src/cmd/compile/internal/devirtualize/devirtualize.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index d9a87f663fb3cb..82256456941264 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -310,7 +310,7 @@ func analyzeAssignments(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Typ return false } - // Different type. + // different type typ = nil return true } From ef1a343555622a55704a12391b38a166eaaa33d2 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 18 Feb 2025 16:22:41 +0100 Subject: [PATCH 21/74] add nil panic line number test --- test/devirtualization_nil_panics.go | 59 ++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/test/devirtualization_nil_panics.go b/test/devirtualization_nil_panics.go index eca06da84afdad..4bcc648383c9ff 100644 --- a/test/devirtualization_nil_panics.go +++ b/test/devirtualization_nil_panics.go @@ -8,6 +8,7 @@ package main import ( "fmt" + "runtime" "strings" ) @@ -17,20 +18,24 @@ type Impl struct{} func (*Impl) A() {} +type Impl2 struct{} + +func (*Impl2) A() {} + func main() { - shouldNilPanic(func() { + shouldNilPanic(28, func() { var v A v.A() v = &Impl{} }) - shouldNilPanic(func() { + shouldNilPanic(36, func() { var v A defer func() { v = &Impl{} }() v.A() }) - shouldNilPanic(func() { + shouldNilPanic(43, func() { var v A f := func() { v = &Impl{} @@ -38,17 +43,59 @@ func main() { v.A() f() }) + + // Make sure that both devirtualized and non devirtualized + // variants have the panic at the same line. + shouldNilPanic(55, func() { + var v A + f := func() { + v = &Impl{} + } + v. // A() is on a sepearate line + A() + f() + }) + shouldNilPanic(65, func() { + var v A + defer func() { + v = &Impl{} + v = &Impl2{} // assign different type, such that the call below does not get devirtualized + }() + v. // A() is on a sepearate line + A() + }) } -func shouldNilPanic(f func()) { +var cnt = 0 + +func shouldNilPanic(wantLine int, f func()) { + cnt++ defer func() { p := recover() if p == nil { panic("no nil deref panic") } - if !strings.Contains(fmt.Sprintf("%s", p), "invalid memory address or nil pointer dereference") { - panic(p) + if strings.Contains(fmt.Sprintf("%s", p), "invalid memory address or nil pointer dereference") { + callers := make([]uintptr, 128) + n := runtime.Callers(0, callers) + callers = callers[:n] + + frames := runtime.CallersFrames(callers) + line := -1 + for f, next := frames.Next(); next; f, next = frames.Next() { + if f.Func.Name() == fmt.Sprintf("main.main.func%v", cnt) { + line = f.Line + break + } + } + + if line != wantLine { + panic(fmt.Sprintf("invalid line number in panic = %v; want = %v", line, wantLine)) + } + + return } + panic(p) }() f() } From a40915a08f4d568cb4d64ccabaa163bdcc408c1a Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 18 Feb 2025 18:29:26 +0100 Subject: [PATCH 22/74] update comment, add test case Change-Id: Ie886020240448e36454252e957aec3158a4a2e8e --- src/cmd/compile/internal/devirtualize/devirtualize.go | 5 +++-- test/devirtualization.go | 7 +++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 82256456941264..eaa1f1438a4913 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -222,8 +222,9 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type) (typ *types.Typ continue case *ir.TypeAssertExpr: if !n.Type().IsInterface() { - // Asserting to a static type, take use of that as this will - // cause a runtime panic, if not satisfied. + // Asserting to a static type iface.(T), take use of that + // as this will either cause a runtime panic, or return the zero value + // of T (var v IfaceTyp; v, _ = iface.(T)). return n.Type() } n = n1.X diff --git a/test/devirtualization.go b/test/devirtualization.go index d3287c2dd267f3..d3f36bbdeef1c2 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -138,6 +138,13 @@ func typeAssertsWithOkReturn() { v.M() // ERROR "devirtualizing" "inlining call" } } + { + var impl2InA A = &Impl2{} // ERROR "does not escape" + var a A + a, _ = impl2InA.(*Impl) + // a now contains the zero value of *Impl + a.A() // ERROR "devirtualizing" "inlining call" + } } func newM() M { // ERROR "can inline" From 650c6cd44cdf8bed0a262d014d3a821c4993fb8a Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 18 Feb 2025 18:37:20 +0100 Subject: [PATCH 23/74] make tests identical Change-Id: If772bf85e30e8729c72d601d9bea40e46a3dbd57 --- test/devirtualization_nil_panics.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/devirtualization_nil_panics.go b/test/devirtualization_nil_panics.go index 4bcc648383c9ff..59da454be7f910 100644 --- a/test/devirtualization_nil_panics.go +++ b/test/devirtualization_nil_panics.go @@ -48,14 +48,13 @@ func main() { // variants have the panic at the same line. shouldNilPanic(55, func() { var v A - f := func() { + defer func() { v = &Impl{} - } + }() v. // A() is on a sepearate line A() - f() }) - shouldNilPanic(65, func() { + shouldNilPanic(64, func() { var v A defer func() { v = &Impl{} From d59bfb343aed8146041f8c11fea0ac9f00105c75 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 18 Feb 2025 19:04:42 +0100 Subject: [PATCH 24/74] update comment Change-Id: Ic23f2d02588c299587de99fe2696e9d6bc0c0abc --- src/cmd/compile/internal/devirtualize/devirtualize.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index eaa1f1438a4913..4182ecdbab57e9 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -131,7 +131,7 @@ func StaticCall(call *ir.CallExpr) { // Here in the devirtualizer, we determine the concrete type of v as beeing an *Impl, but // in can still be a nil interface, but we have not detected that. It is not a huge problem as // the v.(*Impl) type assertion that we make here would also have failed, but with a different - // panic "A is nil, not *Impl", where previously we would get a nil panic. + // panic "pkg.Iface is nil, not *pkg.Impl", where previously we would get a nil panic. // We fix this in the SSA, by introducing an additional nilcheck on the itab. dt.Devirtualized = true dt.SetPos(call.Pos()) // keep proper line numbers in the nil panic (for "v.\nA()") From 4874b46803b58c3a4232be6d78fbef36214ab1e6 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 18 Feb 2025 20:19:20 +0100 Subject: [PATCH 25/74] add comment Change-Id: I228642e30eff6c963b91cde87fe37bade9743a49 --- src/cmd/compile/internal/devirtualize/devirtualize.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 4182ecdbab57e9..4cc8b1edfe81ee 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -204,6 +204,7 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type) (typ *types.Typ switch n1 := n.(type) { case *ir.ConvExpr: + // OCONVNOP might change the type, thus check whether they are identical. if n1.Op() == ir.OCONVNOP && types.Identical(n1.Type(), n1.X.Type()) { n = n1.X continue From 7d5ff1962ae798d46f7f7cb918cbc981826f8e29 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Wed, 19 Feb 2025 10:14:23 +0100 Subject: [PATCH 26/74] remove devirtualized bool --- .../compile/internal/devirtualize/devirtualize.go | 9 ++++----- src/cmd/compile/internal/ir/expr.go | 2 -- src/cmd/compile/internal/ssagen/ssa.go | 13 ------------- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 4cc8b1edfe81ee..7b5c4559cf828e 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -121,8 +121,7 @@ func StaticCall(call *ir.CallExpr) { dt := ir.NewTypeAssertExpr(sel.Pos(), sel.X, typ) if go125ImprovedConcreteTypeAnalysis { - // Mark this type assertion as beeing added from the devirtualizer. - // It is necessary for cases like: + // Consider: // // var v Iface // v.A() @@ -132,9 +131,9 @@ func StaticCall(call *ir.CallExpr) { // in can still be a nil interface, but we have not detected that. It is not a huge problem as // the v.(*Impl) type assertion that we make here would also have failed, but with a different // panic "pkg.Iface is nil, not *pkg.Impl", where previously we would get a nil panic. - // We fix this in the SSA, by introducing an additional nilcheck on the itab. - dt.Devirtualized = true - dt.SetPos(call.Pos()) // keep proper line numbers in the nil panic (for "v.\nA()") + // We fix this, by introducing an additional nilcheck on the itab. + nilCheck := ir.NewUnaryExpr(call.Pos(), ir.OCHECKNIL, ir.NewUnaryExpr(call.Pos(), ir.OITAB, sel.X)) + dt.PtrInit().Append(typecheck.Stmt(nilCheck)) } x := typecheck.XDotMethod(sel.Pos(), dt, sel.Sel, true) diff --git a/src/cmd/compile/internal/ir/expr.go b/src/cmd/compile/internal/ir/expr.go index 0d946d4218556b..5bd26fc14562f6 100644 --- a/src/cmd/compile/internal/ir/expr.go +++ b/src/cmd/compile/internal/ir/expr.go @@ -677,8 +677,6 @@ type TypeAssertExpr struct { // An internal/abi.TypeAssert descriptor to pass to the runtime. Descriptor *obj.LSym - - Devirtualized bool } func NewTypeAssertExpr(pos src.XPos, x Node, typ *types.Type) *TypeAssertExpr { diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index cf454ac4f0268c..07269e65f2fdda 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -5642,19 +5642,6 @@ func (s *state) dottype(n *ir.TypeAssertExpr, commaok bool) (res, resok *ssa.Val if n.ITab != nil { targetItab = s.expr(n.ITab) } - - // For devirualized calls, add a special nil check on the itab, see the - // devirtualize package for more info why it is needed. - if n.Devirtualized { - typs := s.f.Config.Types - iface = s.newValue2( - ssa.OpIMake, - iface.Type, - s.nilCheck(s.newValue1(ssa.OpITab, typs.BytePtr, iface)), - s.newValue1(ssa.OpIData, typs.BytePtr, iface), - ) - } - return s.dottype1(n.Pos(), n.X.Type(), n.Type(), iface, nil, target, targetItab, commaok, n.Descriptor) } From de9ca662131171cf90f1442143976552626000a6 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Fri, 21 Feb 2025 17:28:08 +0100 Subject: [PATCH 27/74] update Change-Id: I2adc6bce361f29ef5d59d694004d3c2f57bde443 --- src/cmd/compile/internal/base/debug.go | 1 + .../internal/devirtualize/devirtualize.go | 45 ++++++++++++------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/cmd/compile/internal/base/debug.go b/src/cmd/compile/internal/base/debug.go index d42e11b2fad881..96c8eb8258c345 100644 --- a/src/cmd/compile/internal/base/debug.go +++ b/src/cmd/compile/internal/base/debug.go @@ -75,6 +75,7 @@ type DebugFlags struct { WrapGlobalMapDbg int `help:"debug trace output for global map init wrapping"` WrapGlobalMapCtl int `help:"global map init wrap control (0 => default, 1 => off, 2 => stress mode, no size cutoff)"` ZeroCopy int `help:"enable zero-copy string->[]byte conversions" concurrent:"ok"` + Testing int `help:"enable zero-copy string->[]byte conversions" concurrent:"ok"` ConcurrentOk bool // true if only concurrentOk flags seen } diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 7b5c4559cf828e..32341a4f26275d 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -174,12 +174,14 @@ func StaticCall(call *ir.CallExpr) { typecheck.FixMethodCall(call) } -const concreteTypeDebug = false +// const concreteTypeDebug = false +var concreteTypeDebug = false // concreteType determines the concrete type of n, following OCONVIFACEs and type asserts. // Returns nil when the concrete type could not be determined, or when there are multiple // (different) types assigned to an interface. func concreteType(n ir.Node) (typ *types.Type) { + concreteTypeDebug = base.Debug.Testing != 0 return concreteType1(n, make(map[*ir.Name]*types.Type)) } @@ -293,17 +295,17 @@ func analyzeAssignments(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Typ var typ *types.Type - handleType := func(pos src.XPos, t *types.Type) bool { + handleType := func(dbgOp ir.Op, pos src.XPos, t *types.Type) bool { if t == nil || t.IsInterface() { if concreteTypeDebug { - base.WarnfAt(pos, "%v assigned with a non concrete type", name) + base.WarnfAt(pos, "%v assigned (%v) with a non concrete type", name, dbgOp) } typ = nil return true } if concreteTypeDebug { - base.WarnfAt(pos, "%v assigned with a concrete type %v", name, t) + base.WarnfAt(pos, "%v assigned (%v) with a concrete type %v", name, dbgOp, t) } if typ == nil || types.Identical(typ, t) { @@ -316,14 +318,14 @@ func analyzeAssignments(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Typ return true } - handleNode := func(n ir.Node) bool { + handleNode := func(dbgOp ir.Op, n ir.Node) bool { if n == nil { return false } if concreteTypeDebug { - base.WarnfAt(n.Pos(), "%v found assignment %v = %v, analyzing the RHS node", name, name, n) + base.WarnfAt(n.Pos(), "%v found assignment %v = %v (%v), analyzing the RHS node", name, name, n, dbgOp) } - return handleType(n.Pos(), concreteType1(n, analyzed)) + return handleType(dbgOp, n.Pos(), concreteType1(n, analyzed)) } var do func(n ir.Node) bool @@ -332,20 +334,20 @@ func analyzeAssignments(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Typ case ir.OAS: n := n.(*ir.AssignStmt) if isName(n.X) { - return handleNode(n.Y) + return handleNode(ir.OAS, n.Y) } case ir.OAS2: n := n.(*ir.AssignListStmt) for i, p := range n.Lhs { if isName(p) { - return handleNode(n.Rhs[i]) + return handleNode(ir.OAS2, n.Rhs[i]) } } case ir.OAS2DOTTYPE: n := n.(*ir.AssignListStmt) for _, p := range n.Lhs { if isName(p) { - return handleNode(n.Rhs[0]) + return handleNode(ir.OAS2DOTTYPE, n.Rhs[0]) } } case ir.OAS2FUNC: @@ -363,7 +365,7 @@ func analyzeAssignments(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Typ if call, ok := rhs.(*ir.CallExpr); ok { retTyp := call.Fun.Type().Results()[i].Type if !retTyp.IsInterface() { - return handleType(n.Pos(), retTyp) + return handleType(ir.OAS2FUNC, n.Pos(), retTyp) } } typ = nil @@ -374,7 +376,7 @@ func analyzeAssignments(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Typ n := n.(*ir.AssignListStmt) for _, p := range n.Lhs { if isName(p) { - return handleType(n.Pos(), n.Rhs[0].Type()) + return handleType(n.Op(), n.Pos(), n.Rhs[0].Type()) } } case ir.OADDR: @@ -398,25 +400,36 @@ func analyzeAssignments(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Typ return true } if isName(n.Value) { - return handleType(n.Pos(), xTyp.Elem()) + return handleType(ir.ORANGE, n.Pos(), xTyp.Elem()) } } else if xTyp.IsChan() { if isName(n.Key) { - return handleType(n.Pos(), xTyp.Elem()) + return handleType(ir.ORANGE, n.Pos(), xTyp.Elem()) } base.Assertf(n.Value == nil, "n.Value != nil in range over chan") } else if xTyp.IsMap() { if isName(n.Key) { - return handleType(n.Pos(), xTyp.Key()) + return handleType(ir.ORANGE, n.Pos(), xTyp.Key()) } if isName(n.Value) { - return handleType(n.Pos(), xTyp.Elem()) + return handleType(ir.ORANGE, n.Pos(), xTyp.Elem()) } } else { + // TODO: fatal? We are after range over func rewrite. + // TODO: range over int? // unknown type typ = nil return true } + case ir.OSWITCH: + n := n.(*ir.SwitchStmt) + if guard, ok := n.Tag.(*ir.TypeSwitchGuard); ok { + for _, v := range n.Cases { + if v.Var != nil && isName(v.Var) { + return handleNode(v.Op(), guard.X) + } + } + } case ir.OCLOSURE: n := n.(*ir.ClosureExpr) if ir.Any(n.Func, do) { From 2efc3198e7be3d4720227f3cee42bf2f11d9c125 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Sat, 22 Feb 2025 13:07:19 +0100 Subject: [PATCH 28/74] add more test cases Change-Id: I41aa7dafcd5578170fa20ef3051623c17d008786 --- .../internal/devirtualize/devirtualize.go | 14 +- test/devirtualization.go | 121 ++++++++++++++++++ 2 files changed, 130 insertions(+), 5 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 32341a4f26275d..12ba6820f9f4cd 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -414,18 +414,22 @@ func analyzeAssignments(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Typ if isName(n.Value) { return handleType(ir.ORANGE, n.Pos(), xTyp.Elem()) } - } else { - // TODO: fatal? We are after range over func rewrite. - // TODO: range over int? - // unknown type + } else if xTyp.IsInteger() || xTyp.IsString() { + // range over int/string, results have no methods, so nothing to devirtualize. typ = nil return true + } else { + base.Fatalf("range over unexpected type %v", n.X.Type()) } case ir.OSWITCH: n := n.(*ir.SwitchStmt) if guard, ok := n.Tag.(*ir.TypeSwitchGuard); ok { for _, v := range n.Cases { - if v.Var != nil && isName(v.Var) { + if v.Var == nil { + base.Assert(guard.Tag == nil) + continue + } + if isName(v.Var) { return handleNode(v.Op(), guard.X) } } diff --git a/test/devirtualization.go b/test/devirtualization.go index d3f36bbdeef1c2..383422ece4c86d 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -176,6 +176,60 @@ func newImpl2() *Impl2 { return &Impl2{} // ERROR "escapes" } +func testTypeSwitch() { + { + var v A = &Impl{} // ERROR "does not escape" + switch v := v.(type) { + case A: + v.A() // ERROR "devirtualizing" "inlining call" + case M: + v.M() // ERROR "devirtualizing" "inlining call" + } + } + { + var v A = &Impl{} // ERROR "does not escape" + switch v := v.(type) { + case A: + v.A() // ERROR "devirtualizing" "inlining call" + case M: + v.M() // ERROR "devirtualizing" "inlining call" + v = &Impl{} // ERROR "does not escape" + v.M() // ERROR "devirtualizing" "inlining call" + } + v.(M).M() // ERROR "devirtualizing" "inlining call" + } + { + var v A = &Impl{} // ERROR "escapes" + switch v1 := v.(type) { + case A: + v1.A() + case M: + v1.M() + v = &Impl2{} // ERROR "escapes" + } + } + { + var v A = &Impl{} // ERROR "escapes" + switch v := v.(type) { + case A: + v.A() // ERROR "devirtualizing" "inlining call" + case M: + v.M() // ERROR "devirtualizing" "inlining call" + case C: + v.C() + } + } + { + var v A = &Impl{} // ERROR "does not escape" + switch v := v.(type) { + case M: + v.M() // ERROR "devirtualizing" "inlining call" + default: + panic("does not implement M") // ERROR "escapes" + } + } +} + func differentTypeAssign() { { var a A @@ -408,6 +462,29 @@ func closureNoDevirt2() { c() } +//go:noinline +func testNamedReturn0() (v A) { + v = &Impl{} // ERROR "escapes" + v.A() + return +} + +//go:noinline +func testNamedReturn1() (v A) { + v = &Impl{} // ERROR "escapes" + v.A() + return &Impl{} // ERROR "escapes" +} + +func testNamedReturns3() (v A) { + v = &Impl{} // ERROR "escapes" + defer func() { // ERROR "can inline" "func literal does not escape" + v.A() + }() + v.A() + return &Impl2{} // ERROR "escapes" +} + var ( globalImpl = &Impl{} globalImpl2 = &Impl2{} @@ -873,6 +950,50 @@ func rangeNoDevirt() { } } +var globalInt = 1 + +func testIfInit() { + { + var a A = &Impl{} // ERROR "does not escape" + var i = &Impl{} // ERROR "does not escape" + if a = i; globalInt == 1 { + a.A() // ERROR "devirtualizing" "inlining call" + } + a.A() // ERROR "devirtualizing" "inlining call" + a.(M).M() // ERROR "devirtualizing" "inlining call" + } + { + var a A = &Impl{} // ERROR "escapes" + var i2 = &Impl2{} // ERROR "escapes" + if a = i2; globalInt == 1 { + a.A() + } + a.A() + } +} + +func testSwitchInit() { + { + var a A = &Impl{} // ERROR "does not escape" + var i = &Impl{} // ERROR "does not escape" + switch a = i; globalInt { + case 12: + a.A() // ERROR "devirtualizing" "inlining call" + } + a.A() // ERROR "devirtualizing" "inlining call" + a.(M).M() // ERROR "devirtualizing" "inlining call" + } + { + var a A = &Impl{} // ERROR "escapes" + var i2 = &Impl2{} // ERROR "escapes" + switch a = i2; globalInt { + case 12: + a.A() + } + a.A() + } +} + type implWrapper Impl func (implWrapper) A() {} // ERROR "can inline" From 0952833bdaa11ef30bdbe19fb33935fe82ec2f10 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Sat, 22 Feb 2025 13:13:44 +0100 Subject: [PATCH 29/74] code tweaks Change-Id: Ib422c5aaa3c47f1842561273911ac8d7474dab56 --- .../compile/internal/devirtualize/devirtualize.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 12ba6820f9f4cd..c822a49800a00c 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -379,11 +379,6 @@ func analyzeAssignments(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Typ return handleType(n.Op(), n.Pos(), n.Rhs[0].Type()) } } - case ir.OADDR: - n := n.(*ir.AddrExpr) - if isName(n.X) { - base.FatalfAt(n.Pos(), "%v not marked addrtaken", name) - } case ir.ORANGE: n := n.(*ir.RangeStmt) xTyp := n.X.Type() @@ -415,7 +410,7 @@ func analyzeAssignments(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Typ return handleType(ir.ORANGE, n.Pos(), xTyp.Elem()) } } else if xTyp.IsInteger() || xTyp.IsString() { - // range over int/string, results have no methods, so nothing to devirtualize. + // range over int/string, results do not have methods, so nothing to devirtualize. typ = nil return true } else { @@ -434,6 +429,11 @@ func analyzeAssignments(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Typ } } } + case ir.OADDR: + n := n.(*ir.AddrExpr) + if isName(n.X) { + base.FatalfAt(n.Pos(), "%v not marked addrtaken", name) + } case ir.OCLOSURE: n := n.(*ir.ClosureExpr) if ir.Any(n.Func, do) { From be0df1a83f4c1ff9b99f09e606cf7072fcc13486 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Sun, 23 Feb 2025 18:29:35 +0100 Subject: [PATCH 30/74] update Change-Id: I47372cc2a2f18fb843572757902b1730dd963bde --- src/cmd/compile/internal/base/debug.go | 1 - .../compile/internal/devirtualize/devirtualize.go | 14 ++++++-------- src/cmd/compile/internal/ir/expr.go | 3 +++ src/cmd/compile/internal/ssagen/ssa.go | 11 +++++++++++ 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/cmd/compile/internal/base/debug.go b/src/cmd/compile/internal/base/debug.go index 96c8eb8258c345..d42e11b2fad881 100644 --- a/src/cmd/compile/internal/base/debug.go +++ b/src/cmd/compile/internal/base/debug.go @@ -75,7 +75,6 @@ type DebugFlags struct { WrapGlobalMapDbg int `help:"debug trace output for global map init wrapping"` WrapGlobalMapCtl int `help:"global map init wrap control (0 => default, 1 => off, 2 => stress mode, no size cutoff)"` ZeroCopy int `help:"enable zero-copy string->[]byte conversions" concurrent:"ok"` - Testing int `help:"enable zero-copy string->[]byte conversions" concurrent:"ok"` ConcurrentOk bool // true if only concurrentOk flags seen } diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index c822a49800a00c..8c7fb7c38e31f5 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -127,13 +127,13 @@ func StaticCall(call *ir.CallExpr) { // v.A() // v = &Impl{} // - // Here in the devirtualizer, we determine the concrete type of v as beeing an *Impl, but - // in can still be a nil interface, but we have not detected that. It is not a huge problem as - // the v.(*Impl) type assertion that we make here would also have failed, but with a different + // Here in the devirtualizer, we determine the concrete type of v as beeing an *Impl, + // but in can still be a nil interface, we have not detected that. The v.(*Impl) + // type assertion that we make here would also have failed, but with a different // panic "pkg.Iface is nil, not *pkg.Impl", where previously we would get a nil panic. // We fix this, by introducing an additional nilcheck on the itab. - nilCheck := ir.NewUnaryExpr(call.Pos(), ir.OCHECKNIL, ir.NewUnaryExpr(call.Pos(), ir.OITAB, sel.X)) - dt.PtrInit().Append(typecheck.Stmt(nilCheck)) + dt.EmitItabNilCheck = true + dt.SetPos(call.Pos()) } x := typecheck.XDotMethod(sel.Pos(), dt, sel.Sel, true) @@ -174,14 +174,12 @@ func StaticCall(call *ir.CallExpr) { typecheck.FixMethodCall(call) } -// const concreteTypeDebug = false -var concreteTypeDebug = false +const concreteTypeDebug = false // concreteType determines the concrete type of n, following OCONVIFACEs and type asserts. // Returns nil when the concrete type could not be determined, or when there are multiple // (different) types assigned to an interface. func concreteType(n ir.Node) (typ *types.Type) { - concreteTypeDebug = base.Debug.Testing != 0 return concreteType1(n, make(map[*ir.Name]*types.Type)) } diff --git a/src/cmd/compile/internal/ir/expr.go b/src/cmd/compile/internal/ir/expr.go index 5bd26fc14562f6..a415a6b5132c77 100644 --- a/src/cmd/compile/internal/ir/expr.go +++ b/src/cmd/compile/internal/ir/expr.go @@ -677,6 +677,9 @@ type TypeAssertExpr struct { // An internal/abi.TypeAssert descriptor to pass to the runtime. Descriptor *obj.LSym + + // Emit a nilcheck on the Itab of X. + EmitItabNilCheck bool } func NewTypeAssertExpr(pos src.XPos, x Node, typ *types.Type) *TypeAssertExpr { diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index 07269e65f2fdda..36d0e9efb2ea29 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -5642,6 +5642,17 @@ func (s *state) dottype(n *ir.TypeAssertExpr, commaok bool) (res, resok *ssa.Val if n.ITab != nil { targetItab = s.expr(n.ITab) } + + if n.EmitItabNilCheck { + typs := s.f.Config.Types + iface = s.newValue2( + ssa.OpIMake, + iface.Type, + s.nilCheck(s.newValue1(ssa.OpITab, typs.BytePtr, iface)), + s.newValue1(ssa.OpIData, typs.BytePtr, iface), + ) + } + return s.dottype1(n.Pos(), n.X.Type(), n.Type(), iface, nil, target, targetItab, commaok, n.Descriptor) } From a87211332af71fbec5bbec609a55226099448d3e Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Mon, 24 Feb 2025 20:09:26 +0100 Subject: [PATCH 31/74] simplify Change-Id: I5f1523f505b050ad46bac22938527550a2610622 --- src/cmd/compile/internal/devirtualize/devirtualize.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 8c7fb7c38e31f5..bd0134bf3dbc5f 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -362,9 +362,7 @@ func analyzeAssignments(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Typ } if call, ok := rhs.(*ir.CallExpr); ok { retTyp := call.Fun.Type().Results()[i].Type - if !retTyp.IsInterface() { - return handleType(ir.OAS2FUNC, n.Pos(), retTyp) - } + return handleType(ir.OAS2FUNC, n.Pos(), retTyp) } typ = nil return true From d2911d952a1d47c5aca6f93ac17fd24290da000a Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Mon, 24 Feb 2025 20:29:10 +0100 Subject: [PATCH 32/74] handle properly booleans from v, ok Change-Id: Ibb6e978384c84b2894dabb2319f8121aab27cac1 --- .../internal/devirtualize/devirtualize.go | 28 +++++++++++------- test/devirtualization.go | 29 +++++++++++++++++++ 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index bd0134bf3dbc5f..1926491d413fd6 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -343,10 +343,23 @@ func analyzeAssignments(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Typ } case ir.OAS2DOTTYPE: n := n.(*ir.AssignListStmt) - for _, p := range n.Lhs { - if isName(p) { - return handleNode(ir.OAS2DOTTYPE, n.Rhs[0]) - } + if isName(n.Lhs[0]) { + return handleNode(ir.OAS2DOTTYPE, n.Rhs[0]) + } + if isName(n.Lhs[1]) { + // boolean, nothing to devirtualize. + typ = nil + return true + } + case ir.OAS2MAPR, ir.OAS2RECV, ir.OSELRECV2: + n := n.(*ir.AssignListStmt) + if isName(n.Lhs[0]) { + return handleType(n.Op(), n.Pos(), n.Rhs[0].Type()) + } + if isName(n.Lhs[1]) { + // boolean, nothing to devirtualize. + typ = nil + return true } case ir.OAS2FUNC: n := n.(*ir.AssignListStmt) @@ -368,13 +381,6 @@ func analyzeAssignments(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Typ return true } } - case ir.OAS2MAPR, ir.OAS2RECV, ir.OSELRECV2: - n := n.(*ir.AssignListStmt) - for _, p := range n.Lhs { - if isName(p) { - return handleType(n.Op(), n.Pos(), n.Rhs[0].Type()) - } - } case ir.ORANGE: n := n.(*ir.RangeStmt) xTyp := n.X.Type() diff --git a/test/devirtualization.go b/test/devirtualization.go index 383422ece4c86d..e5e08e28168fbf 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -1043,6 +1043,35 @@ func selfAssigns() { } } +func boolNoDevirt() { + { + m := make(map[int]*Impl) // ERROR "does not escape" + var v any = &Impl{} // ERROR "escapes" + _, v = m[0] // ERROR "escapes" + v.(A).A() + } + { + m := make(chan *Impl) + var v any = &Impl{} // ERROR "escapes" + select { + case _, v = <-m: // ERROR "escapes" + } + v.(A).A() + } + { + m := make(chan *Impl) + var v any = &Impl{} // ERROR "escapes" + _, v = <-m // ERROR "escapes" + v.(A).A() + } + { + var a any = 4 // ERROR "does not escape" + var v any = &Impl{} // ERROR "escapes" + _, v = a.(int) // ERROR "escapes" + v.(A).A() + } +} + func addrTaken() { { var a A = &Impl{} // ERROR "escapes" From 20574781fc467cb400d1b3640401973651e362bb Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Mon, 24 Feb 2025 20:51:08 +0100 Subject: [PATCH 33/74] add noinline for newinliner Change-Id: I4105f949b41c8443a1b11c5590a84a4ecfbbd2a3 --- test/devirtualization.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/devirtualization.go b/test/devirtualization.go index e5e08e28168fbf..afe54ab39e4776 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -408,6 +408,7 @@ func deferNoDevirt() { a.A() } +//go:noinline func closureDevirt() { var a A func() { // ERROR "func literal does not escape" @@ -419,6 +420,7 @@ func closureDevirt() { a.A() // ERROR "devirtualizing" "inlining call" } +//go:noinline func closureNoDevirt() { var a A func() { // ERROR "func literal does not escape" From 26c8c121d1fc3c47259d3bf0acd3b7d4de72a2e6 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 25 Feb 2025 14:45:18 +0100 Subject: [PATCH 34/74] tmp --- src/cmd/compile/internal/base/debug.go | 1 + .../internal/devirtualize/devirtualize.go | 275 ++++++++---------- test/devirtualization.go | 58 ++-- 3 files changed, 148 insertions(+), 186 deletions(-) diff --git a/src/cmd/compile/internal/base/debug.go b/src/cmd/compile/internal/base/debug.go index d42e11b2fad881..d63b41b55b186c 100644 --- a/src/cmd/compile/internal/base/debug.go +++ b/src/cmd/compile/internal/base/debug.go @@ -75,6 +75,7 @@ type DebugFlags struct { WrapGlobalMapDbg int `help:"debug trace output for global map init wrapping"` WrapGlobalMapCtl int `help:"global map init wrap control (0 => default, 1 => off, 2 => stress mode, no size cutoff)"` ZeroCopy int `help:"enable zero-copy string->[]byte conversions" concurrent:"ok"` + Testing int `help:"here" concurrent:"ok"` ConcurrentOk bool // true if only concurrentOk flags seen } diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 1926491d413fd6..fbf430b1d89837 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -16,14 +16,16 @@ import ( "cmd/compile/internal/ir" "cmd/compile/internal/typecheck" "cmd/compile/internal/types" - "cmd/internal/src" ) -const go125ImprovedConcreteTypeAnalysis = true +// const go125ImprovedConcreteTypeAnalysis = true +var go125ImprovedConcreteTypeAnalysis = false // StaticCall devirtualizes the given call if possible when the concrete callee // is available statically. func StaticCall(call *ir.CallExpr) { + go125ImprovedConcreteTypeAnalysis = base.Debug.Testing != 0 + // For promoted methods (including value-receiver methods promoted // to pointer-receivers), the interface method wrapper may contain // expressions that can panic (e.g., ODEREF, ODOTPTR, @@ -119,23 +121,6 @@ func StaticCall(call *ir.CallExpr) { } dt := ir.NewTypeAssertExpr(sel.Pos(), sel.X, typ) - - if go125ImprovedConcreteTypeAnalysis { - // Consider: - // - // var v Iface - // v.A() - // v = &Impl{} - // - // Here in the devirtualizer, we determine the concrete type of v as beeing an *Impl, - // but in can still be a nil interface, we have not detected that. The v.(*Impl) - // type assertion that we make here would also have failed, but with a different - // panic "pkg.Iface is nil, not *pkg.Impl", where previously we would get a nil panic. - // We fix this, by introducing an additional nilcheck on the itab. - dt.EmitItabNilCheck = true - dt.SetPos(call.Pos()) - } - x := typecheck.XDotMethod(sel.Pos(), dt, sel.Sel, true) switch x.Op() { case ir.ODOTMETH: @@ -180,10 +165,30 @@ const concreteTypeDebug = false // Returns nil when the concrete type could not be determined, or when there are multiple // (different) types assigned to an interface. func concreteType(n ir.Node) (typ *types.Type) { - return concreteType1(n, make(map[*ir.Name]*types.Type)) + var assignements map[*ir.Name][]valOrTyp + var baseFunc *ir.Func + return concreteType1(n, func(n *ir.Name) []valOrTyp { + if assignements == nil { + assignements = make(map[*ir.Name][]valOrTyp) + if n.Curfn == nil { + base.Fatalf("n.Curfn == nil: %v", n) + } + assignements = ifaceAssignments(n.Curfn) + baseFunc = n.Curfn + } + if !n.Type().IsInterface() { + base.Fatalf("node passed to getAssignements is not an interface: %v", n.Type()) + } + if n.Curfn != baseFunc { + base.Fatalf("unexpected n.Curfn = %v; want = %v", n, baseFunc) + } + return assignements[n] + }) } -func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type) (typ *types.Type) { +// TODO: check CloneHash case (method returns a Hash that should be devirted). + +func concreteType1(n ir.Node, getAssignements func(*ir.Name) []valOrTyp) (typ *types.Type) { nn := n // copy for debug messages if concreteTypeDebug { @@ -201,9 +206,25 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type) (typ *types.Typ base.WarnfAt(n.Pos(), "%v analyzing concrete type of %v", nn, n) } + if !n.Type().IsInterface() { + if n, ok := n.(*ir.CallExpr); ok { + if n.Fun != nil { + results := n.Fun.Type().Results() + base.Assert(len(results) == 1) + retTyp := results[0].Type + if !retTyp.IsInterface() { + // TODO: isnt n.Type going to return that? + return retTyp + } + } + } + return n.Type() + } + switch n1 := n.(type) { case *ir.ConvExpr: // OCONVNOP might change the type, thus check whether they are identical. + // TODO: can this happen for iface? if n1.Op() == ir.OCONVNOP && types.Identical(n1.Type(), n1.X.Type()) { n = n1.X continue @@ -221,36 +242,12 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type) (typ *types.Typ n = n1.X continue case *ir.TypeAssertExpr: - if !n.Type().IsInterface() { - // Asserting to a static type iface.(T), take use of that - // as this will either cause a runtime panic, or return the zero value - // of T (var v IfaceTyp; v, _ = iface.(T)). - return n.Type() - } n = n1.X continue - case *ir.CallExpr: - if n1.Fun != nil { - results := n1.Fun.Type().Results() - if len(results) == 1 { - retTyp := results[0].Type - if !retTyp.IsInterface() { - return retTyp - } - } - } - return nil } - - if !n.Type().IsInterface() { - return n.Type() - } - - return analyzeAssignments(n, analyzed) + break } -} -func analyzeAssignments(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Type { if n.Op() != ir.ONAME { return nil } @@ -268,118 +265,105 @@ func analyzeAssignments(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Typ return nil // conservatively assume it's reassigned with a different type indirectly } - if typ, ok := analyzed[name]; ok { - return typ + if name.Curfn == nil { + // TODO: think + base.Fatalf("nil Func %v", name) } - // For now set the Type to nil, as we don't know it yet, we will update - // it at the end of this function, if we find a concrete type. - // This is not ideal, as in-process concreteType1 calls (that this function also - // executes) will get a nil (from the map lookup above), where we could determine the type. - analyzed[name] = nil + assignements := getAssignements(name) + if len(assignements) == 0 { + return nil // call on a nil interface + } - if concreteTypeDebug { - base.WarnfAt(name.Pos(), "analyzing assignments to %v", name) + if v := assignements[0]; v.typ != nil { + typ = v.typ + } else if v.node != nil { + typ = concreteType1(v.node, getAssignements) + } else { + return nil } - // isName reports whether n is a reference to name. - isName := func(x ir.Node) bool { - if x == nil { - return false + for _, v := range assignements[1:] { + t := v.typ + if v.node != nil { + t = concreteType1(v.node, getAssignements) + } + if t == nil || !types.Identical(typ, t) { + return nil } - n, ok := ir.OuterValue(x).(*ir.Name) - return ok && n.Canonical() == name } - var typ *types.Type + return typ +} - handleType := func(dbgOp ir.Op, pos src.XPos, t *types.Type) bool { - if t == nil || t.IsInterface() { - if concreteTypeDebug { - base.WarnfAt(pos, "%v assigned (%v) with a non concrete type", name, dbgOp) - } - typ = nil - return true - } +type valOrTyp struct { + typ *types.Type + node ir.Node +} - if concreteTypeDebug { - base.WarnfAt(pos, "%v assigned (%v) with a concrete type %v", name, dbgOp, t) - } +func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { + out := make(map[*ir.Name][]valOrTyp) - if typ == nil || types.Identical(typ, t) { - typ = t - return false + assign := func(name ir.Node, value valOrTyp) { + if name == nil { + return } - - // different type - typ = nil - return true - } - - handleNode := func(dbgOp ir.Op, n ir.Node) bool { - if n == nil { - return false + n, ok := ir.OuterValue(name).(*ir.Name) + if !ok { + return } - if concreteTypeDebug { - base.WarnfAt(n.Pos(), "%v found assignment %v = %v (%v), analyzing the RHS node", name, name, n, dbgOp) + n = n.Canonical() + if n.Curfn != fun || !n.Type().IsInterface() { + return } - return handleType(dbgOp, n.Pos(), concreteType1(n, analyzed)) + out[n] = append(out[n], value) } - var do func(n ir.Node) bool - do = func(n ir.Node) bool { + var do func(n ir.Node) + do = func(n ir.Node) { switch n.Op() { case ir.OAS: n := n.(*ir.AssignStmt) - if isName(n.X) { - return handleNode(ir.OAS, n.Y) + if n.Y != nil { + assign(n.X, valOrTyp{node: n.Y}) } case ir.OAS2: n := n.(*ir.AssignListStmt) for i, p := range n.Lhs { - if isName(p) { - return handleNode(ir.OAS2, n.Rhs[i]) + if n.Rhs[i] != nil { + assign(p, valOrTyp{node: n.Rhs[i]}) } } case ir.OAS2DOTTYPE: n := n.(*ir.AssignListStmt) - if isName(n.Lhs[0]) { - return handleNode(ir.OAS2DOTTYPE, n.Rhs[0]) - } - if isName(n.Lhs[1]) { - // boolean, nothing to devirtualize. - typ = nil - return true + if n.Rhs[0] == nil { + base.Fatalf("n.Rhs[0] == nil; n = %v", n) } + assign(n.Lhs[0], valOrTyp{node: n.Rhs[0]}) + assign(n.Lhs[1], valOrTyp{}) // TODO: boolean case ir.OAS2MAPR, ir.OAS2RECV, ir.OSELRECV2: n := n.(*ir.AssignListStmt) - if isName(n.Lhs[0]) { - return handleType(n.Op(), n.Pos(), n.Rhs[0].Type()) - } - if isName(n.Lhs[1]) { - // boolean, nothing to devirtualize. - typ = nil - return true + if n.Rhs[0] == nil { + base.Fatalf("n.Rhs[0] == nil; n = %v", n) } + assign(n.Lhs[0], valOrTyp{typ: n.Rhs[0].Type()}) + assign(n.Lhs[1], valOrTyp{}) // TODO: boolean case ir.OAS2FUNC: n := n.(*ir.AssignListStmt) for i, p := range n.Lhs { - if isName(p) { - rhs := n.Rhs[0] - for { - if r, ok := rhs.(*ir.ParenExpr); ok { - rhs = r.X - continue - } - break - } - if call, ok := rhs.(*ir.CallExpr); ok { - retTyp := call.Fun.Type().Results()[i].Type - return handleType(ir.OAS2FUNC, n.Pos(), retTyp) + rhs := n.Rhs[0] + for { + if r, ok := rhs.(*ir.ParenExpr); ok { + rhs = r.X + continue } - typ = nil - return true + break + } + if call, ok := rhs.(*ir.CallExpr); ok { + retTyp := call.Fun.Type().Results()[i].Type + assign(p, valOrTyp{typ: retTyp}) } + assign(p, valOrTyp{}) } case ir.ORANGE: n := n.(*ir.RangeStmt) @@ -391,30 +375,18 @@ func analyzeAssignments(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Typ } if xTyp.IsArray() || xTyp.IsSlice() { - if isName(n.Key) { - // This is an index, int has no methods, so nothing to devirtualize. - typ = nil - return true - } - if isName(n.Value) { - return handleType(ir.ORANGE, n.Pos(), xTyp.Elem()) - } + assign(n.Key, valOrTyp{}) // boolean + assign(n.Value, valOrTyp{typ: xTyp.Elem()}) } else if xTyp.IsChan() { - if isName(n.Key) { - return handleType(ir.ORANGE, n.Pos(), xTyp.Elem()) - } + assign(n.Key, valOrTyp{typ: xTyp.Elem()}) base.Assertf(n.Value == nil, "n.Value != nil in range over chan") } else if xTyp.IsMap() { - if isName(n.Key) { - return handleType(ir.ORANGE, n.Pos(), xTyp.Key()) - } - if isName(n.Value) { - return handleType(ir.ORANGE, n.Pos(), xTyp.Elem()) - } + assign(n.Key, valOrTyp{typ: xTyp.Key()}) + assign(n.Value, valOrTyp{typ: xTyp.Elem()}) } else if xTyp.IsInteger() || xTyp.IsString() { // range over int/string, results do not have methods, so nothing to devirtualize. - typ = nil - return true + assign(n.Key, valOrTyp{}) + assign(n.Value, valOrTyp{}) } else { base.Fatalf("range over unexpected type %v", n.X.Type()) } @@ -426,26 +398,13 @@ func analyzeAssignments(n ir.Node, analyzed map[*ir.Name]*types.Type) *types.Typ base.Assert(guard.Tag == nil) continue } - if isName(v.Var) { - return handleNode(v.Op(), guard.X) - } + assign(v.Var, valOrTyp{node: guard.X}) } } - case ir.OADDR: - n := n.(*ir.AddrExpr) - if isName(n.X) { - base.FatalfAt(n.Pos(), "%v not marked addrtaken", name) - } case ir.OCLOSURE: - n := n.(*ir.ClosureExpr) - if ir.Any(n.Func, do) { - return true - } + ir.Visit(n.(*ir.ClosureExpr).Func, do) } - return false } - - ir.Any(name.Curfn, do) - analyzed[name] = typ - return typ + ir.Visit(fun, do) + return out } diff --git a/test/devirtualization.go b/test/devirtualization.go index afe54ab39e4776..33e4c1635d1562 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -1,4 +1,4 @@ -// errorcheck -0 -m +// errorcheck -0 -m -d=testing=2 // Copyright 2025 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style @@ -298,7 +298,6 @@ func assignWithTypeAssert() { } func nilIface() { - // TODO: these cases can also be devirtualized. { var v A = &Impl{} // ERROR "escapes" v = nil @@ -1017,32 +1016,35 @@ func devirtWrapperType() { } func selfAssigns() { - { - var a A = &Impl{} // ERROR "escapes" - a = a - a.A() - } - { - var a A = &Impl{} // ERROR "escapes" - var asAny any = a - asAny = asAny - asAny.(A).A() - } - { - var a A = &Impl{} // ERROR "escapes" - var asAny any = a - asAny = asAny - a = asAny.(A) - asAny = a - asAny.(A).A() - asAny.(M).M() - } - { - var a A = &Impl{} // ERROR "escapes" - var asAny A = a - a = asAny.(A) - a.A() - } + // { + // var a A = &Impl{} // ERROR "escapes" + // a = a + // a.A() + // } + // + // { + // var a A = &Impl{} // ERROR "escapes" + // var asAny any = a + // asAny = asAny + // asAny.(A).A() + // } + // + // { + // var a A = &Impl{} // ERROR "escapes" + // var asAny any = a + // asAny = asAny + // a = asAny.(A) + // asAny = a + // asAny.(A).A() + // asAny.(M).M() + // } + // + // { + // var a A = &Impl{} // ERROR "escapes" + // var asAny A = a + // a = asAny.(A) + // a.A() + // } } func boolNoDevirt() { From d099b815d8883b7868b6970d21fb4a42e9d036a6 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 25 Feb 2025 15:18:21 +0100 Subject: [PATCH 35/74] update --- .../internal/devirtualize/devirtualize.go | 45 ++++++------- test/devirtualization.go | 64 ++++++++++--------- 2 files changed, 56 insertions(+), 53 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index fbf430b1d89837..6ca5fba2a8ffc7 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -159,15 +159,13 @@ func StaticCall(call *ir.CallExpr) { typecheck.FixMethodCall(call) } -const concreteTypeDebug = false - // concreteType determines the concrete type of n, following OCONVIFACEs and type asserts. // Returns nil when the concrete type could not be determined, or when there are multiple // (different) types assigned to an interface. func concreteType(n ir.Node) (typ *types.Type) { var assignements map[*ir.Name][]valOrTyp var baseFunc *ir.Func - return concreteType1(n, func(n *ir.Name) []valOrTyp { + return concreteType1(n, make(map[*ir.Name]*types.Type), func(n *ir.Name) []valOrTyp { if assignements == nil { assignements = make(map[*ir.Name][]valOrTyp) if n.Curfn == nil { @@ -188,24 +186,8 @@ func concreteType(n ir.Node) (typ *types.Type) { // TODO: check CloneHash case (method returns a Hash that should be devirted). -func concreteType1(n ir.Node, getAssignements func(*ir.Name) []valOrTyp) (typ *types.Type) { - nn := n // copy for debug messages - - if concreteTypeDebug { - defer func() { - if typ == nil { - base.WarnfAt(n.Pos(), "%v concrete type not found", nn) - } else { - base.WarnfAt(n.Pos(), "%v found concrete type %v", nn, typ) - } - }() - } - +func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements func(*ir.Name) []valOrTyp) *types.Type { for { - if concreteTypeDebug { - base.WarnfAt(n.Pos(), "%v analyzing concrete type of %v", nn, n) - } - if !n.Type().IsInterface() { if n, ok := n.(*ir.CallExpr); ok { if n.Fun != nil { @@ -266,19 +248,30 @@ func concreteType1(n ir.Node, getAssignements func(*ir.Name) []valOrTyp) (typ *t } if name.Curfn == nil { - // TODO: think + // TODO: think, we should hit here with an iface global? base.Fatalf("nil Func %v", name) } + if typ, ok := analyzed[name]; ok { + return typ + } + + // For now set the Type to nil, as we don't know it yet, we will update + // it at the end of this function, if we find a concrete type. + // This is not ideal, as in-process concreteType1 calls (that this function also + // executes) will get a nil (from the map lookup above), where we could determine the type. + analyzed[name] = nil + assignements := getAssignements(name) if len(assignements) == 0 { return nil // call on a nil interface } + var typ *types.Type if v := assignements[0]; v.typ != nil { typ = v.typ } else if v.node != nil { - typ = concreteType1(v.node, getAssignements) + typ = concreteType1(v.node, analyzed, getAssignements) } else { return nil } @@ -286,13 +279,14 @@ func concreteType1(n ir.Node, getAssignements func(*ir.Name) []valOrTyp) (typ *t for _, v := range assignements[1:] { t := v.typ if v.node != nil { - t = concreteType1(v.node, getAssignements) + t = concreteType1(v.node, analyzed, getAssignements) } if t == nil || !types.Identical(typ, t) { return nil } } + analyzed[name] = typ return typ } @@ -305,7 +299,7 @@ func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { out := make(map[*ir.Name][]valOrTyp) assign := func(name ir.Node, value valOrTyp) { - if name == nil { + if name == nil || name.Op() != ir.ONAME { return } n, ok := ir.OuterValue(name).(*ir.Name) @@ -313,6 +307,9 @@ func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { return } n = n.Canonical() + if n.Op() != ir.ONAME { + base.Fatalf("reassigned %v", n) + } if n.Curfn != fun || !n.Type().IsInterface() { return } diff --git a/test/devirtualization.go b/test/devirtualization.go index 33e4c1635d1562..8463114dccc2b8 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -1016,35 +1016,41 @@ func devirtWrapperType() { } func selfAssigns() { - // { - // var a A = &Impl{} // ERROR "escapes" - // a = a - // a.A() - // } - // - // { - // var a A = &Impl{} // ERROR "escapes" - // var asAny any = a - // asAny = asAny - // asAny.(A).A() - // } - // - // { - // var a A = &Impl{} // ERROR "escapes" - // var asAny any = a - // asAny = asAny - // a = asAny.(A) - // asAny = a - // asAny.(A).A() - // asAny.(M).M() - // } - // - // { - // var a A = &Impl{} // ERROR "escapes" - // var asAny A = a - // a = asAny.(A) - // a.A() - // } + { + var a A = &Impl{} // ERROR "escapes" + a = a + a.A() + } + { + var a A = &Impl{} // ERROR "escapes" + var asAny any = a + asAny = asAny + asAny.(A).A() + } + { + var a A = &Impl{} // ERROR "escapes" + var asAny any = a + a = asAny.(A) + asAny.(A).A() + a.(A).A() + b := a + b.(A).A() + } + { + var a A = &Impl{} // ERROR "escapes" + var asAny any = a + asAny = asAny + a = asAny.(A) + asAny = a + asAny.(A).A() + asAny.(M).M() + } + { + var a A = &Impl{} // ERROR "escapes" + var asAny A = a + a = asAny.(A) + a.A() + } } func boolNoDevirt() { From 2464ac8b3b910ba04b1ce8cf48dfde18942cf9ea Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 25 Feb 2025 15:32:52 +0100 Subject: [PATCH 36/74] update --- .../internal/devirtualize/devirtualize.go | 2 - test/devirtualization.go | 45 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 6ca5fba2a8ffc7..d81d1a45ea8821 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -184,8 +184,6 @@ func concreteType(n ir.Node) (typ *types.Type) { }) } -// TODO: check CloneHash case (method returns a Hash that should be devirted). - func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements func(*ir.Name) []valOrTyp) *types.Type { for { if !n.Type().IsInterface() { diff --git a/test/devirtualization.go b/test/devirtualization.go index 8463114dccc2b8..663de139934b0b 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -1125,3 +1125,48 @@ func testInvalidAsserts() { a.(any).(M).(*Impl).M() // ERROR "inlining" } } + +type hash interface { + Sum() []byte +} + +type hashWithClone interface { + Sum() []byte + Clone() hash +} + +type clonableHash struct{ state [32]byte } + +func (h *clonableHash) Sum() []byte { // ERROR "can inline" "h does not escape" + return make([]byte, 32) // ERROR "escapes" +} + +func (h *clonableHash) Clone() hash { // ERROR "can inline" "h does not escape" + c := *h // ERROR "moved to heap: c" + return &c +} + +func newHash() hash { // ERROR "can inline" + return &clonableHash{} // ERROR "escapes" +} + +func cloneHash(h hash) (hash, bool) { // ERROR "can inline" "leaking param: h" + if h, ok := h.(hashWithClone); ok { + return h.Clone(), true + } + return nil, false +} + +func devirtIfaceCallThatReturnsIface() { + h := newHash() // ERROR "&clonableHash{} does not escape" "inlining call" + _ = h.Sum() // ERROR "devirtualizing h.Sum to \*clonableHash" "inlining call" "make\(\[\]byte, 32\) does not escape" + + // TODO: make this non-escaping: + h2, ok := cloneHash(h) // ERROR "inlining call to cloneHash" "devirtualizing h.Clone to \*clonableHash" "inlining call to \(\*clonableHash\).Clone" "moved to heap: c" + if !ok { + panic("unexpected") // ERROR "escapes" + } + + // TODO: make this devirtualize: + _ = h2.Sum() +} From 8beaa2293310343cf4980e2b52760ef81eb42dad Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 25 Feb 2025 20:10:24 +0100 Subject: [PATCH 37/74] update Change-Id: I6fe302de0a0e37b8186ace8462c1694271020864 --- .../internal/devirtualize/devirtualize.go | 60 +++++++++- src/cmd/compile/internal/ir/expr.go | 10 +- test/devirtualization.go | 2 +- ...zation_with_type_assertions_interleaved.go | 107 ++++++++++++++++++ 4 files changed, 173 insertions(+), 6 deletions(-) create mode 100644 test/devirtualization_with_type_assertions_interleaved.go diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index d81d1a45ea8821..8745643ae5c2d0 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -16,10 +16,14 @@ import ( "cmd/compile/internal/ir" "cmd/compile/internal/typecheck" "cmd/compile/internal/types" + "cmd/internal/src" ) +// TODO: update tests to include full errors. + // const go125ImprovedConcreteTypeAnalysis = true var go125ImprovedConcreteTypeAnalysis = false +var debug = false // StaticCall devirtualizes the given call if possible when the concrete callee // is available statically. @@ -134,6 +138,7 @@ func StaticCall(call *ir.CallExpr) { if base.Flag.LowerM != 0 { base.WarnfAt(call.Pos(), "partially devirtualizing %v to %v", sel, typ) } + // TODO: with interleaved inlining and devirtualization we migth get here multiple times for the same call. call.SetOp(ir.OCALLINTER) call.Fun = x default: @@ -184,8 +189,22 @@ func concreteType(n ir.Node) (typ *types.Type) { }) } -func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements func(*ir.Name) []valOrTyp) *types.Type { +func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements func(*ir.Name) []valOrTyp) (out *types.Type) { + nn := n + + if go125ImprovedConcreteTypeAnalysis { + defer func() { + if debug { + base.WarnfAt(nn.Pos(), "concreteType1(%v) -> %v", nn, out) + } + }() + } + for { + if debug { + base.WarnfAt(n.Pos(), "concreteType1(%v) current iteration node: %v", nn, n) + } + if !n.Type().IsInterface() { if n, ok := n.(*ir.CallExpr); ok { if n.Fun != nil { @@ -215,6 +234,7 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements } case *ir.InlinedCallExpr: if n1.Op() == ir.OINLCALL { + // TODO: single is fine? n = n1.SingleResult() continue } @@ -225,6 +245,7 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements n = n1.X continue } + break } @@ -250,6 +271,10 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements base.Fatalf("nil Func %v", name) } + if debug { + base.WarnfAt(src.NoXPos, "concreteType1(%v) name: %v, analyzing assignements", nn, name) + } + if typ, ok := analyzed[name]; ok { return typ } @@ -267,16 +292,29 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements var typ *types.Type if v := assignements[0]; v.typ != nil { + if debug { + base.WarnfAt(src.NoXPos, "concreteType1(%v) name: %v, assigned with concrete type %v", nn, name, v.typ) + } typ = v.typ } else if v.node != nil { + if debug { + base.WarnfAt(v.node.Pos(), "concreteType1(%v) name: %v, assigned with %v", nn, name, v.node) + } typ = concreteType1(v.node, analyzed, getAssignements) } else { + // TODO: tu return nil } for _, v := range assignements[1:] { t := v.typ + if t != nil && debug { + base.WarnfAt(src.NoXPos, "concreteType1(%v) name: %v, assigned with concrete type %v", nn, name, v.typ) + } if v.node != nil { + if debug { + base.WarnfAt(v.node.Pos(), "concreteType1(%v) name: %v, assigned with %v", nn, name, v.node) + } t = concreteType1(v.node, analyzed, getAssignements) } if t == nil || !types.Identical(typ, t) { @@ -311,6 +349,21 @@ func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { if n.Curfn != fun || !n.Type().IsInterface() { return } + + // TODO: add test case that fails w/o this. + if value.typ != nil && value.typ.IsInterface() { + value.typ = nil + } + + // TODO: explain + if ir.IsNil(value.node) { + return + } + + if debug { + base.WarnfAt(src.NoXPos, "assign %v = {%v;%v}", n, value.typ, value.node) + } + out[n] = append(out[n], value) } @@ -357,8 +410,11 @@ func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { if call, ok := rhs.(*ir.CallExpr); ok { retTyp := call.Fun.Type().Results()[i].Type assign(p, valOrTyp{typ: retTyp}) + } else if call, ok := rhs.(*ir.InlinedCallExpr); ok { + assign(p, valOrTyp{node: call.Result(i)}) + } else { + assign(p, valOrTyp{}) } - assign(p, valOrTyp{}) } case ir.ORANGE: n := n.(*ir.RangeStmt) diff --git a/src/cmd/compile/internal/ir/expr.go b/src/cmd/compile/internal/ir/expr.go index a415a6b5132c77..5d088651cdd452 100644 --- a/src/cmd/compile/internal/ir/expr.go +++ b/src/cmd/compile/internal/ir/expr.go @@ -386,15 +386,19 @@ func (n *InlinedCallExpr) SingleResult() Node { if have := len(n.ReturnVars); have != 1 { base.FatalfAt(n.Pos(), "inlined call has %v results, expected 1", have) } - if !n.Type().HasShape() && n.ReturnVars[0].Type().HasShape() { + return n.Result(0) +} + +func (n *InlinedCallExpr) Result(i int) Node { + if !n.Type().HasShape() && n.ReturnVars[i].Type().HasShape() { // If the type of the call is not a shape, but the type of the return value // is a shape, we need to do an implicit conversion, so the real type // of n is maintained. - r := NewConvExpr(n.Pos(), OCONVNOP, n.Type(), n.ReturnVars[0]) + r := NewConvExpr(n.Pos(), OCONVNOP, n.Type(), n.ReturnVars[i]) r.SetTypecheck(1) return r } - return n.ReturnVars[0] + return n.ReturnVars[i] } // A LogicalExpr is an expression X Op Y where Op is && or ||. diff --git a/test/devirtualization.go b/test/devirtualization.go index 663de139934b0b..51cd922aa76b69 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -1154,7 +1154,7 @@ func cloneHash(h hash) (hash, bool) { // ERROR "can inline" "leaking param: h" if h, ok := h.(hashWithClone); ok { return h.Clone(), true } - return nil, false + return &clonableHash{}, true } func devirtIfaceCallThatReturnsIface() { diff --git a/test/devirtualization_with_type_assertions_interleaved.go b/test/devirtualization_with_type_assertions_interleaved.go new file mode 100644 index 00000000000000..7e80adc6bf42ce --- /dev/null +++ b/test/devirtualization_with_type_assertions_interleaved.go @@ -0,0 +1,107 @@ +// errorcheck -0 -m -d=testing=2 + +// Copyright 2025 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. + +package escape + +type hashIface interface { + Sum() []byte +} + +type clonableHashIface interface { + Sum() []byte + Clone() hashIface +} + +type hash struct{ state [32]byte } + +func (h *hash) Sum() []byte { // ERROR "can inline" "h does not escape" + return make([]byte, 32) // ERROR "escapes" +} + +func (h *hash) Clone() hashIface { // ERROR "can inline" "h does not escape" + c := *h // ERROR "moved to heap: c" + return &c +} + +type hash2 struct{ state [32]byte } + +func (h *hash2) Sum() []byte { // ERROR "can inline" "h does not escape" + return make([]byte, 32) // ERROR "escapes" +} + +func (h *hash2) Clone() hashIface { // ERROR "can inline" "h does not escape" + c := *h // ERROR "moved to heap: c" + return &c +} + +func newHash() hashIface { // ERROR "can inline" + return &hash{} // ERROR "escapes" +} + +func cloneHash1(h hashIface) hashIface { // ERROR "can inline" "leaking param: h" + if h, ok := h.(clonableHashIface); ok { + return h.Clone() + } + return &hash{} // ERROR "escapes" +} + +func cloneHash2(h hashIface) hashIface { // ERROR "can inline" "leaking param: h" + if h, ok := h.(clonableHashIface); ok { + return h.Clone() + } + return nil +} + +func cloneHash3(h hashIface) hashIface { // ERROR "can inline" "leaking param: h" + if h, ok := h.(clonableHashIface); ok { + return h.Clone() + } + return &hash2{} // ERROR "escapes" +} + +func cloneHashWithBool1(h hashIface) (hashIface, bool) { // ERROR "can inline" "leaking param: h" + if h, ok := h.(clonableHashIface); ok { + return h.Clone(), true + } + return &hash{}, false // ERROR "escapes" +} + +func cloneHashWithBool2(h hashIface) (hashIface, bool) { // ERROR "can inline" "leaking param: h" + if h, ok := h.(clonableHashIface); ok { + return h.Clone(), true + } + return nil, false +} + +func cloneHashWithBool3(h hashIface) (hashIface, bool) { // ERROR "can inline" "leaking param: h" + if h, ok := h.(clonableHashIface); ok { + return h.Clone(), true + } + return &hash2{}, false // ERROR "escapes" +} + +func interleavedWithTypeAssertions() { + h1 := newHash() // ERROR "&hash{} does not escape" "inlining call" + _ = h1.Sum() // ERROR "devirtualizing h1.Sum to \*hash" "inlining call" "make\(\[\]byte, 32\) does not escape" + + h2 := cloneHash1(h1) // ERROR "inlining call to cloneHash1" "devirtualizing h.Clone to \*hash" "inlining call to \(\*hash\).Clone" "&hash{} does not escape" + _ = h2.Sum() // ERROR "devirtualizing h2.Sum to \*hash" "inlining call" "make\(\[\]byte, 32\) does not escape" + + h3 := cloneHash2(h1) // ERROR "inlining call to cloneHash2" "devirtualizing h.Clone to \*hash" "inlining call to \(\*hash\).Clone" + _ = h3.Sum() // ERROR "devirtualizing h3.Sum to \*hash" "inlining call" "make\(\[\]byte, 32\) does not escape" + + h4 := cloneHash3(h1) // ERROR "inlining call to cloneHash3" "devirtualizing h.Clone to \*hash" "inlining call to \(\*hash\).Clone" "moved to heap: c" "&hash2{} escapes to heap" + _ = h4.Sum() + + h5, _ := cloneHashWithBool1(h1) // ERROR "inlining call to cloneHashWithBool1" "devirtualizing h.Clone to \*hash" "inlining call to \(\*hash\).Clone" "&hash{} does not escape" + _ = h5.Sum() // ERROR "devirtualizing h5.Sum to \*hash" "inlining call" "make\(\[\]byte, 32\) does not escape" + + h6, _ := cloneHashWithBool2(h1) // ERROR "inlining call to cloneHashWithBool2" "devirtualizing h.Clone to \*hash" "inlining call to \(\*hash\).Clone" + _ = h6.Sum() // ERROR "devirtualizing h6.Sum to \*hash" "inlining call" "make\(\[\]byte, 32\) does not escape" + + h7, _ := cloneHashWithBool3(h1) // ERROR "inlining call to cloneHashWithBool3" "devirtualizing h.Clone to \*hash" "inlining call to \(\*hash\).Clone" "moved to heap: c" "&hash2{} escapes to heap" + _ = h7.Sum() +} From 9721d271b49732e0efcbd9545ce5b9fceb7aab46 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 25 Feb 2025 20:55:12 +0100 Subject: [PATCH 38/74] update Change-Id: Ic4d519de034cd743ce0f949a9ac393e36c9413d6 --- .../internal/devirtualize/devirtualize.go | 91 ++++++++++--------- test/devirtualization.go | 73 ++++----------- 2 files changed, 69 insertions(+), 95 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 8745643ae5c2d0..bcbdd561d01e65 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -22,7 +22,7 @@ import ( // TODO: update tests to include full errors. // const go125ImprovedConcreteTypeAnalysis = true -var go125ImprovedConcreteTypeAnalysis = false +var go125ImprovedConcreteTypeAnalysis = true var debug = false // StaticCall devirtualizes the given call if possible when the concrete callee @@ -170,7 +170,7 @@ func StaticCall(call *ir.CallExpr) { func concreteType(n ir.Node) (typ *types.Type) { var assignements map[*ir.Name][]valOrTyp var baseFunc *ir.Func - return concreteType1(n, make(map[*ir.Name]*types.Type), func(n *ir.Name) []valOrTyp { + typ, isNil := concreteType1(n, make(map[*ir.Name]*types.Type), func(n *ir.Name) []valOrTyp { if assignements == nil { assignements = make(map[*ir.Name][]valOrTyp) if n.Curfn == nil { @@ -187,9 +187,16 @@ func concreteType(n ir.Node) (typ *types.Type) { } return assignements[n] }) + if isNil && typ != nil { + base.Fatalf("typ = %v; want = ", typ) + } + if typ != nil && typ.IsInterface() { + base.Fatalf("typ.IsInterface() = true; want = false; typ = %v", typ) + } + return typ } -func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements func(*ir.Name) []valOrTyp) (out *types.Type) { +func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements func(*ir.Name) []valOrTyp) (out *types.Type, isNil bool) { nn := n if go125ImprovedConcreteTypeAnalysis { @@ -213,11 +220,11 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements retTyp := results[0].Type if !retTyp.IsInterface() { // TODO: isnt n.Type going to return that? - return retTyp + return retTyp, false } } } - return n.Type() + return n.Type(), false } switch n1 := n.(type) { @@ -250,12 +257,12 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements } if n.Op() != ir.ONAME { - return nil + return nil, false } name := n.(*ir.Name).Canonical() if name.Class != ir.PAUTO { - return nil + return nil, false } if name.Op() != ir.ONAME { @@ -263,7 +270,7 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements } if name.Addrtaken() { - return nil // conservatively assume it's reassigned with a different type indirectly + return nil, false // conservatively assume it's reassigned with a different type indirectly } if name.Curfn == nil { @@ -276,7 +283,7 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements } if typ, ok := analyzed[name]; ok { - return typ + return typ, false } // For now set the Type to nil, as we don't know it yet, we will update @@ -287,46 +294,39 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements assignements := getAssignements(name) if len(assignements) == 0 { - return nil // call on a nil interface + // TODO: here comment + // TODO in analyzed map we set this as nil (meaning unknown type). add tests. + return nil, true } var typ *types.Type - if v := assignements[0]; v.typ != nil { - if debug { - base.WarnfAt(src.NoXPos, "concreteType1(%v) name: %v, assigned with concrete type %v", nn, name, v.typ) - } - typ = v.typ - } else if v.node != nil { - if debug { - base.WarnfAt(v.node.Pos(), "concreteType1(%v) name: %v, assigned with %v", nn, name, v.node) - } - typ = concreteType1(v.node, analyzed, getAssignements) - } else { - // TODO: tu - return nil - } - - for _, v := range assignements[1:] { + for _, v := range assignements { t := v.typ - if t != nil && debug { - base.WarnfAt(src.NoXPos, "concreteType1(%v) name: %v, assigned with concrete type %v", nn, name, v.typ) - } if v.node != nil { - if debug { - base.WarnfAt(v.node.Pos(), "concreteType1(%v) name: %v, assigned with %v", nn, name, v.node) + var isNil bool + t, isNil = concreteType1(v.node, analyzed, getAssignements) + if isNil { + if t != nil { + base.Fatalf("t = %v; want = ", t) + } + // TODO if all are nil, then we return nil? Is that fine? add tests. + continue } - t = concreteType1(v.node, analyzed, getAssignements) } - if t == nil || !types.Identical(typ, t) { - return nil + if t == nil || (typ != nil && !types.Identical(typ, t)) { + return nil, false } + typ = t } analyzed[name] = typ - return typ + return typ, false } type valOrTyp struct { + // either typ or node is populated, neither both, or + // both are nil (either interface was assigned + // or a basic type without methods (i.e. int)) typ *types.Type node ir.Node } @@ -338,28 +338,34 @@ func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { if name == nil || name.Op() != ir.ONAME { return } + + // TODO: is this needed? n, ok := ir.OuterValue(name).(*ir.Name) if !ok { return } + n = n.Canonical() if n.Op() != ir.ONAME { base.Fatalf("reassigned %v", n) } + + // Do not track variables not declared directly in fun, and names that are not interfaces. + // For devirtualization they are unnecessary, we will not even look them up. if n.Curfn != fun || !n.Type().IsInterface() { return } + // n is assigned with nil, we can safely ignore them, see [StaticCall]. + if ir.IsNil(value.node) { + return + } + // TODO: add test case that fails w/o this. if value.typ != nil && value.typ.IsInterface() { value.typ = nil } - // TODO: explain - if ir.IsNil(value.node) { - return - } - if debug { base.WarnfAt(src.NoXPos, "assign %v = {%v;%v}", n, value.typ, value.node) } @@ -388,14 +394,14 @@ func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { base.Fatalf("n.Rhs[0] == nil; n = %v", n) } assign(n.Lhs[0], valOrTyp{node: n.Rhs[0]}) - assign(n.Lhs[1], valOrTyp{}) // TODO: boolean + assign(n.Lhs[1], valOrTyp{}) // boolean does not have methods to devirtualize case ir.OAS2MAPR, ir.OAS2RECV, ir.OSELRECV2: n := n.(*ir.AssignListStmt) if n.Rhs[0] == nil { base.Fatalf("n.Rhs[0] == nil; n = %v", n) } assign(n.Lhs[0], valOrTyp{typ: n.Rhs[0].Type()}) - assign(n.Lhs[1], valOrTyp{}) // TODO: boolean + assign(n.Lhs[1], valOrTyp{}) // boolean does not have methods to devirtualize case ir.OAS2FUNC: n := n.(*ir.AssignListStmt) for i, p := range n.Lhs { @@ -413,6 +419,7 @@ func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { } else if call, ok := rhs.(*ir.InlinedCallExpr); ok { assign(p, valOrTyp{node: call.Result(i)}) } else { + // TODO: can we reach here? Fatal? assign(p, valOrTyp{}) } } diff --git a/test/devirtualization.go b/test/devirtualization.go index 51cd922aa76b69..f8562805c4659f 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -299,26 +299,27 @@ func assignWithTypeAssert() { func nilIface() { { - var v A = &Impl{} // ERROR "escapes" + var v A = &Impl{} // ERROR "&Impl{} does not escape" v = nil - v.A() + v.A() // ERROR "devirtualizing v\.A to \*Impl" "inlining call" } { - var v A = &Impl{} // ERROR "escapes" - v.A() + var v A = &Impl{} // ERROR "&Impl{} does not escape" + v.A() // ERROR "devirtualizing v\.A to \*Impl" "inlining call" v = nil } + { var nilIface A - var v A = &Impl{} // ERROR "escapes" - v.A() + var v A = &Impl{} // ERROR "&Impl{} does not escape" + v.A() // ERROR "devirtualizing v\.A to \*Impl" "inlining call" v = nilIface } { var nilIface A - var v A = &Impl{} // ERROR "escapes" + var v A = &Impl{} // ERROR "&Impl{} does not escape" v = nilIface - v.A() + v.A() // ERROR "devirtualizing v\.A to \*Impl" "inlining call" } { var v A @@ -329,6 +330,17 @@ func nilIface() { var v A v.A() } + { + var v A + var v2 A = v + v2.A() + } + { + var v A + var v2 A + v2 = v + v2.A() + } } func longDevirtTest() { @@ -1125,48 +1137,3 @@ func testInvalidAsserts() { a.(any).(M).(*Impl).M() // ERROR "inlining" } } - -type hash interface { - Sum() []byte -} - -type hashWithClone interface { - Sum() []byte - Clone() hash -} - -type clonableHash struct{ state [32]byte } - -func (h *clonableHash) Sum() []byte { // ERROR "can inline" "h does not escape" - return make([]byte, 32) // ERROR "escapes" -} - -func (h *clonableHash) Clone() hash { // ERROR "can inline" "h does not escape" - c := *h // ERROR "moved to heap: c" - return &c -} - -func newHash() hash { // ERROR "can inline" - return &clonableHash{} // ERROR "escapes" -} - -func cloneHash(h hash) (hash, bool) { // ERROR "can inline" "leaking param: h" - if h, ok := h.(hashWithClone); ok { - return h.Clone(), true - } - return &clonableHash{}, true -} - -func devirtIfaceCallThatReturnsIface() { - h := newHash() // ERROR "&clonableHash{} does not escape" "inlining call" - _ = h.Sum() // ERROR "devirtualizing h.Sum to \*clonableHash" "inlining call" "make\(\[\]byte, 32\) does not escape" - - // TODO: make this non-escaping: - h2, ok := cloneHash(h) // ERROR "inlining call to cloneHash" "devirtualizing h.Clone to \*clonableHash" "inlining call to \(\*clonableHash\).Clone" "moved to heap: c" - if !ok { - panic("unexpected") // ERROR "escapes" - } - - // TODO: make this devirtualize: - _ = h2.Sum() -} From 3b4a3dca366e7dfb7aaba05134209f11038caa40 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Wed, 26 Feb 2025 09:15:13 +0100 Subject: [PATCH 39/74] update --- .../internal/devirtualize/devirtualize.go | 64 ++++++++++++++----- test/devirtualization.go | 7 +- 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index bcbdd561d01e65..5ff0b8c9fcac3e 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -23,12 +23,12 @@ import ( // const go125ImprovedConcreteTypeAnalysis = true var go125ImprovedConcreteTypeAnalysis = true -var debug = false // StaticCall devirtualizes the given call if possible when the concrete callee // is available statically. func StaticCall(call *ir.CallExpr) { - go125ImprovedConcreteTypeAnalysis = base.Debug.Testing != 0 + //go125ImprovedConcreteTypeAnalysis = base.Debug.Testing != 0 + //concreteTypeDebug = base.Debug.Testing != 0 // For promoted methods (including value-receiver methods promoted // to pointer-receivers), the interface method wrapper may contain @@ -125,6 +125,23 @@ func StaticCall(call *ir.CallExpr) { } dt := ir.NewTypeAssertExpr(sel.Pos(), sel.X, typ) + + if go125ImprovedConcreteTypeAnalysis { + // Consider: + // + // var v Iface + // v.A() + // v = &Impl{} + // + // Here in the devirtualizer, we determine the concrete type of v as beeing an *Impl, + // but in can still be a nil interface, we have not detected that. The v.(*Impl) + // type assertion that we make here would also have failed, but with a different + // panic "pkg.Iface is nil, not *pkg.Impl", where previously we would get a nil panic. + // We fix this, by introducing an additional nilcheck on the itab. + dt.EmitItabNilCheck = true + dt.SetPos(call.Pos()) + } + x := typecheck.XDotMethod(sel.Pos(), dt, sel.Sel, true) switch x.Op() { case ir.ODOTMETH: @@ -164,6 +181,8 @@ func StaticCall(call *ir.CallExpr) { typecheck.FixMethodCall(call) } +var concreteTypeDebug = false + // concreteType determines the concrete type of n, following OCONVIFACEs and type asserts. // Returns nil when the concrete type could not be determined, or when there are multiple // (different) types assigned to an interface. @@ -180,10 +199,10 @@ func concreteType(n ir.Node) (typ *types.Type) { baseFunc = n.Curfn } if !n.Type().IsInterface() { - base.Fatalf("node passed to getAssignements is not an interface: %v", n.Type()) + base.Fatalf("name passed to getAssignements is not of an interface type: %v", n.Type()) } if n.Curfn != baseFunc { - base.Fatalf("unexpected n.Curfn = %v; want = %v", n, baseFunc) + base.Fatalf("unexpected n.Curfn = %v; want = %v", n.Curfn, baseFunc) } return assignements[n] }) @@ -201,14 +220,14 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements if go125ImprovedConcreteTypeAnalysis { defer func() { - if debug { - base.WarnfAt(nn.Pos(), "concreteType1(%v) -> %v", nn, out) + if concreteTypeDebug { + base.WarnfAt(nn.Pos(), "concreteType1(%v) -> (typ: %v; isNil: %v)", nn, out, isNil) } }() } for { - if debug { + if concreteTypeDebug { base.WarnfAt(n.Pos(), "concreteType1(%v) current iteration node: %v", nn, n) } @@ -278,7 +297,7 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements base.Fatalf("nil Func %v", name) } - if debug { + if concreteTypeDebug { base.WarnfAt(src.NoXPos, "concreteType1(%v) name: %v, analyzing assignements", nn, name) } @@ -294,8 +313,11 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements assignements := getAssignements(name) if len(assignements) == 0 { - // TODO: here comment - // TODO in analyzed map we set this as nil (meaning unknown type). add tests. + // Variable either declared with zero value, or only assigned + // with nil (getAssignements does not return such assignements). + if concreteTypeDebug { + base.WarnfAt(src.NoXPos, "concreteType1(%v) name: %v assigned with only nil values", nn, name) + } return nil, true } @@ -304,26 +326,40 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements t := v.typ if v.node != nil { var isNil bool + if concreteTypeDebug { + base.WarnfAt(src.NoXPos, "concreteType1(%v) name: %v assigned with node %v", nn, name, v.node) + } t, isNil = concreteType1(v.node, analyzed, getAssignements) if isNil { + if concreteTypeDebug { + base.WarnfAt(src.NoXPos, "concreteType1(%v) name: %v assigned with nil", nn, name) + } if t != nil { base.Fatalf("t = %v; want = ", t) } - // TODO if all are nil, then we return nil? Is that fine? add tests. continue } } + if concreteTypeDebug { + base.WarnfAt(src.NoXPos, "concreteType1(%v) name: %v assigned with %v", nn, name, t) + } if t == nil || (typ != nil && !types.Identical(typ, t)) { return nil, false } typ = t } + if typ == nil { + // Variable either declared with zero value, or only assigned with nil. + return nil, true + } + analyzed[name] = typ return typ, false } type valOrTyp struct { + // TODO: improve comment // either typ or node is populated, neither both, or // both are nil (either interface was assigned // or a basic type without methods (i.e. int)) @@ -331,6 +367,8 @@ type valOrTyp struct { node ir.Node } +// ifaceAssignments returns a map containg every assignement to variables +// directly declared in the provieded func that are of interface types. func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { out := make(map[*ir.Name][]valOrTyp) @@ -366,10 +404,6 @@ func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { value.typ = nil } - if debug { - base.WarnfAt(src.NoXPos, "assign %v = {%v;%v}", n, value.typ, value.node) - } - out[n] = append(out[n], value) } diff --git a/test/devirtualization.go b/test/devirtualization.go index f8562805c4659f..39ed6e2c89117e 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -308,7 +308,6 @@ func nilIface() { v.A() // ERROR "devirtualizing v\.A to \*Impl" "inlining call" v = nil } - { var nilIface A var v A = &Impl{} // ERROR "&Impl{} does not escape" @@ -326,6 +325,12 @@ func nilIface() { v.A() // ERROR "devirtualizing v\.A to \*Impl" "inlining call" v = &Impl{} // ERROR "does not escape" } + { + var v A + var v2 A = v + v2.A() // ERROR "devirtualizing v2\.A to \*Impl" "inlining call" + v2 = &Impl{} // ERROR "does not escape" + } { var v A v.A() From 1ad69499cb813e831ea23d15c9c2f8efc1dde3e2 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Wed, 26 Feb 2025 11:07:41 +0100 Subject: [PATCH 40/74] update --- .../internal/devirtualize/devirtualize.go | 24 +++++++------ test/devirtualization.go | 34 +++++++++++++++++++ test/fixedbugs/issue42284.dir/a.go | 7 ++-- test/fixedbugs/issue42284.dir/b.go | 7 ++-- 4 files changed, 53 insertions(+), 19 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 5ff0b8c9fcac3e..7266bcc859b658 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -138,6 +138,7 @@ func StaticCall(call *ir.CallExpr) { // type assertion that we make here would also have failed, but with a different // panic "pkg.Iface is nil, not *pkg.Impl", where previously we would get a nil panic. // We fix this, by introducing an additional nilcheck on the itab. + // Calling a method on an nil interface in most cases is a bug. dt.EmitItabNilCheck = true dt.SetPos(call.Pos()) } @@ -188,22 +189,21 @@ var concreteTypeDebug = false // (different) types assigned to an interface. func concreteType(n ir.Node) (typ *types.Type) { var assignements map[*ir.Name][]valOrTyp - var baseFunc *ir.Func typ, isNil := concreteType1(n, make(map[*ir.Name]*types.Type), func(n *ir.Name) []valOrTyp { if assignements == nil { assignements = make(map[*ir.Name][]valOrTyp) if n.Curfn == nil { base.Fatalf("n.Curfn == nil: %v", n) } - assignements = ifaceAssignments(n.Curfn) - baseFunc = n.Curfn + fun := n.Curfn + for fun.ClosureParent != nil { + fun = fun.ClosureParent + } + assignements = ifaceAssignments(fun) } if !n.Type().IsInterface() { base.Fatalf("name passed to getAssignements is not of an interface type: %v", n.Type()) } - if n.Curfn != baseFunc { - base.Fatalf("unexpected n.Curfn = %v; want = %v", n.Curfn, baseFunc) - } return assignements[n] }) if isNil && typ != nil { @@ -351,6 +351,8 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements if typ == nil { // Variable either declared with zero value, or only assigned with nil. + // For now don't bother storing the information that we could have + // assigned nil in the analyzed map. return nil, true } @@ -368,7 +370,7 @@ type valOrTyp struct { } // ifaceAssignments returns a map containg every assignement to variables -// directly declared in the provieded func that are of interface types. +// declared in the provieded func (and in closures) that are of interface types. func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { out := make(map[*ir.Name][]valOrTyp) @@ -388,9 +390,9 @@ func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { base.Fatalf("reassigned %v", n) } - // Do not track variables not declared directly in fun, and names that are not interfaces. + // Do not track variables that are not of interface types. // For devirtualization they are unnecessary, we will not even look them up. - if n.Curfn != fun || !n.Type().IsInterface() { + if !n.Type().IsInterface() { return } @@ -461,7 +463,7 @@ func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { n := n.(*ir.RangeStmt) xTyp := n.X.Type() - // range over an array pointer + // Range over an array pointer. if xTyp.IsPtr() && xTyp.Elem().IsArray() { xTyp = xTyp.Elem() } @@ -476,7 +478,7 @@ func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { assign(n.Key, valOrTyp{typ: xTyp.Key()}) assign(n.Value, valOrTyp{typ: xTyp.Elem()}) } else if xTyp.IsInteger() || xTyp.IsString() { - // range over int/string, results do not have methods, so nothing to devirtualize. + // Range over int/string, results do not have methods, so nothing to devirtualize. assign(n.Key, valOrTyp{}) assign(n.Value, valOrTyp{}) } else { diff --git a/test/devirtualization.go b/test/devirtualization.go index 39ed6e2c89117e..11a6641790e8df 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -480,6 +480,40 @@ func closureNoDevirt2() { c() } +//go:noinline +func varDeclaredInClosureReferencesOuter() { + var a A = &Impl{} // ERROR "&Impl{} does not escape" + func() { // ERROR "func literal does not escape" + // defer for noinline + defer func() {}() // ERROR "can inline" "func literal does not escape" + var v A = a + v.A() // ERROR "devirtualizing v.A to \*Impl" "inlining call to \(\*Impl\).A" + }() + func() { // ERROR "func literal does not escape" + // defer for noinline + defer func() {}() // ERROR "can inline" "func literal does not escape" + var v A = a + v = &Impl{} // ERROR "&Impl{} does not escape" + v.A() // ERROR "devirtualizing v.A to \*Impl" "inlining call to \(\*Impl\).A" + }() + + var b A = &Impl{} // ERROR "&Impl{} escapes to heap" + func() { // ERROR "func literal does not escape" + // defer for noinline + defer func() {}() // ERROR "can inline" "func literal does not escape" + var v A = b + v = &Impl2{} // ERROR "&Impl2{} escapes to heap" + v.A() + }() + func() { // ERROR "func literal does not escape" + // defer for noinline + defer func() {}() // ERROR "can inline" "func literal does not escape" + var v A = b + v.A() + v = &Impl2{} // ERROR "&Impl2{} escapes to heap" + }() +} + //go:noinline func testNamedReturn0() (v A) { v = &Impl{} // ERROR "escapes" diff --git a/test/fixedbugs/issue42284.dir/a.go b/test/fixedbugs/issue42284.dir/a.go index ccf54fad54a03c..e55f190d7ee571 100644 --- a/test/fixedbugs/issue42284.dir/a.go +++ b/test/fixedbugs/issue42284.dir/a.go @@ -22,9 +22,8 @@ func g() { h := E() // ERROR "inlining call to E" "T\(0\) does not escape" h.M() // ERROR "devirtualizing h.M to T" "inlining call to T.M" - // BAD: T(0) could be stack allocated. - i := F(T(0)) // ERROR "inlining call to F" "T\(0\) escapes to heap" + i := F(T(0)) // ERROR "inlining call to F" "T\(0\) does not escape" - // Testing that we do NOT devirtualize here: - i.M() + // It is fine that we devirtualize here, as we add an additional nilcheck. + i.M() // ERROR "devirtualizing i.M to T" "inlining call to T.M" } diff --git a/test/fixedbugs/issue42284.dir/b.go b/test/fixedbugs/issue42284.dir/b.go index 559de59184460a..4a0b7cea102e88 100644 --- a/test/fixedbugs/issue42284.dir/b.go +++ b/test/fixedbugs/issue42284.dir/b.go @@ -10,9 +10,8 @@ func g() { h := a.E() // ERROR "inlining call to a.E" "T\(0\) does not escape" h.M() // ERROR "devirtualizing h.M to a.T" "inlining call to a.T.M" - // BAD: T(0) could be stack allocated. - i := a.F(a.T(0)) // ERROR "inlining call to a.F" "a.T\(0\) escapes to heap" + i := a.F(a.T(0)) // ERROR "inlining call to a.F" "a.T\(0\) does not escape" - // Testing that we do NOT devirtualize here: - i.M() + // It is fine that we devirtualize here, as we add an additional nilcheck. + i.M() // ERROR "devirtualizing i.M to a.T" "inlining call to a.T.M" } From 4a63574ff8acac899e3966a694511bcec33e3182 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Wed, 26 Feb 2025 11:30:26 +0100 Subject: [PATCH 41/74] update comments --- .../compile/internal/devirtualize/devirtualize.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 7266bcc859b658..5beeed9a5ac94e 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -138,7 +138,9 @@ func StaticCall(call *ir.CallExpr) { // type assertion that we make here would also have failed, but with a different // panic "pkg.Iface is nil, not *pkg.Impl", where previously we would get a nil panic. // We fix this, by introducing an additional nilcheck on the itab. - // Calling a method on an nil interface in most cases is a bug. + // Calling a method on an nil interface (in most cases) is a bug in a program, so it is fine + // to devirtualize and further (possibly) inline them, even though we would never reach + // the called function. dt.EmitItabNilCheck = true dt.SetPos(call.Pos()) } @@ -360,11 +362,11 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements return typ, false } +// valOrTyp stores a node or a type that is assigned to a variable. +// Neither both of these fields are populated. If both are nil, then +// either an interface type was assigned or a basic type (i.e. int), which +// we know that does not have any methods, thus not possible to devirtualize. type valOrTyp struct { - // TODO: improve comment - // either typ or node is populated, neither both, or - // both are nil (either interface was assigned - // or a basic type without methods (i.e. int)) typ *types.Type node ir.Node } From 03fb72e57cbe18cfc6fa4bbac9e2ac4927167d57 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Wed, 26 Feb 2025 11:37:30 +0100 Subject: [PATCH 42/74] update --- src/cmd/compile/internal/devirtualize/devirtualize.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 5beeed9a5ac94e..4c415d4c7f9611 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -363,7 +363,7 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements } // valOrTyp stores a node or a type that is assigned to a variable. -// Neither both of these fields are populated. If both are nil, then +// Never both of these fields are populated. If both are nil, then // either an interface type was assigned or a basic type (i.e. int), which // we know that does not have any methods, thus not possible to devirtualize. type valOrTyp struct { From 7bfac15aa9ed846cbbd01b87f1f2b315c6693465 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Wed, 26 Feb 2025 12:04:24 +0100 Subject: [PATCH 43/74] update --- .../internal/devirtualize/devirtualize.go | 3 ++- test/devirtualization.go | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 4c415d4c7f9611..4f109a4d318f70 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -354,7 +354,8 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements if typ == nil { // Variable either declared with zero value, or only assigned with nil. // For now don't bother storing the information that we could have - // assigned nil in the analyzed map. + // assigned nil in the analyzed map, if we access the same name again we will + // get an result as if an unknown concrete type was assigned. return nil, true } diff --git a/test/devirtualization.go b/test/devirtualization.go index 11a6641790e8df..e26ef20360a254 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -545,6 +545,22 @@ var ( ) func globals() { + { + // TODO: why no panic? + // .CurFunc is nil + globalA.A() + globalA.(M).M() + globalM.M() + globalM.(A).A() + + var a = globalA + a.A() + a.(M).M() + + var m = globalM + m.M() + m.(A).A() + } { var a A = &Impl{} // ERROR "does not escape" a = globalImpl From f216b542b2af34fea2ab7e1bf0ac0b4494e2a5c1 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Wed, 26 Feb 2025 12:36:14 +0100 Subject: [PATCH 44/74] update --- .../internal/devirtualize/devirtualize.go | 3 +-- test/devirtualization.go | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 4f109a4d318f70..8ae0aaedcd66d8 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -27,7 +27,7 @@ var go125ImprovedConcreteTypeAnalysis = true // StaticCall devirtualizes the given call if possible when the concrete callee // is available statically. func StaticCall(call *ir.CallExpr) { - //go125ImprovedConcreteTypeAnalysis = base.Debug.Testing != 0 + go125ImprovedConcreteTypeAnalysis = base.Debug.Testing != 0 //concreteTypeDebug = base.Debug.Testing != 0 // For promoted methods (including value-receiver methods promoted @@ -404,7 +404,6 @@ func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { return } - // TODO: add test case that fails w/o this. if value.typ != nil && value.typ.IsInterface() { value.typ = nil } diff --git a/test/devirtualization.go b/test/devirtualization.go index e26ef20360a254..f4206ed3b76112 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -145,6 +145,14 @@ func typeAssertsWithOkReturn() { // a now contains the zero value of *Impl a.A() // ERROR "devirtualizing" "inlining call" } + { + a := newANoInline() + a.A() + } + { + _, a := newANoInlineRet2() + a.A() + } } func newM() M { // ERROR "can inline" @@ -176,6 +184,16 @@ func newImpl2() *Impl2 { return &Impl2{} // ERROR "escapes" } +//go:noinline +func newANoInline() A { + return &Impl{} // ERROR "escapes" +} + +//go:noinline +func newANoInlineRet2() (string, A) { + return "", &Impl{} // ERROR "escapes" +} + func testTypeSwitch() { { var v A = &Impl{} // ERROR "does not escape" From e4d294dca54b3e97441753f5078c26ebf2394da3 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Wed, 26 Feb 2025 12:38:43 +0100 Subject: [PATCH 45/74] update --- test/devirtualization.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/devirtualization.go b/test/devirtualization.go index f4206ed3b76112..59cc6b944730b3 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -571,11 +571,11 @@ func globals() { globalM.M() globalM.(A).A() - var a = globalA + a := globalA a.A() a.(M).M() - var m = globalM + m := globalM m.M() m.(A).A() } From 4d6ab6e92f4ac9b3288764140397a0e44b91abf5 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Wed, 26 Feb 2025 14:22:32 +0100 Subject: [PATCH 46/74] update --- .../internal/devirtualize/devirtualize.go | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 8ae0aaedcd66d8..c3d39e0e8b3794 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -21,15 +21,11 @@ import ( // TODO: update tests to include full errors. -// const go125ImprovedConcreteTypeAnalysis = true -var go125ImprovedConcreteTypeAnalysis = true +const go125ImprovedConcreteTypeAnalysis = true // StaticCall devirtualizes the given call if possible when the concrete callee // is available statically. func StaticCall(call *ir.CallExpr) { - go125ImprovedConcreteTypeAnalysis = base.Debug.Testing != 0 - //concreteTypeDebug = base.Debug.Testing != 0 - // For promoted methods (including value-receiver methods promoted // to pointer-receivers), the interface method wrapper may contain // expressions that can panic (e.g., ODEREF, ODOTPTR, @@ -184,7 +180,7 @@ func StaticCall(call *ir.CallExpr) { typecheck.FixMethodCall(call) } -var concreteTypeDebug = false +const concreteTypeDebug = false // concreteType determines the concrete type of n, following OCONVIFACEs and type asserts. // Returns nil when the concrete type could not be determined, or when there are multiple @@ -220,11 +216,9 @@ func concreteType(n ir.Node) (typ *types.Type) { func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements func(*ir.Name) []valOrTyp) (out *types.Type, isNil bool) { nn := n - if go125ImprovedConcreteTypeAnalysis { + if concreteTypeDebug { defer func() { - if concreteTypeDebug { - base.WarnfAt(nn.Pos(), "concreteType1(%v) -> (typ: %v; isNil: %v)", nn, out, isNil) - } + base.WarnfAt(nn.Pos(), "concreteType1(%v) -> (typ: %v; isNil: %v)", nn, out, isNil) }() } @@ -262,7 +256,6 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements } case *ir.InlinedCallExpr: if n1.Op() == ir.OINLCALL { - // TODO: single is fine? n = n1.SingleResult() continue } @@ -290,13 +283,13 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements base.Fatalf("reassigned %v", name) } - if name.Addrtaken() { - return nil, false // conservatively assume it's reassigned with a different type indirectly + // name.Curfn must be set, as we checked name.Class != ir.PAUTO before. + if name.Curfn == nil { + base.Fatalf("name.Curfn = nil; want not nil") } - if name.Curfn == nil { - // TODO: think, we should hit here with an iface global? - base.Fatalf("nil Func %v", name) + if name.Addrtaken() { + return nil, false // conservatively assume it's reassigned with a different type indirectly } if concreteTypeDebug { From 07b4fdcb2a65c6969f400d032533584c390122a5 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Wed, 26 Feb 2025 18:20:31 +0100 Subject: [PATCH 47/74] update Change-Id: Ifad568a8bf9b9da7aadcd2efea27e979740ca428 --- .../internal/devirtualize/devirtualize.go | 57 +++---------------- test/devirtualization.go | 9 ++- 2 files changed, 14 insertions(+), 52 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index c3d39e0e8b3794..159a222ef173dd 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -16,11 +16,8 @@ import ( "cmd/compile/internal/ir" "cmd/compile/internal/typecheck" "cmd/compile/internal/types" - "cmd/internal/src" ) -// TODO: update tests to include full errors. - const go125ImprovedConcreteTypeAnalysis = true // StaticCall devirtualizes the given call if possible when the concrete callee @@ -154,7 +151,6 @@ func StaticCall(call *ir.CallExpr) { if base.Flag.LowerM != 0 { base.WarnfAt(call.Pos(), "partially devirtualizing %v to %v", sel, typ) } - // TODO: with interleaved inlining and devirtualization we migth get here multiple times for the same call. call.SetOp(ir.OCALLINTER) call.Fun = x default: @@ -180,8 +176,6 @@ func StaticCall(call *ir.CallExpr) { typecheck.FixMethodCall(call) } -const concreteTypeDebug = false - // concreteType determines the concrete type of n, following OCONVIFACEs and type asserts. // Returns nil when the concrete type could not be determined, or when there are multiple // (different) types assigned to an interface. @@ -214,39 +208,19 @@ func concreteType(n ir.Node) (typ *types.Type) { } func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements func(*ir.Name) []valOrTyp) (out *types.Type, isNil bool) { - nn := n - - if concreteTypeDebug { - defer func() { - base.WarnfAt(nn.Pos(), "concreteType1(%v) -> (typ: %v; isNil: %v)", nn, out, isNil) - }() - } - for { - if concreteTypeDebug { - base.WarnfAt(n.Pos(), "concreteType1(%v) current iteration node: %v", nn, n) - } - if !n.Type().IsInterface() { - if n, ok := n.(*ir.CallExpr); ok { - if n.Fun != nil { - results := n.Fun.Type().Results() - base.Assert(len(results) == 1) - retTyp := results[0].Type - if !retTyp.IsInterface() { - // TODO: isnt n.Type going to return that? - return retTyp, false - } - } - } return n.Type(), false } switch n1 := n.(type) { case *ir.ConvExpr: - // OCONVNOP might change the type, thus check whether they are identical. - // TODO: can this happen for iface? - if n1.Op() == ir.OCONVNOP && types.Identical(n1.Type(), n1.X.Type()) { + if n1.Op() == ir.OCONVNOP { + if !n1.Type().IsInterface() || !types.Identical(n1.Type(), n1.X.Type()) { + // As we check (directly before this switch) wheter n is an interface, thus we should only reach + // here for iface conversions where both operands are the same. + base.Fatalf("not identical/interface types found n1.Type = %v; n1.X.Type = %v", n1.Type(), n1.X.Type()) + } n = n1.X continue } @@ -292,10 +266,6 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements return nil, false // conservatively assume it's reassigned with a different type indirectly } - if concreteTypeDebug { - base.WarnfAt(src.NoXPos, "concreteType1(%v) name: %v, analyzing assignements", nn, name) - } - if typ, ok := analyzed[name]; ok { return typ, false } @@ -310,9 +280,6 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements if len(assignements) == 0 { // Variable either declared with zero value, or only assigned // with nil (getAssignements does not return such assignements). - if concreteTypeDebug { - base.WarnfAt(src.NoXPos, "concreteType1(%v) name: %v assigned with only nil values", nn, name) - } return nil, true } @@ -321,23 +288,14 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements t := v.typ if v.node != nil { var isNil bool - if concreteTypeDebug { - base.WarnfAt(src.NoXPos, "concreteType1(%v) name: %v assigned with node %v", nn, name, v.node) - } t, isNil = concreteType1(v.node, analyzed, getAssignements) if isNil { - if concreteTypeDebug { - base.WarnfAt(src.NoXPos, "concreteType1(%v) name: %v assigned with nil", nn, name) - } if t != nil { base.Fatalf("t = %v; want = ", t) } continue } } - if concreteTypeDebug { - base.WarnfAt(src.NoXPos, "concreteType1(%v) name: %v assigned with %v", nn, name, t) - } if t == nil || (typ != nil && !types.Identical(typ, t)) { return nil, false } @@ -375,7 +333,6 @@ func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { return } - // TODO: is this needed? n, ok := ir.OuterValue(name).(*ir.Name) if !ok { return @@ -450,7 +407,7 @@ func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { } else if call, ok := rhs.(*ir.InlinedCallExpr); ok { assign(p, valOrTyp{node: call.Result(i)}) } else { - // TODO: can we reach here? Fatal? + // TODO: can we reach here? assign(p, valOrTyp{}) } } diff --git a/test/devirtualization.go b/test/devirtualization.go index 59cc6b944730b3..063a7b7d144638 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -62,6 +62,13 @@ func typeAsserts() { c.(A).A() // ERROR "devirtualizing" "inlining call" c.(M).M() // ERROR "devirtualizing" "inlining call" + M(a).M() // ERROR "devirtualizing" "inlining call" + M(M(a)).M() // ERROR "devirtualizing" "inlining call" + + a2 := a.(A) + A(a2).A() // ERROR "devirtualizing" "inlining call" + A(A(a2)).A() // ERROR "devirtualizing" "inlining call" + { var a C = &CImpl{} // ERROR "does not escape" a.(any).(C).C() // ERROR "devirtualizing" "inlining" @@ -564,8 +571,6 @@ var ( func globals() { { - // TODO: why no panic? - // .CurFunc is nil globalA.A() globalA.(M).M() globalM.M() From d30e6cd8fb57e96b2c2ebb96fbcbacfa6bc487eb Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Wed, 26 Feb 2025 18:23:10 +0100 Subject: [PATCH 48/74] typos Change-Id: Ifb8d5e9711fe9820b382c6cb4271bb89b11997ba --- .../internal/devirtualize/devirtualize.go | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 159a222ef173dd..5f042a6013e47e 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -180,10 +180,10 @@ func StaticCall(call *ir.CallExpr) { // Returns nil when the concrete type could not be determined, or when there are multiple // (different) types assigned to an interface. func concreteType(n ir.Node) (typ *types.Type) { - var assignements map[*ir.Name][]valOrTyp + var assignments map[*ir.Name][]valOrTyp typ, isNil := concreteType1(n, make(map[*ir.Name]*types.Type), func(n *ir.Name) []valOrTyp { - if assignements == nil { - assignements = make(map[*ir.Name][]valOrTyp) + if assignments == nil { + assignments = make(map[*ir.Name][]valOrTyp) if n.Curfn == nil { base.Fatalf("n.Curfn == nil: %v", n) } @@ -191,12 +191,12 @@ func concreteType(n ir.Node) (typ *types.Type) { for fun.ClosureParent != nil { fun = fun.ClosureParent } - assignements = ifaceAssignments(fun) + assignments = ifaceAssignments(fun) } if !n.Type().IsInterface() { - base.Fatalf("name passed to getAssignements is not of an interface type: %v", n.Type()) + base.Fatalf("name passed to getAssignments is not of an interface type: %v", n.Type()) } - return assignements[n] + return assignments[n] }) if isNil && typ != nil { base.Fatalf("typ = %v; want = ", typ) @@ -207,7 +207,7 @@ func concreteType(n ir.Node) (typ *types.Type) { return typ } -func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements func(*ir.Name) []valOrTyp) (out *types.Type, isNil bool) { +func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignments func(*ir.Name) []valOrTyp) (out *types.Type, isNil bool) { for { if !n.Type().IsInterface() { return n.Type(), false @@ -276,19 +276,19 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignements // executes) will get a nil (from the map lookup above), where we could determine the type. analyzed[name] = nil - assignements := getAssignements(name) - if len(assignements) == 0 { + assignments := getAssignments(name) + if len(assignments) == 0 { // Variable either declared with zero value, or only assigned - // with nil (getAssignements does not return such assignements). + // with nil (getAssignements does not return such assignments). return nil, true } var typ *types.Type - for _, v := range assignements { + for _, v := range assignments { t := v.typ if v.node != nil { var isNil bool - t, isNil = concreteType1(v.node, analyzed, getAssignements) + t, isNil = concreteType1(v.node, analyzed, getAssignments) if isNil { if t != nil { base.Fatalf("t = %v; want = ", t) From f08082daf89b5c9d456ebeb2db8084d1d9e7e0d1 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Wed, 26 Feb 2025 19:09:43 +0100 Subject: [PATCH 49/74] update Change-Id: I2bc464114e862a2a2d65f41a483bdeb12818a49e --- src/cmd/compile/internal/ir/expr.go | 4 +- src/cmd/compile/internal/ssagen/ssa.go | 3 + test/devirtualization.go | 680 +++++++++--------- ...zation_with_type_assertions_interleaved.go | 64 +- 4 files changed, 378 insertions(+), 373 deletions(-) diff --git a/src/cmd/compile/internal/ir/expr.go b/src/cmd/compile/internal/ir/expr.go index 5d088651cdd452..3bd7c49a888204 100644 --- a/src/cmd/compile/internal/ir/expr.go +++ b/src/cmd/compile/internal/ir/expr.go @@ -682,7 +682,9 @@ type TypeAssertExpr struct { // An internal/abi.TypeAssert descriptor to pass to the runtime. Descriptor *obj.LSym - // Emit a nilcheck on the Itab of X. + // When set to true, then the ssagen package will emit a nilcheck on the itab, that + // will lead to a nil check panic in case the itab is nil at runtime. + // It must not be set for type asserts using the commaok form. EmitItabNilCheck bool } diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index 36d0e9efb2ea29..16688b574ade73 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -5644,6 +5644,9 @@ func (s *state) dottype(n *ir.TypeAssertExpr, commaok bool) (res, resok *ssa.Val } if n.EmitItabNilCheck { + if commaok { + base.Fatalf("unexpected *ir.TypeAssertExpr with EmitItabNilCheck == true && commaok == true") + } typs := s.f.Config.Types iface = s.newValue2( ssa.OpIMake, diff --git a/test/devirtualization.go b/test/devirtualization.go index 063a7b7d144638..900b4cc305aa33 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -14,143 +14,143 @@ type C interface{ C() } type Impl struct{} -func (*Impl) M() {} // ERROR "can inline" +func (*Impl) M() {} // ERROR "can inline \(\*Impl\).M$" -func (*Impl) A() {} // ERROR "can inline" +func (*Impl) A() {} // ERROR "can inline \(\*Impl\).A$" type Impl2 struct{} -func (*Impl2) M() {} // ERROR "can inline" +func (*Impl2) M() {} // ERROR "can inline \(\*Impl2\).M$" -func (*Impl2) A() {} // ERROR "can inline" +func (*Impl2) A() {} // ERROR "can inline \(\*Impl2\).A$" type CImpl struct{} -func (CImpl) C() {} // ERROR "can inline" +func (CImpl) C() {} // ERROR "can inline CImpl.C$" func typeAsserts() { - var a M = &Impl{} // ERROR "&Impl{} does not escape" + var a M = &Impl{} // ERROR "&Impl{} does not escape$" - a.(M).M() // ERROR "devirtualizing a.\(M\).M" "inlining call" - a.(A).A() // ERROR "devirtualizing a.\(A\).A" "inlining call" - a.(*Impl).M() // ERROR "inlining call" - a.(*Impl).A() // ERROR "inlining call" + a.(M).M() // ERROR "devirtualizing a.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" + a.(A).A() // ERROR "devirtualizing a.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A$" + a.(*Impl).M() // ERROR "inlining call to \(\*Impl\).M$" + a.(*Impl).A() // ERROR "inlining call to \(\*Impl\).A$" v := a.(M) - v.M() // ERROR "devirtualizing v.M" "inlining call" - v.(A).A() // ERROR "devirtualizing v.\(A\).A" "inlining call" - v.(*Impl).A() // ERROR "inlining call" - v.(*Impl).M() // ERROR "inlining call" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" + v.(A).A() // ERROR "devirtualizing v.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.(*Impl).A() // ERROR "inlining call to \(\*Impl\).A$" + v.(*Impl).M() // ERROR "inlining call to \(\*Impl\).M$" v2 := a.(A) - v2.A() // ERROR "devirtualizing v2.A" "inlining call" - v2.(M).M() // ERROR "devirtualizing v2.\(M\).M" "inlining call" - v2.(*Impl).A() // ERROR "inlining call" - v2.(*Impl).M() // ERROR "inlining call" + v2.A() // ERROR "devirtualizing v2.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v2.(M).M() // ERROR "devirtualizing v2.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" + v2.(*Impl).A() // ERROR "inlining call to \(\*Impl\).A$" + v2.(*Impl).M() // ERROR "inlining call to \(\*Impl\).M$" - a.(M).(A).A() // ERROR "devirtualizing a.\(M\).\(A\).A" "inlining call" - a.(A).(M).M() // ERROR "devirtualizing a.\(A\).\(M\).M" "inlining call" + a.(M).(A).A() // ERROR "devirtualizing a.\(M\).\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A$" + a.(A).(M).M() // ERROR "devirtualizing a.\(A\).\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" - a.(M).(A).(*Impl).A() // ERROR "inlining call" - a.(A).(M).(*Impl).M() // ERROR "inlining call" + a.(M).(A).(*Impl).A() // ERROR "inlining call to \(\*Impl\).A$" + a.(A).(M).(*Impl).M() // ERROR "inlining call to \(\*Impl\).M$" - any(a).(M).M() // ERROR "devirtualizing" "inlining call" - any(a).(A).A() // ERROR "devirtualizing" "inlining call" - any(a).(M).(any).(A).A() // ERROR "devirtualizing" "inlining call" + any(a).(M).M() // ERROR "devirtualizing any\(a\).\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" + any(a).(A).A() // ERROR "devirtualizing any\(a\).\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A$" + any(a).(M).(any).(A).A() // ERROR "devirtualizing any\(a\).\(M\).\(any\).\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A$" c := any(a) - c.(A).A() // ERROR "devirtualizing" "inlining call" - c.(M).M() // ERROR "devirtualizing" "inlining call" + c.(A).A() // ERROR "devirtualizing c.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A$" + c.(M).M() // ERROR "devirtualizing c.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" - M(a).M() // ERROR "devirtualizing" "inlining call" - M(M(a)).M() // ERROR "devirtualizing" "inlining call" + M(a).M() // ERROR "devirtualizing M\(a\).M to \*Impl$" "inlining call to \(\*Impl\).M$" + M(M(a)).M() // ERROR "devirtualizing M\(M\(a\)\).M to \*Impl$" "inlining call to \(\*Impl\).M$" a2 := a.(A) - A(a2).A() // ERROR "devirtualizing" "inlining call" - A(A(a2)).A() // ERROR "devirtualizing" "inlining call" + A(a2).A() // ERROR "devirtualizing A\(a2\).A to \*Impl$" "inlining call to \(\*Impl\).A$" + A(A(a2)).A() // ERROR "devirtualizing A\(A\(a2\)\).A to \*Impl$" "inlining call to \(\*Impl\).A$" { - var a C = &CImpl{} // ERROR "does not escape" - a.(any).(C).C() // ERROR "devirtualizing" "inlining" - a.(any).(*CImpl).C() // ERROR "inlining" + var a C = &CImpl{} // ERROR "&CImpl{} does not escape$" + a.(any).(C).C() // ERROR "devirtualizing a.\(any\).\(C\).C to \*CImpl$" "inlining call to CImpl.C$" + a.(any).(*CImpl).C() // ERROR "inlining call to CImpl.C$" } } func typeAssertsWithOkReturn() { { - var a M = &Impl{} // ERROR "does not escape" + var a M = &Impl{} // ERROR "&Impl{} does not escape$" if v, ok := a.(M); ok { - v.M() // ERROR "devirtualizing" "inlining call" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" } } { - var a M = &Impl{} // ERROR "does not escape" + var a M = &Impl{} // ERROR "&Impl{} does not escape$" if v, ok := a.(A); ok { - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } } { - var a M = &Impl{} // ERROR "does not escape" + var a M = &Impl{} // ERROR "&Impl{} does not escape$" v, ok := a.(M) if ok { - v.M() // ERROR "devirtualizing" "inlining call" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" } } { - var a M = &Impl{} // ERROR "does not escape" + var a M = &Impl{} // ERROR "&Impl{} does not escape$" v, ok := a.(A) if ok { - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } } { - var a M = &Impl{} // ERROR "does not escape" + var a M = &Impl{} // ERROR "&Impl{} does not escape$" v, ok := a.(*Impl) if ok { - v.A() // ERROR "inlining" - v.M() // ERROR "inlining" + v.A() // ERROR "inlining call to \(\*Impl\).A$" + v.M() // ERROR "inlining call to \(\*Impl\).M$" } } { - var a M = &Impl{} // ERROR "does not escape" + var a M = &Impl{} // ERROR "&Impl{} does not escape$" v, _ := a.(M) - v.M() // ERROR "devirtualizing" "inlining call" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" } { - var a M = &Impl{} // ERROR "does not escape" + var a M = &Impl{} // ERROR "&Impl{} does not escape$" v, _ := a.(A) - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { - var a M = &Impl{} // ERROR "does not escape" + var a M = &Impl{} // ERROR "&Impl{} does not escape$" v, _ := a.(*Impl) - v.A() // ERROR "inlining" - v.M() // ERROR "inlining" + v.A() // ERROR "inlining call to \(\*Impl\).A$" + v.M() // ERROR "inlining call to \(\*Impl\).M$" } { - a := newM() // ERROR "does not escape" "inlining call" - callA(a) // ERROR "devirtualizing" "inlining call" - callIfA(a) // ERROR "devirtualizing" "inlining call" + a := newM() // ERROR "&Impl{} does not escape$" "inlining call to newM$" + callA(a) // ERROR "devirtualizing m.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A$" "inlining call to callA$" + callIfA(a) // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" "inlining call to callIfA$" } { - var a M = &Impl{} // ERROR "does not escape" + var a M = &Impl{} // ERROR "&Impl{} does not escape$" // Note the !ok condition, devirtualizing here is fine. if v, ok := a.(M); !ok { - v.M() // ERROR "devirtualizing" "inlining call" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" } } { var a A = newImplNoInline() if v, ok := a.(M); ok { - v.M() // ERROR "devirtualizing" "inlining call" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" } } { - var impl2InA A = &Impl2{} // ERROR "does not escape" + var impl2InA A = &Impl2{} // ERROR "&Impl2{} does not escape$" var a A a, _ = impl2InA.(*Impl) // a now contains the zero value of *Impl - a.A() // ERROR "devirtualizing" "inlining call" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { a := newANoInline() @@ -162,15 +162,15 @@ func typeAssertsWithOkReturn() { } } -func newM() M { // ERROR "can inline" - return &Impl{} // ERROR "escapes" +func newM() M { // ERROR "can inline newM$" + return &Impl{} // ERROR "&Impl{} escapes to heap$" } -func callA(m M) { // ERROR "can inline" "leaking param" +func callA(m M) { // ERROR "can inline callA$" "leaking param: m$" m.(A).A() } -func callIfA(m M) { // ERROR "can inline" "leaking param" +func callIfA(m M) { // ERROR "can inline callIfA$" "leaking param: m$" if v, ok := m.(A); ok { v.A() } @@ -178,79 +178,79 @@ func callIfA(m M) { // ERROR "can inline" "leaking param" //go:noinline func newImplNoInline() *Impl { - return &Impl{} // ERROR "escapes" + return &Impl{} // ERROR "&Impl{} escapes to heap$" } //go:noinline func newImpl2ret2() (string, *Impl2) { - return "str", &Impl2{} // ERROR "escapes" + return "str", &Impl2{} // ERROR "&Impl2{} escapes to heap$" } //go:noinline func newImpl2() *Impl2 { - return &Impl2{} // ERROR "escapes" + return &Impl2{} // ERROR "&Impl2{} escapes to heap$" } //go:noinline func newANoInline() A { - return &Impl{} // ERROR "escapes" + return &Impl{} // ERROR "&Impl{} escapes to heap$" } //go:noinline func newANoInlineRet2() (string, A) { - return "", &Impl{} // ERROR "escapes" + return "", &Impl{} // ERROR "&Impl{} escapes to heap$" } func testTypeSwitch() { { - var v A = &Impl{} // ERROR "does not escape" + var v A = &Impl{} // ERROR "&Impl{} does not escape$" switch v := v.(type) { case A: - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" case M: - v.M() // ERROR "devirtualizing" "inlining call" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" } } { - var v A = &Impl{} // ERROR "does not escape" + var v A = &Impl{} // ERROR "&Impl{} does not escape$" switch v := v.(type) { case A: - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" case M: - v.M() // ERROR "devirtualizing" "inlining call" - v = &Impl{} // ERROR "does not escape" - v.M() // ERROR "devirtualizing" "inlining call" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" + v = &Impl{} // ERROR "&Impl{} does not escape$" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" } - v.(M).M() // ERROR "devirtualizing" "inlining call" + v.(M).M() // ERROR "devirtualizing v.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" } { - var v A = &Impl{} // ERROR "escapes" + var v A = &Impl{} // ERROR "&Impl{} escapes to heap$" switch v1 := v.(type) { case A: v1.A() case M: v1.M() - v = &Impl2{} // ERROR "escapes" + v = &Impl2{} // ERROR "&Impl2{} escapes to heap$" } } { - var v A = &Impl{} // ERROR "escapes" + var v A = &Impl{} // ERROR "&Impl{} escapes to heap$" switch v := v.(type) { case A: - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" case M: - v.M() // ERROR "devirtualizing" "inlining call" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" case C: v.C() } } { - var v A = &Impl{} // ERROR "does not escape" + var v A = &Impl{} // ERROR "&Impl{} does not escape$" switch v := v.(type) { case M: - v.M() // ERROR "devirtualizing" "inlining call" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" default: - panic("does not implement M") // ERROR "escapes" + panic("does not implement M") // ERROR ".does not implement M. escapes to heap$" } } } @@ -258,48 +258,48 @@ func testTypeSwitch() { func differentTypeAssign() { { var a A - a = &Impl{} // ERROR "escapes" - a = &Impl2{} // ERROR "escapes" + a = &Impl{} // ERROR "&Impl{} escapes to heap$" + a = &Impl2{} // ERROR "&Impl2{} escapes to heap$" a.A() } { - a := A(&Impl{}) // ERROR "escapes" - a = &Impl2{} // ERROR "escapes" + a := A(&Impl{}) // ERROR "&Impl{} escapes to heap$" + a = &Impl2{} // ERROR "&Impl2{} escapes to heap$" a.A() } { - a := A(&Impl{}) // ERROR "escapes" + a := A(&Impl{}) // ERROR "&Impl{} escapes to heap$" a.A() - a = &Impl2{} // ERROR "escapes" + a = &Impl2{} // ERROR "&Impl2{} escapes to heap$" } { - a := A(&Impl{}) // ERROR "escapes" - a = &Impl2{} // ERROR "escapes" + a := A(&Impl{}) // ERROR "&Impl{} escapes to heap$" + a = &Impl2{} // ERROR "&Impl2{} escapes to heap$" var asAny any = a asAny.(A).A() } { - a := A(&Impl{}) // ERROR "escapes" + a := A(&Impl{}) // ERROR "&Impl{} escapes to heap$" var asAny any = a - asAny = &Impl2{} // ERROR "escapes" + asAny = &Impl2{} // ERROR "&Impl2{} escapes to heap$" asAny.(A).A() } { - a := A(&Impl{}) // ERROR "escapes" + a := A(&Impl{}) // ERROR "&Impl{} escapes to heap$" var asAny any = a asAny.(A).A() - asAny = &Impl2{} // ERROR "escapes" - a.A() // ERROR "devirtualizing" "inlining call" + asAny = &Impl2{} // ERROR "&Impl2{} escapes to heap$" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { var a A - a = &Impl{} // ERROR "escapes" + a = &Impl{} // ERROR "&Impl{} escapes to heap$" a = newImpl2() a.A() } { var a A - a = &Impl{} // ERROR "escapes" + a = &Impl{} // ERROR "&Impl{} escapes to heap$" _, a = newImpl2ret2() a.A() } @@ -307,54 +307,54 @@ func differentTypeAssign() { func assignWithTypeAssert() { { - var i1 A = &Impl{} // ERROR "does not escape" - var i2 A = &Impl2{} // ERROR "does not escape" + var i1 A = &Impl{} // ERROR "&Impl{} does not escape$" + var i2 A = &Impl2{} // ERROR "&Impl2{} does not escape$" i1 = i2.(*Impl) // this will panic - i1.A() // ERROR "devirtualizing i1\.A to \*Impl" "inlining call" - i2.A() // ERROR "devirtualizing i2\.A to \*Impl2" "inlining call" + i1.A() // ERROR "devirtualizing i1.A to \*Impl$" "inlining call to \(\*Impl\).A$" + i2.A() // ERROR "devirtualizing i2.A to \*Impl2$" "inlining call to \(\*Impl2\).A$" } { - var i1 A = &Impl{} // ERROR "does not escape" - var i2 A = &Impl2{} // ERROR "does not escape" + var i1 A = &Impl{} // ERROR "&Impl{} does not escape$" + var i2 A = &Impl2{} // ERROR "&Impl2{} does not escape$" i1, _ = i2.(*Impl) // i1 is going to be nil - i1.A() // ERROR "devirtualizing i1\.A to \*Impl" "inlining call" - i2.A() // ERROR "devirtualizing i2\.A to \*Impl2" "inlining call" + i1.A() // ERROR "devirtualizing i1.A to \*Impl$" "inlining call to \(\*Impl\).A$" + i2.A() // ERROR "devirtualizing i2.A to \*Impl2$" "inlining call to \(\*Impl2\).A$" } } func nilIface() { { - var v A = &Impl{} // ERROR "&Impl{} does not escape" + var v A = &Impl{} // ERROR "&Impl{} does not escape$" v = nil - v.A() // ERROR "devirtualizing v\.A to \*Impl" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { - var v A = &Impl{} // ERROR "&Impl{} does not escape" - v.A() // ERROR "devirtualizing v\.A to \*Impl" "inlining call" + var v A = &Impl{} // ERROR "&Impl{} does not escape$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" v = nil } { var nilIface A - var v A = &Impl{} // ERROR "&Impl{} does not escape" - v.A() // ERROR "devirtualizing v\.A to \*Impl" "inlining call" + var v A = &Impl{} // ERROR "&Impl{} does not escape$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" v = nilIface } { var nilIface A - var v A = &Impl{} // ERROR "&Impl{} does not escape" + var v A = &Impl{} // ERROR "&Impl{} does not escape$" v = nilIface - v.A() // ERROR "devirtualizing v\.A to \*Impl" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { var v A - v.A() // ERROR "devirtualizing v\.A to \*Impl" "inlining call" - v = &Impl{} // ERROR "does not escape" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v = &Impl{} // ERROR "&Impl{} does not escape$" } { var v A var v2 A = v - v2.A() // ERROR "devirtualizing v2\.A to \*Impl" "inlining call" - v2 = &Impl{} // ERROR "does not escape" + v2.A() // ERROR "devirtualizing v2.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v2 = &Impl{} // ERROR "&Impl{} does not escape$" } { var v A @@ -377,99 +377,99 @@ func longDevirtTest() { var a interface { M A - } = &Impl{} // ERROR "does not escape" + } = &Impl{} // ERROR "&Impl{} does not escape$" { var b A = a - b.A() // ERROR "devirtualizing" "inlining call" - b.(M).M() // ERROR "devirtualizing" "inlining call" + b.A() // ERROR "devirtualizing b.A to \*Impl$" "inlining call to \(\*Impl\).A$" + b.(M).M() // ERROR "devirtualizing b.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" } { var b M = a - b.M() // ERROR "devirtualizing" "inlining call" - b.(A).A() // ERROR "devirtualizing" "inlining call" + b.M() // ERROR "devirtualizing b.M to \*Impl$" "inlining call to \(\*Impl\).M$" + b.(A).A() // ERROR "devirtualizing b.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A$" } { var b A = a.(M).(A) - b.A() // ERROR "devirtualizing" "inlining call" - b.(M).M() // ERROR "devirtualizing" "inlining call" + b.A() // ERROR "devirtualizing b.A to \*Impl$" "inlining call to \(\*Impl\).A$" + b.(M).M() // ERROR "devirtualizing b.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" } { var b M = a.(A).(M) - b.M() // ERROR "devirtualizing" "inlining call" - b.(A).A() // ERROR "devirtualizing" "inlining call" + b.M() // ERROR "devirtualizing b.M to \*Impl$" "inlining call to \(\*Impl\).M$" + b.(A).A() // ERROR "devirtualizing b.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A$" } if v, ok := a.(A); ok { - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } if v, ok := a.(M); ok { - v.M() // ERROR "devirtualizing" "inlining call" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" } { var c A = a if v, ok := c.(A); ok { - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } - c = &Impl{} // ERROR "does not escape" + c = &Impl{} // ERROR "&Impl{} does not escape$" if v, ok := c.(M); ok { - v.M() // ERROR "devirtualizing" "inlining call" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" } if v, ok := c.(interface { A M }); ok { - v.M() // ERROR "devirtualizing" "inlining call" - v.A() // ERROR "devirtualizing" "inlining call" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } } } func deferDevirt() { var a A - defer func() { // ERROR "func literal does not escape" "can inline" - a = &Impl{} // ERROR "escapes" + defer func() { // ERROR "can inline deferDevirt.func1$" "func literal does not escape$" + a = &Impl{} // ERROR "&Impl{} escapes to heap$" }() - a = &Impl{} // ERROR "does not escape" - a.A() // ERROR "devirtualizing" "inlining call" + a = &Impl{} // ERROR "&Impl{} does not escape$" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" } func deferNoDevirt() { var a A - defer func() { // ERROR "func literal does not escape" "can inline" - a = &Impl2{} // ERROR "escapes" + defer func() { // ERROR "can inline deferNoDevirt.func1$" "func literal does not escape$" + a = &Impl2{} // ERROR "&Impl2{} escapes to heap$" }() - a = &Impl{} // ERROR "escapes" + a = &Impl{} // ERROR "&Impl{} escapes to heap$" a.A() } //go:noinline func closureDevirt() { var a A - func() { // ERROR "func literal does not escape" + func() { // ERROR "func literal does not escape$" // defer so that it does not lnline. - defer func() {}() // ERROR "can inline" "func literal does not escape" - a = &Impl{} // ERROR "escapes" + defer func() {}() // ERROR "can inline closureDevirt.func1.1$" "func literal does not escape$" + a = &Impl{} // ERROR "&Impl{} escapes to heap$" }() - a = &Impl{} // ERROR "does not escape" - a.A() // ERROR "devirtualizing" "inlining call" + a = &Impl{} // ERROR "&Impl{} does not escape$" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" } //go:noinline func closureNoDevirt() { var a A - func() { // ERROR "func literal does not escape" + func() { // ERROR "func literal does not escape$" // defer so that it does not lnline. - defer func() {}() // ERROR "can inline" "func literal does not escape" - a = &Impl2{} // ERROR "escapes" + defer func() {}() // ERROR "can inline closureNoDevirt.func1.1$" "func literal does not escape$" + a = &Impl2{} // ERROR "&Impl2{} escapes to heap$" }() - a = &Impl{} // ERROR "escapes" + a = &Impl{} // ERROR "&Impl{} escapes to heap$" a.A() } @@ -477,28 +477,28 @@ var global = "1" func closureDevirt2() { var a A - a = &Impl{} // ERROR "does not escape" - c := func() { // ERROR "can inline" "func literal does not escape" - a = &Impl{} // ERROR "escapes" + a = &Impl{} // ERROR "&Impl{} does not escape$" + c := func() { // ERROR "can inline closureDevirt2.func1$" "func literal does not escape$" + a = &Impl{} // ERROR "&Impl{} escapes to heap$" } if global == "1" { - c = func() { // ERROR "can inline" "func literal does not escape" - a = &Impl{} // ERROR "escapes" + c = func() { // ERROR "can inline closureDevirt2.func2$" "func literal does not escape$" + a = &Impl{} // ERROR "&Impl{} escapes to heap$" } } - a.A() // ERROR "devirtualizing" "inlining call" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" c() } func closureNoDevirt2() { var a A - a = &Impl{} // ERROR "escapes" - c := func() { // ERROR "can inline" "func literal does not escape" - a = &Impl2{} // ERROR "escapes" + a = &Impl{} // ERROR "&Impl{} escapes to heap$" + c := func() { // ERROR "can inline closureNoDevirt2.func1$" "func literal does not escape$" + a = &Impl2{} // ERROR "&Impl2{} escapes to heap$" } if global == "1" { - c = func() { // ERROR "can inline" "func literal does not escape" - a = &Impl{} // ERROR "escapes" + c = func() { // ERROR "can inline closureNoDevirt2.func2$" "func literal does not escape$" + a = &Impl{} // ERROR "&Impl{} escapes to heap$" } } a.A() @@ -507,59 +507,59 @@ func closureNoDevirt2() { //go:noinline func varDeclaredInClosureReferencesOuter() { - var a A = &Impl{} // ERROR "&Impl{} does not escape" - func() { // ERROR "func literal does not escape" + var a A = &Impl{} // ERROR "&Impl{} does not escape$" + func() { // ERROR "func literal does not escape$" // defer for noinline - defer func() {}() // ERROR "can inline" "func literal does not escape" + defer func() {}() // ERROR "can inline varDeclaredInClosureReferencesOuter.func1.1$" "func literal does not escape$" var v A = a - v.A() // ERROR "devirtualizing v.A to \*Impl" "inlining call to \(\*Impl\).A" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" }() - func() { // ERROR "func literal does not escape" + func() { // ERROR "func literal does not escape$" // defer for noinline - defer func() {}() // ERROR "can inline" "func literal does not escape" + defer func() {}() // ERROR "can inline varDeclaredInClosureReferencesOuter.func2.1$" "func literal does not escape$" var v A = a - v = &Impl{} // ERROR "&Impl{} does not escape" - v.A() // ERROR "devirtualizing v.A to \*Impl" "inlining call to \(\*Impl\).A" + v = &Impl{} // ERROR "&Impl{} does not escape$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" }() - var b A = &Impl{} // ERROR "&Impl{} escapes to heap" - func() { // ERROR "func literal does not escape" + var b A = &Impl{} // ERROR "&Impl{} escapes to heap$" + func() { // ERROR "func literal does not escape$" // defer for noinline - defer func() {}() // ERROR "can inline" "func literal does not escape" + defer func() {}() // ERROR "can inline varDeclaredInClosureReferencesOuter.func3.1$" "func literal does not escape$" var v A = b - v = &Impl2{} // ERROR "&Impl2{} escapes to heap" + v = &Impl2{} // ERROR "&Impl2{} escapes to heap$" v.A() }() - func() { // ERROR "func literal does not escape" + func() { // ERROR "func literal does not escape$" // defer for noinline - defer func() {}() // ERROR "can inline" "func literal does not escape" + defer func() {}() // ERROR "can inline varDeclaredInClosureReferencesOuter.func4.1$" "func literal does not escape$" var v A = b v.A() - v = &Impl2{} // ERROR "&Impl2{} escapes to heap" + v = &Impl2{} // ERROR "&Impl2{} escapes to heap$" }() } //go:noinline func testNamedReturn0() (v A) { - v = &Impl{} // ERROR "escapes" + v = &Impl{} // ERROR "&Impl{} escapes to heap$" v.A() return } //go:noinline func testNamedReturn1() (v A) { - v = &Impl{} // ERROR "escapes" + v = &Impl{} // ERROR "&Impl{} escapes to heap$" v.A() - return &Impl{} // ERROR "escapes" + return &Impl{} // ERROR "&Impl{} escapes to heap$" } func testNamedReturns3() (v A) { - v = &Impl{} // ERROR "escapes" - defer func() { // ERROR "can inline" "func literal does not escape" + v = &Impl{} // ERROR "&Impl{} escapes to heap$" + defer func() { // ERROR "can inline testNamedReturns3.func1$" "func literal does not escape$" v.A() }() v.A() - return &Impl2{} // ERROR "escapes" + return &Impl2{} // ERROR "&Impl2{} escapes to heap$" } var ( @@ -585,39 +585,39 @@ func globals() { m.(A).A() } { - var a A = &Impl{} // ERROR "does not escape" + var a A = &Impl{} // ERROR "&Impl{} does not escape$" a = globalImpl - a.A() // ERROR "devirtualizing" "inlining call" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { - var a A = &Impl{} // ERROR "does not escape" + var a A = &Impl{} // ERROR "&Impl{} does not escape$" a = A(globalImpl) - a.A() // ERROR "devirtualizing" "inlining call" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { - var a A = &Impl{} // ERROR "does not escape" + var a A = &Impl{} // ERROR "&Impl{} does not escape$" a = M(globalImpl).(A) - a.A() // ERROR "devirtualizing" "inlining call" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { - var a A = &Impl{} // ERROR "does not escape" + var a A = &Impl{} // ERROR "&Impl{} does not escape$" a = globalA.(*Impl) - a.A() // ERROR "devirtualizing" "inlining call" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" a = globalM.(*Impl) - a.A() // ERROR "devirtualizing" "inlining call" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { - var a A = &Impl{} // ERROR "escapes" + var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" a = globalImpl2 a.A() } { - var a A = &Impl{} // ERROR "escapes" + var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" a = globalA a.A() } { - var a A = &Impl{} // ERROR "escapes" + var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" a = globalM.(A) a.A() } @@ -625,63 +625,63 @@ func globals() { func mapsDevirt() { { - m := make(map[int]*Impl) // ERROR "does not escape" + m := make(map[int]*Impl) // ERROR "make\(map\[int\]\*Impl\) does not escape$" var v A = m[0] - v.A() // ERROR "devirtualizing" "inlining call" - v.(M).M() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.(M).M() // ERROR "devirtualizing v.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" } { - m := make(map[int]*Impl) // ERROR "does not escape" + m := make(map[int]*Impl) // ERROR "make\(map\[int\]\*Impl\) does not escape$" var v A var ok bool if v, ok = m[0]; ok { - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { - m := make(map[int]*Impl) // ERROR "does not escape" + m := make(map[int]*Impl) // ERROR "make\(map\[int\]\*Impl\) does not escape$" var v A v, _ = m[0] - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } } func mapsNoDevirt() { { - m := make(map[int]*Impl) // ERROR "does not escape" + m := make(map[int]*Impl) // ERROR "make\(map\[int\]\*Impl\) does not escape$" var v A = m[0] v.A() - v = &Impl2{} // ERROR "escapes" + v = &Impl2{} // ERROR "&Impl2{} escapes to heap$" v.(M).M() } { - m := make(map[int]*Impl) // ERROR "does not escape" + m := make(map[int]*Impl) // ERROR "make\(map\[int\]\*Impl\) does not escape$" var v A var ok bool if v, ok = m[0]; ok { v.A() } - v = &Impl2{} // ERROR "escapes" + v = &Impl2{} // ERROR "&Impl2{} escapes to heap$" v.A() } { - m := make(map[int]*Impl) // ERROR "does not escape" - var v A = &Impl{} // ERROR "escapes" + m := make(map[int]*Impl) // ERROR "make\(map\[int\]\*Impl\) does not escape$" + var v A = &Impl{} // ERROR "&Impl{} escapes to heap$" v, _ = m[0] - v = &Impl2{} // ERROR "escapes" + v = &Impl2{} // ERROR "&Impl2{} escapes to heap$" v.A() } { - m := make(map[int]A) // ERROR "does not escape" - var v A = &Impl{} // ERROR "escapes" + m := make(map[int]A) // ERROR "make\(map\[int\]A\) does not escape$" + var v A = &Impl{} // ERROR "&Impl{} escapes to heap$" v = m[0] v.A() } { - m := make(map[int]A) // ERROR "does not escape" - var v A = &Impl{} // ERROR "escapes" + m := make(map[int]A) // ERROR "make\(map\[int\]A\) does not escape$" + var v A = &Impl{} // ERROR "&Impl{} escapes to heap$" var ok bool if v, ok = m[0]; ok { v.A() @@ -689,8 +689,8 @@ func mapsNoDevirt() { v.A() } { - m := make(map[int]A) // ERROR "does not escape" - var v A = &Impl{} // ERROR "escapes" + m := make(map[int]A) // ERROR "make\(map\[int\]A\) does not escape$" + var v A = &Impl{} // ERROR "&Impl{} escapes to heap$" v, _ = m[0] v.A() } @@ -700,43 +700,43 @@ func chanDevirt() { { m := make(chan *Impl) var v A = <-m - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { m := make(chan *Impl) var v A v = <-m - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { m := make(chan *Impl) var v A v, _ = <-m - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { m := make(chan *Impl) var v A var ok bool if v, ok = <-m; ok { - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { m := make(chan *Impl) var v A var ok bool if v, ok = <-m; ok { - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } select { case <-m: - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" case v = <-m: - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" case v, ok = <-m: - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } } } @@ -745,21 +745,21 @@ func chanNoDevirt() { { m := make(chan *Impl) var v A = <-m - v = &Impl2{} // ERROR "escapes" + v = &Impl2{} // ERROR "&Impl2{} escapes to heap$" v.A() } { m := make(chan *Impl) var v A v = <-m - v = &Impl2{} // ERROR "escapes" + v = &Impl2{} // ERROR "&Impl2{} escapes to heap$" v.A() } { m := make(chan *Impl) var v A v, _ = <-m - v = &Impl2{} // ERROR "escapes" + v = &Impl2{} // ERROR "&Impl2{} escapes to heap$" v.A() } { @@ -769,12 +769,12 @@ func chanNoDevirt() { if v, ok = <-m; ok { v.A() } - v = &Impl2{} // ERROR "escapes" + v = &Impl2{} // ERROR "&Impl2{} escapes to heap$" v.A() } { m := make(chan *Impl) - var v A = &Impl2{} // ERROR "escapes" + var v A = &Impl2{} // ERROR "&Impl2{} escapes to heap$" var ok bool if v, ok = <-m; ok { v.A() @@ -782,7 +782,7 @@ func chanNoDevirt() { } { m := make(chan *Impl) - var v A = &Impl2{} // ERROR "escapes" + var v A = &Impl2{} // ERROR "&Impl2{} escapes to heap$" select { case v = <-m: v.A() @@ -791,7 +791,7 @@ func chanNoDevirt() { } { m := make(chan *Impl) - var v A = &Impl2{} // ERROR "escapes" + var v A = &Impl2{} // ERROR "&Impl2{} escapes to heap$" select { case v, _ = <-m: v.A() @@ -801,19 +801,19 @@ func chanNoDevirt() { { m := make(chan A) - var v A = &Impl{} // ERROR "escapes" + var v A = &Impl{} // ERROR "&Impl{} escapes to heap$" v = <-m v.A() } { m := make(chan A) - var v A = &Impl{} // ERROR "escapes" + var v A = &Impl{} // ERROR "&Impl{} escapes to heap$" v, _ = <-m v.A() } { m := make(chan A) - var v A = &Impl{} // ERROR "escapes" + var v A = &Impl{} // ERROR "&Impl{} escapes to heap$" var ok bool if v, ok = <-m; ok { v.A() @@ -821,7 +821,7 @@ func chanNoDevirt() { } { m := make(chan A) - var v A = &Impl{} // ERROR "escapes" + var v A = &Impl{} // ERROR "&Impl{} escapes to heap$" select { case v = <-m: v.A() @@ -830,7 +830,7 @@ func chanNoDevirt() { } { m := make(chan A) - var v A = &Impl{} // ERROR "escapes" + var v A = &Impl{} // ERROR "&Impl{} escapes to heap$" select { case v, _ = <-m: v.A() @@ -842,120 +842,120 @@ func chanNoDevirt() { func rangeDevirt() { { var v A - m := make(map[*Impl]struct{}) // ERROR "does not escape" - v = &Impl{} // ERROR "does not escape" + m := make(map[*Impl]struct{}) // ERROR "make\(map\[\*Impl\]struct {}\) does not escape$" + v = &Impl{} // ERROR "&Impl{} does not escape$" for v = range m { } - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { var v A - m := make(map[*Impl]*Impl) // ERROR "does not escape" - v = &Impl{} // ERROR "does not escape" + m := make(map[*Impl]*Impl) // ERROR "make\(map\[\*Impl\]\*Impl\) does not escape$" + v = &Impl{} // ERROR "&Impl{} does not escape$" for v = range m { } - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { var v A - m := make(map[*Impl]*Impl) // ERROR "does not escape" - v = &Impl{} // ERROR "does not escape" + m := make(map[*Impl]*Impl) // ERROR "make\(map\[\*Impl\]\*Impl\) does not escape$" + v = &Impl{} // ERROR "&Impl{} does not escape$" for _, v = range m { } - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { var v A m := make(chan *Impl) - v = &Impl{} // ERROR "does not escape" + v = &Impl{} // ERROR "&Impl{} does not escape$" for v = range m { } - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { var v A - m := []*Impl{} // ERROR "does not escape" - v = &Impl{} // ERROR "does not escape" + m := []*Impl{} // ERROR "\[\]\*Impl{} does not escape$" + v = &Impl{} // ERROR "&Impl{} does not escape$" for _, v = range m { } - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { var v A - v = &Impl{} // ERROR "does not escape" - impl := &Impl{} // ERROR "does not escape" + v = &Impl{} // ERROR "&Impl{} does not escape$" + impl := &Impl{} // ERROR "&Impl{} does not escape$" i := 0 for v = impl; i < 10; i++ { } - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { var v A - v = &Impl{} // ERROR "does not escape" - impl := &Impl{} // ERROR "does not escape" + v = &Impl{} // ERROR "&Impl{} does not escape$" + impl := &Impl{} // ERROR "&Impl{} does not escape$" i := 0 for v = impl; i < 10; i++ { } - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { var v A - m := [1]*Impl{&Impl{}} // ERROR "does not escape" - v = &Impl{} // ERROR "does not escape" + m := [1]*Impl{&Impl{}} // ERROR "&Impl{} does not escape$" + v = &Impl{} // ERROR "&Impl{} does not escape$" for _, v = range m { } - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } { var v A - m := [1]*Impl{&Impl{}} // ERROR "does not escape" - v = &Impl{} // ERROR "does not escape" + m := [1]*Impl{&Impl{}} // ERROR "&Impl{} does not escape$" + v = &Impl{} // ERROR "&Impl{} does not escape$" for _, v = range &m { } - v.A() // ERROR "devirtualizing" "inlining call" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" } } func rangeNoDevirt() { { - var v A = &Impl2{} // ERROR "escapes" - m := make(map[*Impl]struct{}) // ERROR "does not escape" + var v A = &Impl2{} // ERROR "&Impl2{} escapes to heap$" + m := make(map[*Impl]struct{}) // ERROR "make\(map\[\*Impl\]struct {}\) does not escape$" for v = range m { } v.A() } { - var v A = &Impl2{} // ERROR "escapes" - m := make(map[*Impl]*Impl) // ERROR "does not escape" + var v A = &Impl2{} // ERROR "&Impl2{} escapes to heap$" + m := make(map[*Impl]*Impl) // ERROR "make\(map\[\*Impl\]\*Impl\) does not escape$" for v = range m { } v.A() } { - var v A = &Impl2{} // ERROR "escapes" - m := make(map[*Impl]*Impl) // ERROR "does not escape" + var v A = &Impl2{} // ERROR "&Impl2{} escapes to heap$" + m := make(map[*Impl]*Impl) // ERROR "make\(map\[\*Impl\]\*Impl\) does not escape$" for _, v = range m { } v.A() } { - var v A = &Impl2{} // ERROR "escapes" + var v A = &Impl2{} // ERROR "&Impl2{} escapes to heap$" m := make(chan *Impl) for v = range m { } v.A() } { - var v A = &Impl2{} // ERROR "escapes" - m := []*Impl{} // ERROR "does not escape" + var v A = &Impl2{} // ERROR "&Impl2{} escapes to heap$" + m := []*Impl{} // ERROR "\[\]\*Impl{} does not escape$" for _, v = range m { } v.A() } { var v A - v = &Impl2{} // ERROR "escapes" - impl := &Impl{} // ERROR "escapes" + v = &Impl2{} // ERROR "&Impl2{} escapes to heap$" + impl := &Impl{} // ERROR "&Impl{} escapes to heap$" i := 0 for v = impl; i < 10; i++ { } @@ -963,8 +963,8 @@ func rangeNoDevirt() { } { var v A - v = &Impl2{} // ERROR "escapes" - impl := &Impl{} // ERROR "escapes" + v = &Impl2{} // ERROR "&Impl2{} escapes to heap$" + impl := &Impl{} // ERROR "&Impl{} escapes to heap$" i := 0 for v = impl; i < 10; i++ { } @@ -972,52 +972,52 @@ func rangeNoDevirt() { } { var v A - m := [1]*Impl{&Impl{}} // ERROR "escapes" - v = &Impl2{} // ERROR "escapes" + m := [1]*Impl{&Impl{}} // ERROR "&Impl{} escapes to heap$" + v = &Impl2{} // ERROR "&Impl2{} escapes to heap$" for _, v = range m { } v.A() } { var v A - m := [1]*Impl{&Impl{}} // ERROR "escapes" - v = &Impl2{} // ERROR "escapes" + m := [1]*Impl{&Impl{}} // ERROR "&Impl{} escapes to heap$" + v = &Impl2{} // ERROR "&Impl2{} escapes to heap$" for _, v = range &m { } v.A() } { - var v A = &Impl{} // ERROR "escapes" - m := make(map[A]struct{}) // ERROR "does not escape" + var v A = &Impl{} // ERROR "&Impl{} escapes to heap$" + m := make(map[A]struct{}) // ERROR "make\(map\[A\]struct {}\) does not escape$" for v = range m { } v.A() } { - var v A = &Impl{} // ERROR "escapes" - m := make(map[A]A) // ERROR "does not escape" + var v A = &Impl{} // ERROR "&Impl{} escapes to heap$" + m := make(map[A]A) // ERROR "make\(map\[A\]A\) does not escape$" for v = range m { } v.A() } { - var v A = &Impl{} // ERROR "escapes" - m := make(map[A]A) // ERROR "does not escape" + var v A = &Impl{} // ERROR "&Impl{} escapes to heap$" + m := make(map[A]A) // ERROR "make\(map\[A\]A\) does not escape$" for _, v = range m { } v.A() } { - var v A = &Impl{} // ERROR "escapes" + var v A = &Impl{} // ERROR "&Impl{} escapes to heap$" m := make(chan A) for v = range m { } v.A() } { - var v A = &Impl{} // ERROR "escapes" - m := []A{} // ERROR "does not escape" + var v A = &Impl{} // ERROR "&Impl{} escapes to heap$" + m := []A{} // ERROR "\[\]A{} does not escape$" for _, v = range m { } v.A() @@ -1025,16 +1025,16 @@ func rangeNoDevirt() { { var v A - m := [1]A{&Impl{}} // ERROR "escapes" - v = &Impl{} // ERROR "escapes" + m := [1]A{&Impl{}} // ERROR "&Impl{} escapes to heap$" + v = &Impl{} // ERROR "&Impl{} escapes to heap$" for _, v = range m { } v.A() } { var v A - m := [1]A{&Impl{}} // ERROR "escapes" - v = &Impl{} // ERROR "escapes" + m := [1]A{&Impl{}} // ERROR "&Impl{} escapes to heap$" + v = &Impl{} // ERROR "&Impl{} escapes to heap$" for _, v = range &m { } v.A() @@ -1045,17 +1045,17 @@ var globalInt = 1 func testIfInit() { { - var a A = &Impl{} // ERROR "does not escape" - var i = &Impl{} // ERROR "does not escape" + var a A = &Impl{} // ERROR "&Impl{} does not escape$" + var i = &Impl{} // ERROR "&Impl{} does not escape$" if a = i; globalInt == 1 { - a.A() // ERROR "devirtualizing" "inlining call" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" } - a.A() // ERROR "devirtualizing" "inlining call" - a.(M).M() // ERROR "devirtualizing" "inlining call" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" + a.(M).M() // ERROR "devirtualizing a.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" } { - var a A = &Impl{} // ERROR "escapes" - var i2 = &Impl2{} // ERROR "escapes" + var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" + var i2 = &Impl2{} // ERROR "&Impl2{} escapes to heap$" if a = i2; globalInt == 1 { a.A() } @@ -1065,18 +1065,18 @@ func testIfInit() { func testSwitchInit() { { - var a A = &Impl{} // ERROR "does not escape" - var i = &Impl{} // ERROR "does not escape" + var a A = &Impl{} // ERROR "&Impl{} does not escape$" + var i = &Impl{} // ERROR "&Impl{} does not escape$" switch a = i; globalInt { case 12: - a.A() // ERROR "devirtualizing" "inlining call" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" } - a.A() // ERROR "devirtualizing" "inlining call" - a.(M).M() // ERROR "devirtualizing" "inlining call" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" + a.(M).M() // ERROR "devirtualizing a.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" } { - var a A = &Impl{} // ERROR "escapes" - var i2 = &Impl2{} // ERROR "escapes" + var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" + var i2 = &Impl2{} // ERROR "&Impl2{} escapes to heap$" switch a = i2; globalInt { case 12: a.A() @@ -1087,38 +1087,38 @@ func testSwitchInit() { type implWrapper Impl -func (implWrapper) A() {} // ERROR "can inline" +func (implWrapper) A() {} // ERROR "can inline implWrapper.A$" //go:noinline func devirtWrapperType() { { - i := &Impl{} // ERROR "does not escape" + i := &Impl{} // ERROR "&Impl{} does not escape$" // This is an OCONVNOP, so we have to be carefull, not to devirtualize it to Impl.A. var a A = (*implWrapper)(i) - a.A() // ERROR "devirtualizing a.A to \*implWrapper" "inlining call" + a.A() // ERROR "devirtualizing a.A to \*implWrapper$" "inlining call to implWrapper.A$" } { i := Impl{} // This is an OCONVNOP, so we have to be carefull, not to devirtualize it to Impl.A. - var a A = (implWrapper)(i) // ERROR "does not escape" - a.A() // ERROR "devirtualizing a.A to implWrapper" "inlining call" + var a A = (implWrapper)(i) // ERROR "implWrapper\(i\) does not escape$" + a.A() // ERROR "devirtualizing a.A to implWrapper$" "inlining call to implWrapper.A$" } } func selfAssigns() { { - var a A = &Impl{} // ERROR "escapes" + var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" a = a a.A() } { - var a A = &Impl{} // ERROR "escapes" + var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" var asAny any = a asAny = asAny asAny.(A).A() } { - var a A = &Impl{} // ERROR "escapes" + var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" var asAny any = a a = asAny.(A) asAny.(A).A() @@ -1127,7 +1127,7 @@ func selfAssigns() { b.(A).A() } { - var a A = &Impl{} // ERROR "escapes" + var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" var asAny any = a asAny = asAny a = asAny.(A) @@ -1136,7 +1136,7 @@ func selfAssigns() { asAny.(M).M() } { - var a A = &Impl{} // ERROR "escapes" + var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" var asAny A = a a = asAny.(A) a.A() @@ -1145,73 +1145,73 @@ func selfAssigns() { func boolNoDevirt() { { - m := make(map[int]*Impl) // ERROR "does not escape" - var v any = &Impl{} // ERROR "escapes" - _, v = m[0] // ERROR "escapes" + m := make(map[int]*Impl) // ERROR "make\(map\[int\]\*Impl\) does not escape$" + var v any = &Impl{} // ERROR "&Impl{} escapes to heap$" + _, v = m[0] // ERROR ".autotmp_[0-9]+ escapes to heap$" v.(A).A() } { m := make(chan *Impl) - var v any = &Impl{} // ERROR "escapes" + var v any = &Impl{} // ERROR "&Impl{} escapes to heap$" select { - case _, v = <-m: // ERROR "escapes" + case _, v = <-m: // ERROR ".autotmp_[0-9]+ escapes to heap$" } v.(A).A() } { m := make(chan *Impl) - var v any = &Impl{} // ERROR "escapes" - _, v = <-m // ERROR "escapes" + var v any = &Impl{} // ERROR "&Impl{} escapes to heap$" + _, v = <-m // ERROR ".autotmp_[0-9]+ escapes to heap$" v.(A).A() } { - var a any = 4 // ERROR "does not escape" - var v any = &Impl{} // ERROR "escapes" - _, v = a.(int) // ERROR "escapes" + var a any = 4 // ERROR "4 does not escape$" + var v any = &Impl{} // ERROR "&Impl{} escapes to heap$" + _, v = a.(int) // ERROR ".autotmp_[0-9]+ escapes to heap$" v.(A).A() } } func addrTaken() { { - var a A = &Impl{} // ERROR "escapes" + var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" var ptrA = &a a.A() _ = ptrA } { - var a A = &Impl{} // ERROR "escapes" + var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" var ptrA = &a - *ptrA = &Impl{} // ERROR "escapes" + *ptrA = &Impl{} // ERROR "&Impl{} escapes to heap$" a.A() } { - var a A = &Impl{} // ERROR "escapes" + var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" var ptrA = &a - *ptrA = &Impl2{} // ERROR "escapes" + *ptrA = &Impl2{} // ERROR "&Impl2{} escapes to heap$" a.A() } } func testInvalidAsserts() { - any(0).(interface{ A() }).A() // ERROR "escapes" + any(0).(interface{ A() }).A() // ERROR "any\(0\) escapes to heap$" { - var a M = &Impl{} // ERROR "escapes" + var a M = &Impl{} // ERROR "&Impl{} escapes to heap$" a.(C).C() // this will panic a.(any).(C).C() // this will panic } { - var a C = &CImpl{} // ERROR "escapes" + var a C = &CImpl{} // ERROR "&CImpl{} escapes to heap$" a.(M).M() // this will panic a.(any).(M).M() // this will panic } { - var a C = &CImpl{} // ERROR "does not escape" + var a C = &CImpl{} // ERROR "&CImpl{} does not escape$" // this will panic - a.(M).(*Impl).M() // ERROR "inlining" + a.(M).(*Impl).M() // ERROR "inlining call to \(\*Impl\).M$" // this will panic - a.(any).(M).(*Impl).M() // ERROR "inlining" + a.(any).(M).(*Impl).M() // ERROR "inlining call to \(\*Impl\).M$" } } diff --git a/test/devirtualization_with_type_assertions_interleaved.go b/test/devirtualization_with_type_assertions_interleaved.go index 7e80adc6bf42ce..52a61a94ff4076 100644 --- a/test/devirtualization_with_type_assertions_interleaved.go +++ b/test/devirtualization_with_type_assertions_interleaved.go @@ -17,91 +17,91 @@ type clonableHashIface interface { type hash struct{ state [32]byte } -func (h *hash) Sum() []byte { // ERROR "can inline" "h does not escape" - return make([]byte, 32) // ERROR "escapes" +func (h *hash) Sum() []byte { // ERROR "can inline \(\*hash\).Sum$" "h does not escape$" + return make([]byte, 32) // ERROR "make\(\[\]byte, 32\) escapes to heap$" } -func (h *hash) Clone() hashIface { // ERROR "can inline" "h does not escape" - c := *h // ERROR "moved to heap: c" +func (h *hash) Clone() hashIface { // ERROR "can inline \(\*hash\).Clone$" "h does not escape$" + c := *h // ERROR "moved to heap: c$" return &c } type hash2 struct{ state [32]byte } -func (h *hash2) Sum() []byte { // ERROR "can inline" "h does not escape" - return make([]byte, 32) // ERROR "escapes" +func (h *hash2) Sum() []byte { // ERROR "can inline \(\*hash2\).Sum$" "h does not escape$" + return make([]byte, 32) // ERROR "make\(\[\]byte, 32\) escapes to heap$" } -func (h *hash2) Clone() hashIface { // ERROR "can inline" "h does not escape" - c := *h // ERROR "moved to heap: c" +func (h *hash2) Clone() hashIface { // ERROR "can inline \(\*hash2\).Clone$" "h does not escape$" + c := *h // ERROR "moved to heap: c$" return &c } -func newHash() hashIface { // ERROR "can inline" - return &hash{} // ERROR "escapes" +func newHash() hashIface { // ERROR "can inline newHash$" + return &hash{} // ERROR "&hash{} escapes to heap$" } -func cloneHash1(h hashIface) hashIface { // ERROR "can inline" "leaking param: h" +func cloneHash1(h hashIface) hashIface { // ERROR "can inline cloneHash1$" "leaking param: h$" if h, ok := h.(clonableHashIface); ok { return h.Clone() } - return &hash{} // ERROR "escapes" + return &hash{} // ERROR "&hash{} escapes to heap$" } -func cloneHash2(h hashIface) hashIface { // ERROR "can inline" "leaking param: h" +func cloneHash2(h hashIface) hashIface { // ERROR "can inline cloneHash2$" "leaking param: h$" if h, ok := h.(clonableHashIface); ok { return h.Clone() } return nil } -func cloneHash3(h hashIface) hashIface { // ERROR "can inline" "leaking param: h" +func cloneHash3(h hashIface) hashIface { // ERROR "can inline cloneHash3$" "leaking param: h$" if h, ok := h.(clonableHashIface); ok { return h.Clone() } - return &hash2{} // ERROR "escapes" + return &hash2{} // ERROR "&hash2{} escapes to heap$" } -func cloneHashWithBool1(h hashIface) (hashIface, bool) { // ERROR "can inline" "leaking param: h" +func cloneHashWithBool1(h hashIface) (hashIface, bool) { // ERROR "can inline cloneHashWithBool1$" "leaking param: h$" if h, ok := h.(clonableHashIface); ok { return h.Clone(), true } - return &hash{}, false // ERROR "escapes" + return &hash{}, false // ERROR "&hash{} escapes to heap$" } -func cloneHashWithBool2(h hashIface) (hashIface, bool) { // ERROR "can inline" "leaking param: h" +func cloneHashWithBool2(h hashIface) (hashIface, bool) { // ERROR "can inline cloneHashWithBool2$" "leaking param: h$" if h, ok := h.(clonableHashIface); ok { return h.Clone(), true } return nil, false } -func cloneHashWithBool3(h hashIface) (hashIface, bool) { // ERROR "can inline" "leaking param: h" +func cloneHashWithBool3(h hashIface) (hashIface, bool) { // ERROR "can inline cloneHashWithBool3$" "leaking param: h$" if h, ok := h.(clonableHashIface); ok { return h.Clone(), true } - return &hash2{}, false // ERROR "escapes" + return &hash2{}, false // ERROR "&hash2{} escapes to heap$" } func interleavedWithTypeAssertions() { - h1 := newHash() // ERROR "&hash{} does not escape" "inlining call" - _ = h1.Sum() // ERROR "devirtualizing h1.Sum to \*hash" "inlining call" "make\(\[\]byte, 32\) does not escape" + h1 := newHash() // ERROR "&hash{} does not escape$" "inlining call to newHash$" + _ = h1.Sum() // ERROR "devirtualizing h1.Sum to \*hash$" "inlining call to \(\*hash\).Sum$" "make\(\[\]byte, 32\) does not escape$" - h2 := cloneHash1(h1) // ERROR "inlining call to cloneHash1" "devirtualizing h.Clone to \*hash" "inlining call to \(\*hash\).Clone" "&hash{} does not escape" - _ = h2.Sum() // ERROR "devirtualizing h2.Sum to \*hash" "inlining call" "make\(\[\]byte, 32\) does not escape" + h2 := cloneHash1(h1) // ERROR "&hash{} does not escape$" "devirtualizing h.Clone to \*hash$" "inlining call to \(\*hash\).Clone$" "inlining call to cloneHash1$" + _ = h2.Sum() // ERROR "devirtualizing h2.Sum to \*hash$" "inlining call to \(\*hash\).Sum$" "make\(\[\]byte, 32\) does not escape$" - h3 := cloneHash2(h1) // ERROR "inlining call to cloneHash2" "devirtualizing h.Clone to \*hash" "inlining call to \(\*hash\).Clone" - _ = h3.Sum() // ERROR "devirtualizing h3.Sum to \*hash" "inlining call" "make\(\[\]byte, 32\) does not escape" + h3 := cloneHash2(h1) // ERROR "devirtualizing h.Clone to \*hash$" "inlining call to \(\*hash\).Clone$" "inlining call to cloneHash2$" + _ = h3.Sum() // ERROR "devirtualizing h3.Sum to \*hash$" "inlining call to \(\*hash\).Sum$" "make\(\[\]byte, 32\) does not escape$" - h4 := cloneHash3(h1) // ERROR "inlining call to cloneHash3" "devirtualizing h.Clone to \*hash" "inlining call to \(\*hash\).Clone" "moved to heap: c" "&hash2{} escapes to heap" + h4 := cloneHash3(h1) // ERROR "&hash2{} escapes to heap$" "devirtualizing h.Clone to \*hash$" "inlining call to \(\*hash\).Clone$" "inlining call to cloneHash3$" "moved to heap: c$" _ = h4.Sum() - h5, _ := cloneHashWithBool1(h1) // ERROR "inlining call to cloneHashWithBool1" "devirtualizing h.Clone to \*hash" "inlining call to \(\*hash\).Clone" "&hash{} does not escape" - _ = h5.Sum() // ERROR "devirtualizing h5.Sum to \*hash" "inlining call" "make\(\[\]byte, 32\) does not escape" + h5, _ := cloneHashWithBool1(h1) // ERROR "&hash{} does not escape$" "devirtualizing h.Clone to \*hash$" "inlining call to \(\*hash\).Clone$" "inlining call to cloneHashWithBool1$" + _ = h5.Sum() // ERROR "devirtualizing h5.Sum to \*hash$" "inlining call to \(\*hash\).Sum$" "make\(\[\]byte, 32\) does not escape$" - h6, _ := cloneHashWithBool2(h1) // ERROR "inlining call to cloneHashWithBool2" "devirtualizing h.Clone to \*hash" "inlining call to \(\*hash\).Clone" - _ = h6.Sum() // ERROR "devirtualizing h6.Sum to \*hash" "inlining call" "make\(\[\]byte, 32\) does not escape" + h6, _ := cloneHashWithBool2(h1) // ERROR "devirtualizing h.Clone to \*hash$" "inlining call to \(\*hash\).Clone$" "inlining call to cloneHashWithBool2$" + _ = h6.Sum() // ERROR "devirtualizing h6.Sum to \*hash$" "inlining call to \(\*hash\).Sum$" "make\(\[\]byte, 32\) does not escape$" - h7, _ := cloneHashWithBool3(h1) // ERROR "inlining call to cloneHashWithBool3" "devirtualizing h.Clone to \*hash" "inlining call to \(\*hash\).Clone" "moved to heap: c" "&hash2{} escapes to heap" + h7, _ := cloneHashWithBool3(h1) // ERROR "&hash2{} escapes to heap$" "devirtualizing h.Clone to \*hash$" "inlining call to \(\*hash\).Clone$" "inlining call to cloneHashWithBool3$" "moved to heap: c$" _ = h7.Sum() } From ee1174781f132062e0ef6de8af4dd3790c0b8efd Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Wed, 26 Feb 2025 19:12:02 +0100 Subject: [PATCH 50/74] remove testing debug flag Change-Id: I082ea0732b3d0948362a5a1715085724dd0751e0 --- src/cmd/compile/internal/base/debug.go | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cmd/compile/internal/base/debug.go b/src/cmd/compile/internal/base/debug.go index d63b41b55b186c..d42e11b2fad881 100644 --- a/src/cmd/compile/internal/base/debug.go +++ b/src/cmd/compile/internal/base/debug.go @@ -75,7 +75,6 @@ type DebugFlags struct { WrapGlobalMapDbg int `help:"debug trace output for global map init wrapping"` WrapGlobalMapCtl int `help:"global map init wrap control (0 => default, 1 => off, 2 => stress mode, no size cutoff)"` ZeroCopy int `help:"enable zero-copy string->[]byte conversions" concurrent:"ok"` - Testing int `help:"here" concurrent:"ok"` ConcurrentOk bool // true if only concurrentOk flags seen } From 633f1086dee2accb7ecb4425a6df20634ec53e41 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Wed, 26 Feb 2025 19:19:14 +0100 Subject: [PATCH 51/74] remove testing params Change-Id: I96ca210ff3173ce4b8e72cbeffbb2f39c0863de4 --- test/devirtualization.go | 2 +- test/devirtualization_with_type_assertions_interleaved.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/devirtualization.go b/test/devirtualization.go index 900b4cc305aa33..6e137f0a1039c0 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -1,4 +1,4 @@ -// errorcheck -0 -m -d=testing=2 +// errorcheck -0 -m // Copyright 2025 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/test/devirtualization_with_type_assertions_interleaved.go b/test/devirtualization_with_type_assertions_interleaved.go index 52a61a94ff4076..e25e9c29aa586f 100644 --- a/test/devirtualization_with_type_assertions_interleaved.go +++ b/test/devirtualization_with_type_assertions_interleaved.go @@ -1,4 +1,4 @@ -// errorcheck -0 -m -d=testing=2 +// errorcheck -0 -m // Copyright 2025 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style From 3651bfb71ef64eab4272d4cf7a8674427c7f6ba3 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Wed, 26 Feb 2025 19:42:41 +0100 Subject: [PATCH 52/74] fix newinliner Change-Id: I37586e6e3aef9c48b0eefb748e32a0f633ee73c2 --- test/devirtualization.go | 240 +++++++++--------- ...zation_with_type_assertions_interleaved.go | 24 +- 2 files changed, 132 insertions(+), 132 deletions(-) diff --git a/test/devirtualization.go b/test/devirtualization.go index 6e137f0a1039c0..688dddac29cc29 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -31,48 +31,48 @@ func (CImpl) C() {} // ERROR "can inline CImpl.C$" func typeAsserts() { var a M = &Impl{} // ERROR "&Impl{} does not escape$" - a.(M).M() // ERROR "devirtualizing a.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" - a.(A).A() // ERROR "devirtualizing a.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A$" - a.(*Impl).M() // ERROR "inlining call to \(\*Impl\).M$" - a.(*Impl).A() // ERROR "inlining call to \(\*Impl\).A$" + a.(M).M() // ERROR "devirtualizing a.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M" + a.(A).A() // ERROR "devirtualizing a.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A" + a.(*Impl).M() // ERROR "inlining call to \(\*Impl\).M" + a.(*Impl).A() // ERROR "inlining call to \(\*Impl\).A" v := a.(M) - v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" - v.(A).A() // ERROR "devirtualizing v.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A$" - v.(*Impl).A() // ERROR "inlining call to \(\*Impl\).A$" - v.(*Impl).M() // ERROR "inlining call to \(\*Impl\).M$" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M" + v.(A).A() // ERROR "devirtualizing v.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A" + v.(*Impl).A() // ERROR "inlining call to \(\*Impl\).A" + v.(*Impl).M() // ERROR "inlining call to \(\*Impl\).M" v2 := a.(A) - v2.A() // ERROR "devirtualizing v2.A to \*Impl$" "inlining call to \(\*Impl\).A$" - v2.(M).M() // ERROR "devirtualizing v2.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" - v2.(*Impl).A() // ERROR "inlining call to \(\*Impl\).A$" - v2.(*Impl).M() // ERROR "inlining call to \(\*Impl\).M$" + v2.A() // ERROR "devirtualizing v2.A to \*Impl$" "inlining call to \(\*Impl\).A" + v2.(M).M() // ERROR "devirtualizing v2.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M" + v2.(*Impl).A() // ERROR "inlining call to \(\*Impl\).A" + v2.(*Impl).M() // ERROR "inlining call to \(\*Impl\).M" - a.(M).(A).A() // ERROR "devirtualizing a.\(M\).\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A$" - a.(A).(M).M() // ERROR "devirtualizing a.\(A\).\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" + a.(M).(A).A() // ERROR "devirtualizing a.\(M\).\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A" + a.(A).(M).M() // ERROR "devirtualizing a.\(A\).\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M" - a.(M).(A).(*Impl).A() // ERROR "inlining call to \(\*Impl\).A$" - a.(A).(M).(*Impl).M() // ERROR "inlining call to \(\*Impl\).M$" + a.(M).(A).(*Impl).A() // ERROR "inlining call to \(\*Impl\).A" + a.(A).(M).(*Impl).M() // ERROR "inlining call to \(\*Impl\).M" - any(a).(M).M() // ERROR "devirtualizing any\(a\).\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" - any(a).(A).A() // ERROR "devirtualizing any\(a\).\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A$" - any(a).(M).(any).(A).A() // ERROR "devirtualizing any\(a\).\(M\).\(any\).\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A$" + any(a).(M).M() // ERROR "devirtualizing any\(a\).\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M" + any(a).(A).A() // ERROR "devirtualizing any\(a\).\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A" + any(a).(M).(any).(A).A() // ERROR "devirtualizing any\(a\).\(M\).\(any\).\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A" c := any(a) - c.(A).A() // ERROR "devirtualizing c.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A$" - c.(M).M() // ERROR "devirtualizing c.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" + c.(A).A() // ERROR "devirtualizing c.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A" + c.(M).M() // ERROR "devirtualizing c.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M" - M(a).M() // ERROR "devirtualizing M\(a\).M to \*Impl$" "inlining call to \(\*Impl\).M$" - M(M(a)).M() // ERROR "devirtualizing M\(M\(a\)\).M to \*Impl$" "inlining call to \(\*Impl\).M$" + M(a).M() // ERROR "devirtualizing M\(a\).M to \*Impl$" "inlining call to \(\*Impl\).M" + M(M(a)).M() // ERROR "devirtualizing M\(M\(a\)\).M to \*Impl$" "inlining call to \(\*Impl\).M" a2 := a.(A) - A(a2).A() // ERROR "devirtualizing A\(a2\).A to \*Impl$" "inlining call to \(\*Impl\).A$" - A(A(a2)).A() // ERROR "devirtualizing A\(A\(a2\)\).A to \*Impl$" "inlining call to \(\*Impl\).A$" + A(a2).A() // ERROR "devirtualizing A\(a2\).A to \*Impl$" "inlining call to \(\*Impl\).A" + A(A(a2)).A() // ERROR "devirtualizing A\(A\(a2\)\).A to \*Impl$" "inlining call to \(\*Impl\).A" { var a C = &CImpl{} // ERROR "&CImpl{} does not escape$" - a.(any).(C).C() // ERROR "devirtualizing a.\(any\).\(C\).C to \*CImpl$" "inlining call to CImpl.C$" - a.(any).(*CImpl).C() // ERROR "inlining call to CImpl.C$" + a.(any).(C).C() // ERROR "devirtualizing a.\(any\).\(C\).C to \*CImpl$" "inlining call to CImpl.C" + a.(any).(*CImpl).C() // ERROR "inlining call to CImpl.C" } } @@ -80,69 +80,69 @@ func typeAssertsWithOkReturn() { { var a M = &Impl{} // ERROR "&Impl{} does not escape$" if v, ok := a.(M); ok { - v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M" } } { var a M = &Impl{} // ERROR "&Impl{} does not escape$" if v, ok := a.(A); ok { - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } } { var a M = &Impl{} // ERROR "&Impl{} does not escape$" v, ok := a.(M) if ok { - v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M" } } { var a M = &Impl{} // ERROR "&Impl{} does not escape$" v, ok := a.(A) if ok { - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } } { var a M = &Impl{} // ERROR "&Impl{} does not escape$" v, ok := a.(*Impl) if ok { - v.A() // ERROR "inlining call to \(\*Impl\).A$" - v.M() // ERROR "inlining call to \(\*Impl\).M$" + v.A() // ERROR "inlining call to \(\*Impl\).A" + v.M() // ERROR "inlining call to \(\*Impl\).M" } } { var a M = &Impl{} // ERROR "&Impl{} does not escape$" v, _ := a.(M) - v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M" } { var a M = &Impl{} // ERROR "&Impl{} does not escape$" v, _ := a.(A) - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } { var a M = &Impl{} // ERROR "&Impl{} does not escape$" v, _ := a.(*Impl) - v.A() // ERROR "inlining call to \(\*Impl\).A$" - v.M() // ERROR "inlining call to \(\*Impl\).M$" + v.A() // ERROR "inlining call to \(\*Impl\).A" + v.M() // ERROR "inlining call to \(\*Impl\).M" } { - a := newM() // ERROR "&Impl{} does not escape$" "inlining call to newM$" - callA(a) // ERROR "devirtualizing m.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A$" "inlining call to callA$" - callIfA(a) // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" "inlining call to callIfA$" + a := newM() // ERROR "&Impl{} does not escape$" "inlining call to newM" + callA(a) // ERROR "devirtualizing m.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A" "inlining call to callA" + callIfA(a) // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" "inlining call to callIfA" } { var a M = &Impl{} // ERROR "&Impl{} does not escape$" // Note the !ok condition, devirtualizing here is fine. if v, ok := a.(M); !ok { - v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M" } } { var a A = newImplNoInline() if v, ok := a.(M); ok { - v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M" } } { @@ -150,7 +150,7 @@ func typeAssertsWithOkReturn() { var a A a, _ = impl2InA.(*Impl) // a now contains the zero value of *Impl - a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" } { a := newANoInline() @@ -206,22 +206,22 @@ func testTypeSwitch() { var v A = &Impl{} // ERROR "&Impl{} does not escape$" switch v := v.(type) { case A: - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" case M: - v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M" } } { var v A = &Impl{} // ERROR "&Impl{} does not escape$" switch v := v.(type) { case A: - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" case M: - v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M" v = &Impl{} // ERROR "&Impl{} does not escape$" - v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M" } - v.(M).M() // ERROR "devirtualizing v.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" + v.(M).M() // ERROR "devirtualizing v.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M" } { var v A = &Impl{} // ERROR "&Impl{} escapes to heap$" @@ -237,9 +237,9 @@ func testTypeSwitch() { var v A = &Impl{} // ERROR "&Impl{} escapes to heap$" switch v := v.(type) { case A: - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" case M: - v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M" case C: v.C() } @@ -248,7 +248,7 @@ func testTypeSwitch() { var v A = &Impl{} // ERROR "&Impl{} does not escape$" switch v := v.(type) { case M: - v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M" default: panic("does not implement M") // ERROR ".does not implement M. escapes to heap$" } @@ -289,7 +289,7 @@ func differentTypeAssign() { var asAny any = a asAny.(A).A() asAny = &Impl2{} // ERROR "&Impl2{} escapes to heap$" - a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" } { var a A @@ -310,15 +310,15 @@ func assignWithTypeAssert() { var i1 A = &Impl{} // ERROR "&Impl{} does not escape$" var i2 A = &Impl2{} // ERROR "&Impl2{} does not escape$" i1 = i2.(*Impl) // this will panic - i1.A() // ERROR "devirtualizing i1.A to \*Impl$" "inlining call to \(\*Impl\).A$" - i2.A() // ERROR "devirtualizing i2.A to \*Impl2$" "inlining call to \(\*Impl2\).A$" + i1.A() // ERROR "devirtualizing i1.A to \*Impl$" "inlining call to \(\*Impl\).A" + i2.A() // ERROR "devirtualizing i2.A to \*Impl2$" "inlining call to \(\*Impl2\).A" } { var i1 A = &Impl{} // ERROR "&Impl{} does not escape$" var i2 A = &Impl2{} // ERROR "&Impl2{} does not escape$" i1, _ = i2.(*Impl) // i1 is going to be nil - i1.A() // ERROR "devirtualizing i1.A to \*Impl$" "inlining call to \(\*Impl\).A$" - i2.A() // ERROR "devirtualizing i2.A to \*Impl2$" "inlining call to \(\*Impl2\).A$" + i1.A() // ERROR "devirtualizing i1.A to \*Impl$" "inlining call to \(\*Impl\).A" + i2.A() // ERROR "devirtualizing i2.A to \*Impl2$" "inlining call to \(\*Impl2\).A" } } @@ -326,34 +326,34 @@ func nilIface() { { var v A = &Impl{} // ERROR "&Impl{} does not escape$" v = nil - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } { var v A = &Impl{} // ERROR "&Impl{} does not escape$" - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" v = nil } { var nilIface A var v A = &Impl{} // ERROR "&Impl{} does not escape$" - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" v = nilIface } { var nilIface A var v A = &Impl{} // ERROR "&Impl{} does not escape$" v = nilIface - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } { var v A - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" v = &Impl{} // ERROR "&Impl{} does not escape$" } { var v A var v2 A = v - v2.A() // ERROR "devirtualizing v2.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v2.A() // ERROR "devirtualizing v2.A to \*Impl$" "inlining call to \(\*Impl\).A" v2 = &Impl{} // ERROR "&Impl{} does not escape$" } { @@ -381,52 +381,52 @@ func longDevirtTest() { { var b A = a - b.A() // ERROR "devirtualizing b.A to \*Impl$" "inlining call to \(\*Impl\).A$" - b.(M).M() // ERROR "devirtualizing b.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" + b.A() // ERROR "devirtualizing b.A to \*Impl$" "inlining call to \(\*Impl\).A" + b.(M).M() // ERROR "devirtualizing b.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M" } { var b M = a - b.M() // ERROR "devirtualizing b.M to \*Impl$" "inlining call to \(\*Impl\).M$" - b.(A).A() // ERROR "devirtualizing b.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A$" + b.M() // ERROR "devirtualizing b.M to \*Impl$" "inlining call to \(\*Impl\).M" + b.(A).A() // ERROR "devirtualizing b.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A" } { var b A = a.(M).(A) - b.A() // ERROR "devirtualizing b.A to \*Impl$" "inlining call to \(\*Impl\).A$" - b.(M).M() // ERROR "devirtualizing b.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" + b.A() // ERROR "devirtualizing b.A to \*Impl$" "inlining call to \(\*Impl\).A" + b.(M).M() // ERROR "devirtualizing b.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M" } { var b M = a.(A).(M) - b.M() // ERROR "devirtualizing b.M to \*Impl$" "inlining call to \(\*Impl\).M$" - b.(A).A() // ERROR "devirtualizing b.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A$" + b.M() // ERROR "devirtualizing b.M to \*Impl$" "inlining call to \(\*Impl\).M" + b.(A).A() // ERROR "devirtualizing b.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A" } if v, ok := a.(A); ok { - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } if v, ok := a.(M); ok { - v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M" } { var c A = a if v, ok := c.(A); ok { - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } c = &Impl{} // ERROR "&Impl{} does not escape$" if v, ok := c.(M); ok { - v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M" } if v, ok := c.(interface { A M }); ok { - v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M$" - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.M() // ERROR "devirtualizing v.M to \*Impl$" "inlining call to \(\*Impl\).M" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } } } @@ -437,7 +437,7 @@ func deferDevirt() { a = &Impl{} // ERROR "&Impl{} escapes to heap$" }() a = &Impl{} // ERROR "&Impl{} does not escape$" - a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" } func deferNoDevirt() { @@ -458,7 +458,7 @@ func closureDevirt() { a = &Impl{} // ERROR "&Impl{} escapes to heap$" }() a = &Impl{} // ERROR "&Impl{} does not escape$" - a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" } //go:noinline @@ -486,7 +486,7 @@ func closureDevirt2() { a = &Impl{} // ERROR "&Impl{} escapes to heap$" } } - a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" c() } @@ -512,14 +512,14 @@ func varDeclaredInClosureReferencesOuter() { // defer for noinline defer func() {}() // ERROR "can inline varDeclaredInClosureReferencesOuter.func1.1$" "func literal does not escape$" var v A = a - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" }() func() { // ERROR "func literal does not escape$" // defer for noinline defer func() {}() // ERROR "can inline varDeclaredInClosureReferencesOuter.func2.1$" "func literal does not escape$" var v A = a v = &Impl{} // ERROR "&Impl{} does not escape$" - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" }() var b A = &Impl{} // ERROR "&Impl{} escapes to heap$" @@ -587,24 +587,24 @@ func globals() { { var a A = &Impl{} // ERROR "&Impl{} does not escape$" a = globalImpl - a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" } { var a A = &Impl{} // ERROR "&Impl{} does not escape$" a = A(globalImpl) - a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" } { var a A = &Impl{} // ERROR "&Impl{} does not escape$" a = M(globalImpl).(A) - a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" } { var a A = &Impl{} // ERROR "&Impl{} does not escape$" a = globalA.(*Impl) - a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" a = globalM.(*Impl) - a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" } { var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" @@ -627,23 +627,23 @@ func mapsDevirt() { { m := make(map[int]*Impl) // ERROR "make\(map\[int\]\*Impl\) does not escape$" var v A = m[0] - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" - v.(M).M() // ERROR "devirtualizing v.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" + v.(M).M() // ERROR "devirtualizing v.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M" } { m := make(map[int]*Impl) // ERROR "make\(map\[int\]\*Impl\) does not escape$" var v A var ok bool if v, ok = m[0]; ok { - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } { m := make(map[int]*Impl) // ERROR "make\(map\[int\]\*Impl\) does not escape$" var v A v, _ = m[0] - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } } @@ -700,43 +700,43 @@ func chanDevirt() { { m := make(chan *Impl) var v A = <-m - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } { m := make(chan *Impl) var v A v = <-m - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } { m := make(chan *Impl) var v A v, _ = <-m - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } { m := make(chan *Impl) var v A var ok bool if v, ok = <-m; ok { - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } { m := make(chan *Impl) var v A var ok bool if v, ok = <-m; ok { - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } select { case <-m: - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" case v = <-m: - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" case v, ok = <-m: - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } } } @@ -846,7 +846,7 @@ func rangeDevirt() { v = &Impl{} // ERROR "&Impl{} does not escape$" for v = range m { } - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } { var v A @@ -854,7 +854,7 @@ func rangeDevirt() { v = &Impl{} // ERROR "&Impl{} does not escape$" for v = range m { } - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } { var v A @@ -862,7 +862,7 @@ func rangeDevirt() { v = &Impl{} // ERROR "&Impl{} does not escape$" for _, v = range m { } - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } { var v A @@ -870,7 +870,7 @@ func rangeDevirt() { v = &Impl{} // ERROR "&Impl{} does not escape$" for v = range m { } - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } { var v A @@ -878,7 +878,7 @@ func rangeDevirt() { v = &Impl{} // ERROR "&Impl{} does not escape$" for _, v = range m { } - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } { var v A @@ -887,7 +887,7 @@ func rangeDevirt() { i := 0 for v = impl; i < 10; i++ { } - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } { var v A @@ -896,7 +896,7 @@ func rangeDevirt() { i := 0 for v = impl; i < 10; i++ { } - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } { var v A @@ -904,7 +904,7 @@ func rangeDevirt() { v = &Impl{} // ERROR "&Impl{} does not escape$" for _, v = range m { } - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } { var v A @@ -912,7 +912,7 @@ func rangeDevirt() { v = &Impl{} // ERROR "&Impl{} does not escape$" for _, v = range &m { } - v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A$" + v.A() // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" } } @@ -1048,10 +1048,10 @@ func testIfInit() { var a A = &Impl{} // ERROR "&Impl{} does not escape$" var i = &Impl{} // ERROR "&Impl{} does not escape$" if a = i; globalInt == 1 { - a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" } - a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" - a.(M).M() // ERROR "devirtualizing a.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" + a.(M).M() // ERROR "devirtualizing a.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M" } { var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" @@ -1069,10 +1069,10 @@ func testSwitchInit() { var i = &Impl{} // ERROR "&Impl{} does not escape$" switch a = i; globalInt { case 12: - a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" } - a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A$" - a.(M).M() // ERROR "devirtualizing a.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M$" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" + a.(M).M() // ERROR "devirtualizing a.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M" } { var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" @@ -1095,13 +1095,13 @@ func devirtWrapperType() { i := &Impl{} // ERROR "&Impl{} does not escape$" // This is an OCONVNOP, so we have to be carefull, not to devirtualize it to Impl.A. var a A = (*implWrapper)(i) - a.A() // ERROR "devirtualizing a.A to \*implWrapper$" "inlining call to implWrapper.A$" + a.A() // ERROR "devirtualizing a.A to \*implWrapper$" "inlining call to implWrapper.A" } { i := Impl{} // This is an OCONVNOP, so we have to be carefull, not to devirtualize it to Impl.A. var a A = (implWrapper)(i) // ERROR "implWrapper\(i\) does not escape$" - a.A() // ERROR "devirtualizing a.A to implWrapper$" "inlining call to implWrapper.A$" + a.A() // ERROR "devirtualizing a.A to implWrapper$" "inlining call to implWrapper.A" } } @@ -1209,9 +1209,9 @@ func testInvalidAsserts() { var a C = &CImpl{} // ERROR "&CImpl{} does not escape$" // this will panic - a.(M).(*Impl).M() // ERROR "inlining call to \(\*Impl\).M$" + a.(M).(*Impl).M() // ERROR "inlining call to \(\*Impl\).M" // this will panic - a.(any).(M).(*Impl).M() // ERROR "inlining call to \(\*Impl\).M$" + a.(any).(M).(*Impl).M() // ERROR "inlining call to \(\*Impl\).M" } } diff --git a/test/devirtualization_with_type_assertions_interleaved.go b/test/devirtualization_with_type_assertions_interleaved.go index e25e9c29aa586f..9cf7d995845936 100644 --- a/test/devirtualization_with_type_assertions_interleaved.go +++ b/test/devirtualization_with_type_assertions_interleaved.go @@ -84,24 +84,24 @@ func cloneHashWithBool3(h hashIface) (hashIface, bool) { // ERROR "can inline cl } func interleavedWithTypeAssertions() { - h1 := newHash() // ERROR "&hash{} does not escape$" "inlining call to newHash$" - _ = h1.Sum() // ERROR "devirtualizing h1.Sum to \*hash$" "inlining call to \(\*hash\).Sum$" "make\(\[\]byte, 32\) does not escape$" + h1 := newHash() // ERROR "&hash{} does not escape$" "inlining call to newHash" + _ = h1.Sum() // ERROR "devirtualizing h1.Sum to \*hash$" "inlining call to \(\*hash\).Sum" "make\(\[\]byte, 32\) does not escape$" - h2 := cloneHash1(h1) // ERROR "&hash{} does not escape$" "devirtualizing h.Clone to \*hash$" "inlining call to \(\*hash\).Clone$" "inlining call to cloneHash1$" - _ = h2.Sum() // ERROR "devirtualizing h2.Sum to \*hash$" "inlining call to \(\*hash\).Sum$" "make\(\[\]byte, 32\) does not escape$" + h2 := cloneHash1(h1) // ERROR "&hash{} does not escape$" "devirtualizing h.Clone to \*hash$" "inlining call to \(\*hash\).Clone" "inlining call to cloneHash1" + _ = h2.Sum() // ERROR "devirtualizing h2.Sum to \*hash$" "inlining call to \(\*hash\).Sum" "make\(\[\]byte, 32\) does not escape$" - h3 := cloneHash2(h1) // ERROR "devirtualizing h.Clone to \*hash$" "inlining call to \(\*hash\).Clone$" "inlining call to cloneHash2$" - _ = h3.Sum() // ERROR "devirtualizing h3.Sum to \*hash$" "inlining call to \(\*hash\).Sum$" "make\(\[\]byte, 32\) does not escape$" + h3 := cloneHash2(h1) // ERROR "devirtualizing h.Clone to \*hash$" "inlining call to \(\*hash\).Clone" "inlining call to cloneHash2" + _ = h3.Sum() // ERROR "devirtualizing h3.Sum to \*hash$" "inlining call to \(\*hash\).Sum" "make\(\[\]byte, 32\) does not escape$" - h4 := cloneHash3(h1) // ERROR "&hash2{} escapes to heap$" "devirtualizing h.Clone to \*hash$" "inlining call to \(\*hash\).Clone$" "inlining call to cloneHash3$" "moved to heap: c$" + h4 := cloneHash3(h1) // ERROR "&hash2{} escapes to heap$" "devirtualizing h.Clone to \*hash$" "inlining call to \(\*hash\).Clone" "inlining call to cloneHash3" "moved to heap: c$" _ = h4.Sum() - h5, _ := cloneHashWithBool1(h1) // ERROR "&hash{} does not escape$" "devirtualizing h.Clone to \*hash$" "inlining call to \(\*hash\).Clone$" "inlining call to cloneHashWithBool1$" - _ = h5.Sum() // ERROR "devirtualizing h5.Sum to \*hash$" "inlining call to \(\*hash\).Sum$" "make\(\[\]byte, 32\) does not escape$" + h5, _ := cloneHashWithBool1(h1) // ERROR "&hash{} does not escape$" "devirtualizing h.Clone to \*hash$" "inlining call to \(\*hash\).Clone" "inlining call to cloneHashWithBool1" + _ = h5.Sum() // ERROR "devirtualizing h5.Sum to \*hash$" "inlining call to \(\*hash\).Sum" "make\(\[\]byte, 32\) does not escape$" - h6, _ := cloneHashWithBool2(h1) // ERROR "devirtualizing h.Clone to \*hash$" "inlining call to \(\*hash\).Clone$" "inlining call to cloneHashWithBool2$" - _ = h6.Sum() // ERROR "devirtualizing h6.Sum to \*hash$" "inlining call to \(\*hash\).Sum$" "make\(\[\]byte, 32\) does not escape$" + h6, _ := cloneHashWithBool2(h1) // ERROR "devirtualizing h.Clone to \*hash$" "inlining call to \(\*hash\).Clone" "inlining call to cloneHashWithBool2" + _ = h6.Sum() // ERROR "devirtualizing h6.Sum to \*hash$" "inlining call to \(\*hash\).Sum" "make\(\[\]byte, 32\) does not escape$" - h7, _ := cloneHashWithBool3(h1) // ERROR "&hash2{} escapes to heap$" "devirtualizing h.Clone to \*hash$" "inlining call to \(\*hash\).Clone$" "inlining call to cloneHashWithBool3$" "moved to heap: c$" + h7, _ := cloneHashWithBool3(h1) // ERROR "&hash2{} escapes to heap$" "devirtualizing h.Clone to \*hash$" "inlining call to \(\*hash\).Clone" "inlining call to cloneHashWithBool3" "moved to heap: c$" _ = h7.Sum() } From 99232355b0adb2ec005582d528cae07d50157716 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Wed, 26 Feb 2025 21:42:12 +0100 Subject: [PATCH 53/74] update pprof test Change-Id: I6bc6a7a0af24fffc5466613e18e58c1ceef2cba6 --- src/runtime/pprof/pprof_test.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/runtime/pprof/pprof_test.go b/src/runtime/pprof/pprof_test.go index a6a9fa27cd74cb..276bb901a6f9c7 100644 --- a/src/runtime/pprof/pprof_test.go +++ b/src/runtime/pprof/pprof_test.go @@ -342,15 +342,17 @@ type inlineWrapperInterface interface { type inlineWrapper struct { } -// TODO: test fails if this method inlines. -// -//go:noinline func (h inlineWrapper) dump(pcs []uintptr) { dumpCallers(pcs) } func inlinedWrapperCallerDump(pcs []uintptr) { var h inlineWrapperInterface + + // Take the address of h, such that h.dump() call (below) + // does not get devirtualized by the compiler. + _ = &h + h = &inlineWrapper{} h.dump(pcs) } From 996d6e65c1b123828b6c214b1e0b1c4bf3a8c4fa Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Thu, 27 Feb 2025 10:31:02 +0100 Subject: [PATCH 54/74] few review comments resolved Change-Id: If0a938489a8a0e78b27372c1d95a9effc77411e2 --- .../internal/devirtualize/devirtualize.go | 35 ++++++++++++------- src/cmd/compile/internal/ir/expr.go | 6 ++-- src/cmd/compile/internal/ssagen/ssa.go | 9 +++-- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 5f042a6013e47e..03fb87069fe2ec 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -126,15 +126,15 @@ func StaticCall(call *ir.CallExpr) { // v.A() // v = &Impl{} // - // Here in the devirtualizer, we determine the concrete type of v as beeing an *Impl, - // but in can still be a nil interface, we have not detected that. The v.(*Impl) + // Here in the devirtualizer, we determine the concrete type of v as being an *Impl, + // but it can still be a nil interface, we have not detected that. The v.(*Impl) // type assertion that we make here would also have failed, but with a different // panic "pkg.Iface is nil, not *pkg.Impl", where previously we would get a nil panic. // We fix this, by introducing an additional nilcheck on the itab. // Calling a method on an nil interface (in most cases) is a bug in a program, so it is fine // to devirtualize and further (possibly) inline them, even though we would never reach // the called function. - dt.EmitItabNilCheck = true + dt.UseNilPanic = true dt.SetPos(call.Pos()) } @@ -207,7 +207,15 @@ func concreteType(n ir.Node) (typ *types.Type) { return typ } -func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignments func(*ir.Name) []valOrTyp) (out *types.Type, isNil bool) { +// concreteType1 analyzes the node n and returns its concrete type if it is statically known. +// Otherwise, it returns a nil Type, indicating that a concrete type was not determined. +// This can happen in cases where n is assigned an interface type and the concrete type +// is not statically known (e.g., a non-inlined function call returning an interface type) +// or when multiple distinct concrete types are assigned. +// +// If n is statically known to be nil, this function returns a nil Type with isNil == true. +// However, if any concrete type is found, it is returned instead, even if n was assigned with nil. +func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignments func(*ir.Name) []valOrTyp) (t *types.Type, isNil bool) { for { if !n.Type().IsInterface() { return n.Type(), false @@ -279,7 +287,7 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignments assignments := getAssignments(name) if len(assignments) == 0 { // Variable either declared with zero value, or only assigned - // with nil (getAssignements does not return such assignments). + // with nil (getAssignments does not return such assignments). return nil, true } @@ -323,8 +331,9 @@ type valOrTyp struct { node ir.Node } -// ifaceAssignments returns a map containg every assignement to variables -// declared in the provieded func (and in closures) that are of interface types. +// ifaceAssignments returns a map containg every assignment to variables +// declared in the provided func (and in closures) that are of interface type. +// It does not collect nil assignments (and thus zero val decls). func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { out := make(map[*ir.Name][]valOrTyp) @@ -338,17 +347,17 @@ func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { return } - n = n.Canonical() - if n.Op() != ir.ONAME { - base.Fatalf("reassigned %v", n) - } - // Do not track variables that are not of interface types. // For devirtualization they are unnecessary, we will not even look them up. if !n.Type().IsInterface() { return } + n = n.Canonical() + if n.Op() != ir.ONAME { + base.Fatalf("reassigned %v", n) + } + // n is assigned with nil, we can safely ignore them, see [StaticCall]. if ir.IsNil(value.node) { return @@ -434,6 +443,8 @@ func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { assign(n.Key, valOrTyp{}) assign(n.Value, valOrTyp{}) } else { + // We will not reach here in case of an range-over-func, as it is + // rewrtten to function calls in the noder package. base.Fatalf("range over unexpected type %v", n.X.Type()) } case ir.OSWITCH: diff --git a/src/cmd/compile/internal/ir/expr.go b/src/cmd/compile/internal/ir/expr.go index 3bd7c49a888204..63cce4e2f0cd9f 100644 --- a/src/cmd/compile/internal/ir/expr.go +++ b/src/cmd/compile/internal/ir/expr.go @@ -682,10 +682,10 @@ type TypeAssertExpr struct { // An internal/abi.TypeAssert descriptor to pass to the runtime. Descriptor *obj.LSym - // When set to true, then the ssagen package will emit a nilcheck on the itab, that - // will lead to a nil check panic in case the itab is nil at runtime. + // When set to true, if this assert would panic, then use a nil pointer panic + // instead of an interface conversion panic. // It must not be set for type asserts using the commaok form. - EmitItabNilCheck bool + UseNilPanic bool } func NewTypeAssertExpr(pos src.XPos, x Node, typ *types.Type) *TypeAssertExpr { diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index 16688b574ade73..8f9b83e93af7c0 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -5643,9 +5643,14 @@ func (s *state) dottype(n *ir.TypeAssertExpr, commaok bool) (res, resok *ssa.Val targetItab = s.expr(n.ITab) } - if n.EmitItabNilCheck { + if n.UseNilPanic { if commaok { - base.Fatalf("unexpected *ir.TypeAssertExpr with EmitItabNilCheck == true && commaok == true") + base.Fatalf("unexpected *ir.TypeAssertExpr with UseNilPanic == true && commaok == true") + } + if n.Type().IsInterface() { + // Currently we do not expect the compiler to emit type asserts with UseNilPanic, that assert to an interface type. + // If needed, this can be relaxed in the future, but for now we can assert that. + base.Fatalf("unexpected *ir.TypeAssertExpr with UseNilPanic == true && Type().IsInterface() == true") } typs := s.f.Config.Types iface = s.newValue2( From 29f5308761ad542152ddcfb3a026773c6a411365 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Thu, 27 Feb 2025 19:53:26 +0100 Subject: [PATCH 55/74] update Change-Id: I9369a86a3c51eff30283a608f6e4b6664c997f5f --- .../internal/devirtualize/devirtualize.go | 222 ++++++++++++++---- .../inline/interleaved/interleaved.go | 34 +-- src/cmd/compile/internal/ir/expr.go | 11 +- 3 files changed, 193 insertions(+), 74 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 03fb87069fe2ec..53ddc5a0882d56 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -22,7 +22,7 @@ const go125ImprovedConcreteTypeAnalysis = true // StaticCall devirtualizes the given call if possible when the concrete callee // is available statically. -func StaticCall(call *ir.CallExpr) { +func StaticCall(s *State, call *ir.CallExpr) { // For promoted methods (including value-receiver methods promoted // to pointer-receivers), the interface method wrapper may contain // expressions that can panic (e.g., ODEREF, ODOTPTR, @@ -44,7 +44,7 @@ func StaticCall(call *ir.CallExpr) { sel := call.Fun.(*ir.SelectorExpr) var typ *types.Type if go125ImprovedConcreteTypeAnalysis { - typ = concreteType(sel.X) + typ = concreteType(s, sel.X) if typ == nil { return } @@ -176,27 +176,43 @@ func StaticCall(call *ir.CallExpr) { typecheck.FixMethodCall(call) } +const concreteTypeDebug = false + // concreteType determines the concrete type of n, following OCONVIFACEs and type asserts. // Returns nil when the concrete type could not be determined, or when there are multiple // (different) types assigned to an interface. -func concreteType(n ir.Node) (typ *types.Type) { - var assignments map[*ir.Name][]valOrTyp +func concreteType(s *State, n ir.Node) (typ *types.Type) { typ, isNil := concreteType1(n, make(map[*ir.Name]*types.Type), func(n *ir.Name) []valOrTyp { - if assignments == nil { - assignments = make(map[*ir.Name][]valOrTyp) - if n.Curfn == nil { - base.Fatalf("n.Curfn == nil: %v", n) - } - fun := n.Curfn - for fun.ClosureParent != nil { - fun = fun.ClosureParent - } - assignments = ifaceAssignments(fun) + if n.Curfn == nil { + base.Fatalf("n.Curfn == nil: %v", n) } if !n.Type().IsInterface() { base.Fatalf("name passed to getAssignments is not of an interface type: %v", n.Type()) } - return assignments[n] + + fun := n.Curfn + for fun.ClosureParent != nil { + fun = fun.ClosureParent + } + + if s.ifaceAssignments == nil { + s.ifaceAssignments = make(map[*ir.Name][]valOrTyp) + s.ifaceCallExprAssigns = make(map[*ir.CallExpr][]ifaceAssignRef) + + if concreteTypeDebug { + base.Warn("concreteType(): analyzing assignments in %v func", fun) + } + + s.fun = fun + s.analyze(fun.Init()) + s.analyze(fun.Body) + } + + if s.fun != fun { + base.Fatalf("unexpected func = %v; want = %v", fun, s.fun) + } + + return s.ifaceAssignments[n] }) if isNil && typ != nil { base.Fatalf("typ = %v; want = ", typ) @@ -209,14 +225,26 @@ func concreteType(n ir.Node) (typ *types.Type) { // concreteType1 analyzes the node n and returns its concrete type if it is statically known. // Otherwise, it returns a nil Type, indicating that a concrete type was not determined. -// This can happen in cases where n is assigned an interface type and the concrete type -// is not statically known (e.g., a non-inlined function call returning an interface type) +// This can happen in cases where n is assigned an interface type and the concrete type of that +// interface is not statically known (e.g. a non-inlined function call returning an interface type) // or when multiple distinct concrete types are assigned. // // If n is statically known to be nil, this function returns a nil Type with isNil == true. // However, if any concrete type is found, it is returned instead, even if n was assigned with nil. func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignments func(*ir.Name) []valOrTyp) (t *types.Type, isNil bool) { + nn := n // for debug messages + + if concreteTypeDebug { + defer func() { + base.Warn("concreteType1(%v) -> (%v;%v)", nn, t, isNil) + }() + } + for { + if concreteTypeDebug { + base.Warn("concreteType1(%v): analyzing %v", nn, n) + } + if !n.Type().IsInterface() { return n.Type(), false } @@ -248,7 +276,6 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignments n = n1.X continue } - break } @@ -284,6 +311,10 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignments // executes) will get a nil (from the map lookup above), where we could determine the type. analyzed[name] = nil + if concreteTypeDebug { + base.Warn("concreteType1(%v): analyzing assignments to %v", nn, name) + } + assignments := getAssignments(name) if len(assignments) == 0 { // Variable either declared with zero value, or only assigned @@ -322,35 +353,95 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignments return typ, false } -// valOrTyp stores a node or a type that is assigned to a variable. -// Never both of these fields are populated. If both are nil, then -// either an interface type was assigned or a basic type (i.e. int), which -// we know that does not have any methods, thus not possible to devirtualize. +// valOrTyp stores either node or a type that is assigned to a variable. +// Never both of these fields are populated. +// If both are nil, then either an interface type was assigned (e.g. a non-inlined +// function call returning an interface type, in such case we don't know the +// concrete type) or a basic type (i.e. int), which we know that does not have any +// methods, thus not possible to devirtualize. type valOrTyp struct { typ *types.Type node ir.Node } -// ifaceAssignments returns a map containg every assignment to variables -// declared in the provided func (and in closures) that are of interface type. -// It does not collect nil assignments (and thus zero val decls). -func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { - out := make(map[*ir.Name][]valOrTyp) +// State holds precomputed state for use in (and filled by the first call to) [StaticCall]. +type State struct { + // ifaceAssignments stores all assignments to all interface variables, found in a func. + ifaceAssignments map[*ir.Name][]valOrTyp - assign := func(name ir.Node, value valOrTyp) { - if name == nil || name.Op() != ir.ONAME { - return + // ifaceCallExprAssigns stores every [*ir.CallExpr] found in a func, which has + // an interface result, that is assigned to a variable. + ifaceCallExprAssigns map[*ir.CallExpr][]ifaceAssignRef + + fun *ir.Func +} + +type ifaceAssignRef struct { + name *ir.Name // ifaceAssignments[name] + valOrTypeIndex int // ifaceAssignments[name][valOrTypeIndex] + returnIndex int // (*ir.CallExpr).Result(returnIndex) +} + +// InlinedCall updates the [State] to take into account a newly inlined call. +func (s *State) InlinedCall(origCall *ir.CallExpr, newInlinedCall *ir.InlinedCallExpr) { + if s.ifaceAssignments == nil { + // Full analyze has not been yet executed, so we can skip it for now. + // When no devirtualization happens in a function, it is unecessary to analyze it. + return + } + + // Analyze assignments in the newly inlined function. + s.analyze(newInlinedCall.Init()) + s.analyze(newInlinedCall.Body) + + v, ok := s.ifaceCallExprAssigns[origCall] + if !ok { + return + } + delete(s.ifaceCallExprAssigns, origCall) + + // Update assignments to reference the new ReturnVars of the inlined call. + for _, ni := range v { + vt := &s.ifaceAssignments[ni.name][ni.valOrTypeIndex] + if vt.node != nil || vt.typ != nil { + base.Fatalf("unexpected non-empty valOrTyp") + } + if concreteTypeDebug { + base.Warn( + "Invalidate(%v, %v): replacing interface node in (%v,%v) to %v (typ %v)", + origCall, newInlinedCall, ni.name, ni.valOrTypeIndex, + newInlinedCall.ReturnVars[ni.returnIndex], + newInlinedCall.ReturnVars[ni.returnIndex].Type(), + ) + } + *vt = valOrTyp{node: newInlinedCall.ReturnVars[ni.returnIndex]} + } +} + +// analyze analyzes every assignment to interface variables in nodes, updating [State]. +func (s *State) analyze(nodes ir.Nodes) { + assign := func(name ir.Node, value valOrTyp) (*ir.Name, int) { + if name == nil || name.Op() != ir.ONAME || ir.IsBlank(name) { + return nil, -1 } n, ok := ir.OuterValue(name).(*ir.Name) - if !ok { - return + if !ok || n.Curfn == nil { + return nil, -1 + } + + fun := n.Curfn + for fun.ClosureParent != nil { + fun = fun.ClosureParent + } + if s.fun != fun { + base.Fatalf("unexpected func = %v; want = %v", fun, s.fun) } // Do not track variables that are not of interface types. // For devirtualization they are unnecessary, we will not even look them up. if !n.Type().IsInterface() { - return + return nil, -1 } n = n.Canonical() @@ -360,14 +451,19 @@ func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { // n is assigned with nil, we can safely ignore them, see [StaticCall]. if ir.IsNil(value.node) { - return + return nil, -1 } if value.typ != nil && value.typ.IsInterface() { value.typ = nil } - out[n] = append(out[n], value) + if concreteTypeDebug { + base.Warn("populateIfaceAssignments(): assignment found %v = (%v;%v)", name, value.typ, value.node) + } + + s.ifaceAssignments[n] = append(s.ifaceAssignments[n], value) + return n, len(s.ifaceAssignments[n]) - 1 } var do func(n ir.Node) @@ -376,7 +472,23 @@ func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { case ir.OAS: n := n.(*ir.AssignStmt) if n.Y != nil { - assign(n.X, valOrTyp{node: n.Y}) + rhs := n.Y + for { + if r, ok := rhs.(*ir.ParenExpr); ok { + rhs = r.X + continue + } + break + } + if call, ok := rhs.(*ir.CallExpr); ok && call.Fun != nil { + retTyp := call.Fun.Type().Results()[0].Type + n, idx := assign(n.X, valOrTyp{typ: retTyp}) + if n != nil && retTyp.IsInterface() { + s.ifaceCallExprAssigns[call] = append(s.ifaceCallExprAssigns[call], ifaceAssignRef{n, idx, 0}) + } + } else { + assign(n.X, valOrTyp{node: rhs}) + } } case ir.OAS2: n := n.(*ir.AssignListStmt) @@ -401,22 +513,29 @@ func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { assign(n.Lhs[1], valOrTyp{}) // boolean does not have methods to devirtualize case ir.OAS2FUNC: n := n.(*ir.AssignListStmt) - for i, p := range n.Lhs { - rhs := n.Rhs[0] - for { - if r, ok := rhs.(*ir.ParenExpr); ok { - rhs = r.X - continue - } - break + rhs := n.Rhs[0] + for { + if r, ok := rhs.(*ir.ParenExpr); ok { + rhs = r.X + continue } - if call, ok := rhs.(*ir.CallExpr); ok { + break + } + if call, ok := rhs.(*ir.CallExpr); ok { + for i, p := range n.Lhs { retTyp := call.Fun.Type().Results()[i].Type - assign(p, valOrTyp{typ: retTyp}) - } else if call, ok := rhs.(*ir.InlinedCallExpr); ok { - assign(p, valOrTyp{node: call.Result(i)}) - } else { - // TODO: can we reach here? + n, idx := assign(p, valOrTyp{typ: retTyp}) + if n != nil && retTyp.IsInterface() { + s.ifaceCallExprAssigns[call] = append(s.ifaceCallExprAssigns[call], ifaceAssignRef{n, idx, i}) + } + } + } else if call, ok := rhs.(*ir.InlinedCallExpr); ok { + for i, p := range n.Lhs { + assign(p, valOrTyp{node: call.ReturnVars[i]}) + } + } else { + // TODO: can we reach here? + for _, p := range n.Lhs { assign(p, valOrTyp{}) } } @@ -462,6 +581,5 @@ func ifaceAssignments(fun *ir.Func) map[*ir.Name][]valOrTyp { ir.Visit(n.(*ir.ClosureExpr).Func, do) } } - ir.Visit(fun, do) - return out + ir.VisitList(nodes, do) } diff --git a/src/cmd/compile/internal/inline/interleaved/interleaved.go b/src/cmd/compile/internal/inline/interleaved/interleaved.go index a35121517ac001..3f7b42c596b06c 100644 --- a/src/cmd/compile/internal/inline/interleaved/interleaved.go +++ b/src/cmd/compile/internal/inline/interleaved/interleaved.go @@ -58,7 +58,7 @@ func DevirtualizeAndInlinePackage(pkg *ir.Package, profile *pgoir.Profile) { // Do a first pass at counting call sites. for i := range s.parens { - s.resolve(i) + s.resolve(&s.devirtState, i) } } @@ -102,10 +102,11 @@ func DevirtualizeAndInlinePackage(pkg *ir.Package, profile *pgoir.Profile) { for { for i := l0; i < l1; i++ { // can't use "range parens" here paren := s.parens[i] - if new := s.edit(i); new != nil { + if origCall, newInlinedCall := s.edit(&s.devirtState, i); newInlinedCall != nil { // Update AST and recursively mark nodes. - paren.X = new - ir.EditChildren(new, s.mark) // mark may append to parens + paren.X = newInlinedCall + ir.EditChildren(newInlinedCall, s.mark) // mark may append to parens + s.devirtState.InlinedCall(origCall, newInlinedCall) done = false } } @@ -113,8 +114,9 @@ func DevirtualizeAndInlinePackage(pkg *ir.Package, profile *pgoir.Profile) { if l0 == l1 { break } + for i := l0; i < l1; i++ { - s.resolve(i) + s.resolve(&s.devirtState, i) } } @@ -176,7 +178,9 @@ type callSite struct { } type inlClosureState struct { - fn *ir.Func + fn *ir.Func + devirtState devirtualize.State + profile *pgoir.Profile callSites map[*ir.ParenExpr]bool // callSites[p] == "p appears in parens" (do not append again) resolved []*ir.Func // for each call in parens, the resolved target of the call @@ -188,7 +192,7 @@ type inlClosureState struct { // resolve attempts to resolve a call to a potentially inlineable callee // and updates use counts on the callees. Returns the call site count // for that callee. -func (s *inlClosureState) resolve(i int) (*ir.Func, int) { +func (s *inlClosureState) resolve(state *devirtualize.State, i int) (*ir.Func, int) { p := s.parens[i] if i < len(s.resolved) { if callee := s.resolved[i]; callee != nil { @@ -200,7 +204,7 @@ func (s *inlClosureState) resolve(i int) (*ir.Func, int) { if !ok { // previously inlined return nil, -1 } - devirtualize.StaticCall(call) + devirtualize.StaticCall(state, call) if callee := inline.InlineCallTarget(s.fn, call, s.profile); callee != nil { for len(s.resolved) <= i { s.resolved = append(s.resolved, nil) @@ -213,23 +217,23 @@ func (s *inlClosureState) resolve(i int) (*ir.Func, int) { return nil, 0 } -func (s *inlClosureState) edit(i int) ir.Node { +func (s *inlClosureState) edit(state *devirtualize.State, i int) (*ir.CallExpr, *ir.InlinedCallExpr) { n := s.parens[i].X call, ok := n.(*ir.CallExpr) if !ok { - return nil + return nil, nil } // This is redundant with earlier calls to // resolve, but because things can change it // must be re-checked. - callee, count := s.resolve(i) + callee, count := s.resolve(state, i) if count <= 0 { - return nil + return nil, nil } if inlCall := inline.TryInlineCall(s.fn, call, s.bigCaller, s.profile, count == 1 && callee.ClosureParent != nil); inlCall != nil { - return inlCall + return call, inlCall } - return nil + return nil, nil } // Mark inserts parentheses, and is called repeatedly. @@ -344,7 +348,7 @@ func (s *inlClosureState) fixpoint() bool { done = true for i := 0; i < len(s.parens); i++ { // can't use "range parens" here paren := s.parens[i] - if new := s.edit(i); new != nil { + if _, new := s.edit(new(devirtualize.State), i); new != nil { // Update AST and recursively mark nodes. paren.X = new ir.EditChildren(new, s.mark) // mark may append to parens diff --git a/src/cmd/compile/internal/ir/expr.go b/src/cmd/compile/internal/ir/expr.go index 63cce4e2f0cd9f..08e61e9c772675 100644 --- a/src/cmd/compile/internal/ir/expr.go +++ b/src/cmd/compile/internal/ir/expr.go @@ -386,19 +386,16 @@ func (n *InlinedCallExpr) SingleResult() Node { if have := len(n.ReturnVars); have != 1 { base.FatalfAt(n.Pos(), "inlined call has %v results, expected 1", have) } - return n.Result(0) -} - -func (n *InlinedCallExpr) Result(i int) Node { - if !n.Type().HasShape() && n.ReturnVars[i].Type().HasShape() { + // TODO: do we need to do that also? + if !n.Type().HasShape() && n.ReturnVars[0].Type().HasShape() { // If the type of the call is not a shape, but the type of the return value // is a shape, we need to do an implicit conversion, so the real type // of n is maintained. - r := NewConvExpr(n.Pos(), OCONVNOP, n.Type(), n.ReturnVars[i]) + r := NewConvExpr(n.Pos(), OCONVNOP, n.Type(), n.ReturnVars[0]) r.SetTypecheck(1) return r } - return n.ReturnVars[i] + return n.ReturnVars[0] } // A LogicalExpr is an expression X Op Y where Op is && or ||. From 053b51d076684d72b73a866ddf092677007c260d Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Thu, 27 Feb 2025 20:07:59 +0100 Subject: [PATCH 56/74] update Change-Id: Ie5faf154c7361c612f10ca9f7168555c5e958309 --- .../inline/interleaved/interleaved.go | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/cmd/compile/internal/inline/interleaved/interleaved.go b/src/cmd/compile/internal/inline/interleaved/interleaved.go index 3f7b42c596b06c..d7dd5750de2fad 100644 --- a/src/cmd/compile/internal/inline/interleaved/interleaved.go +++ b/src/cmd/compile/internal/inline/interleaved/interleaved.go @@ -58,7 +58,7 @@ func DevirtualizeAndInlinePackage(pkg *ir.Package, profile *pgoir.Profile) { // Do a first pass at counting call sites. for i := range s.parens { - s.resolve(&s.devirtState, i) + s.resolve(i) } } @@ -102,11 +102,11 @@ func DevirtualizeAndInlinePackage(pkg *ir.Package, profile *pgoir.Profile) { for { for i := l0; i < l1; i++ { // can't use "range parens" here paren := s.parens[i] - if origCall, newInlinedCall := s.edit(&s.devirtState, i); newInlinedCall != nil { + if origCall, inlinedCall := s.edit(i); inlinedCall != nil { // Update AST and recursively mark nodes. - paren.X = newInlinedCall - ir.EditChildren(newInlinedCall, s.mark) // mark may append to parens - s.devirtState.InlinedCall(origCall, newInlinedCall) + paren.X = inlinedCall + ir.EditChildren(inlinedCall, s.mark) // mark may append to parens + s.devirtState.InlinedCall(origCall, inlinedCall) done = false } } @@ -116,7 +116,7 @@ func DevirtualizeAndInlinePackage(pkg *ir.Package, profile *pgoir.Profile) { } for i := l0; i < l1; i++ { - s.resolve(&s.devirtState, i) + s.resolve(i) } } @@ -192,7 +192,7 @@ type inlClosureState struct { // resolve attempts to resolve a call to a potentially inlineable callee // and updates use counts on the callees. Returns the call site count // for that callee. -func (s *inlClosureState) resolve(state *devirtualize.State, i int) (*ir.Func, int) { +func (s *inlClosureState) resolve(i int) (*ir.Func, int) { p := s.parens[i] if i < len(s.resolved) { if callee := s.resolved[i]; callee != nil { @@ -204,7 +204,7 @@ func (s *inlClosureState) resolve(state *devirtualize.State, i int) (*ir.Func, i if !ok { // previously inlined return nil, -1 } - devirtualize.StaticCall(state, call) + devirtualize.StaticCall(&s.devirtState, call) if callee := inline.InlineCallTarget(s.fn, call, s.profile); callee != nil { for len(s.resolved) <= i { s.resolved = append(s.resolved, nil) @@ -217,7 +217,7 @@ func (s *inlClosureState) resolve(state *devirtualize.State, i int) (*ir.Func, i return nil, 0 } -func (s *inlClosureState) edit(state *devirtualize.State, i int) (*ir.CallExpr, *ir.InlinedCallExpr) { +func (s *inlClosureState) edit(i int) (*ir.CallExpr, *ir.InlinedCallExpr) { n := s.parens[i].X call, ok := n.(*ir.CallExpr) if !ok { @@ -226,7 +226,7 @@ func (s *inlClosureState) edit(state *devirtualize.State, i int) (*ir.CallExpr, // This is redundant with earlier calls to // resolve, but because things can change it // must be re-checked. - callee, count := s.resolve(state, i) + callee, count := s.resolve(i) if count <= 0 { return nil, nil } @@ -348,10 +348,11 @@ func (s *inlClosureState) fixpoint() bool { done = true for i := 0; i < len(s.parens); i++ { // can't use "range parens" here paren := s.parens[i] - if _, new := s.edit(new(devirtualize.State), i); new != nil { + if origCall, inlinedCall := s.edit(i); inlinedCall != nil { // Update AST and recursively mark nodes. - paren.X = new - ir.EditChildren(new, s.mark) // mark may append to parens + paren.X = inlinedCall + ir.EditChildren(inlinedCall, s.mark) // mark may append to parens + s.devirtState.InlinedCall(origCall, inlinedCall) done = false changed = true } From a445213c60794e3bfc802ca133f71a95fad04fb7 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Fri, 28 Feb 2025 09:09:33 +0100 Subject: [PATCH 57/74] typos, code move Change-Id: I63269d2d60de6ee9e66de3827d068270c3b344d5 --- src/cmd/compile/internal/devirtualize/devirtualize.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 53ddc5a0882d56..ca21400b7b0964 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -196,14 +196,12 @@ func concreteType(s *State, n ir.Node) (typ *types.Type) { } if s.ifaceAssignments == nil { - s.ifaceAssignments = make(map[*ir.Name][]valOrTyp) - s.ifaceCallExprAssigns = make(map[*ir.CallExpr][]ifaceAssignRef) - if concreteTypeDebug { base.Warn("concreteType(): analyzing assignments in %v func", fun) } - s.fun = fun + s.ifaceAssignments = make(map[*ir.Name][]valOrTyp) + s.ifaceCallExprAssigns = make(map[*ir.CallExpr][]ifaceAssignRef) s.analyze(fun.Init()) s.analyze(fun.Body) } @@ -253,7 +251,7 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignments case *ir.ConvExpr: if n1.Op() == ir.OCONVNOP { if !n1.Type().IsInterface() || !types.Identical(n1.Type(), n1.X.Type()) { - // As we check (directly before this switch) wheter n is an interface, thus we should only reach + // As we check (directly before this switch) whether n is an interface, thus we should only reach // here for iface conversions where both operands are the same. base.Fatalf("not identical/interface types found n1.Type = %v; n1.X.Type = %v", n1.Type(), n1.X.Type()) } @@ -386,7 +384,7 @@ type ifaceAssignRef struct { func (s *State) InlinedCall(origCall *ir.CallExpr, newInlinedCall *ir.InlinedCallExpr) { if s.ifaceAssignments == nil { // Full analyze has not been yet executed, so we can skip it for now. - // When no devirtualization happens in a function, it is unecessary to analyze it. + // When no devirtualization happens in a function, it is unnecessary to analyze it. return } From feea6d39f36fb22ba936772be764daf7acfda08f Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Fri, 28 Feb 2025 09:37:22 +0100 Subject: [PATCH 58/74] additional test case Change-Id: I281207906537ffc038ad766e6f7fab39e330a85d --- test/devirtualization.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/devirtualization.go b/test/devirtualization.go index 688dddac29cc29..9082a59e0721e5 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -132,6 +132,11 @@ func typeAssertsWithOkReturn() { callA(a) // ERROR "devirtualizing m.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A" "inlining call to callA" callIfA(a) // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" "inlining call to callIfA" } + { + _, a := newM2ret() // ERROR "&Impl{} does not escape$" "inlining call to newM2ret" + callA(a) // ERROR "devirtualizing m.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A" "inlining call to callA" + callIfA(a) // ERROR "devirtualizing v.A to \*Impl$" "inlining call to \(\*Impl\).A" "inlining call to callIfA" + } { var a M = &Impl{} // ERROR "&Impl{} does not escape$" // Note the !ok condition, devirtualizing here is fine. @@ -166,6 +171,10 @@ func newM() M { // ERROR "can inline newM$" return &Impl{} // ERROR "&Impl{} escapes to heap$" } +func newM2ret() (int, M) { // ERROR "can inline newM2ret$" + return -1, &Impl{} // ERROR "&Impl{} escapes to heap$" +} + func callA(m M) { // ERROR "can inline callA$" "leaking param: m$" m.(A).A() } From b079b6067d6150c8b75d6635dda73225752245bd Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Fri, 28 Feb 2025 11:15:22 +0100 Subject: [PATCH 59/74] fix tabs in comment Change-Id: I59d5c6dbfb4a66effa00df8dc55ecce52f9c1bf4 --- src/cmd/compile/internal/devirtualize/devirtualize.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index ca21400b7b0964..ae89974cd3e742 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -123,8 +123,8 @@ func StaticCall(s *State, call *ir.CallExpr) { // Consider: // // var v Iface - // v.A() - // v = &Impl{} + // v.A() + // v = &Impl{} // // Here in the devirtualizer, we determine the concrete type of v as being an *Impl, // but it can still be a nil interface, we have not detected that. The v.(*Impl) From 11ddd3e0671c6f7ec666664b6039913448fe2e0f Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Fri, 28 Feb 2025 12:23:05 +0100 Subject: [PATCH 60/74] removed added line Change-Id: Iec2bfec31beff34c1dd37832750955085d410b31 --- src/cmd/compile/internal/inline/interleaved/interleaved.go | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cmd/compile/internal/inline/interleaved/interleaved.go b/src/cmd/compile/internal/inline/interleaved/interleaved.go index d7dd5750de2fad..12f9b8056a13ef 100644 --- a/src/cmd/compile/internal/inline/interleaved/interleaved.go +++ b/src/cmd/compile/internal/inline/interleaved/interleaved.go @@ -114,7 +114,6 @@ func DevirtualizeAndInlinePackage(pkg *ir.Package, profile *pgoir.Profile) { if l0 == l1 { break } - for i := l0; i < l1; i++ { s.resolve(i) } From 67b899c556609ba4816ad269e51ceef71ece3d9c Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Fri, 28 Feb 2025 13:27:35 +0100 Subject: [PATCH 61/74] add two tests Change-Id: I6bd78f314088c4f373e6d7e182bd2cad2d888169 --- test/devirtualization.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/devirtualization.go b/test/devirtualization.go index 9082a59e0721e5..447b60f5fa8e72 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -514,6 +514,32 @@ func closureNoDevirt2() { c() } +//go:noinline +func closureDevirt3() { + var a A = &Impl{} // ERROR "&Impl{} does not escape$" + func() { // ERROR "func literal does not escape$" + defer func() {}() // ERROR "can inline closureDevirt3.func1.1$" "func literal does not escape$" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" + }() + func() { // ERROR "can inline closureDevirt3.func2$" + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" + }() // ERROR "inlining call to closureDevirt3.func2$" "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" +} + +//go:noinline +func closureNoDevirt3() { + var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" + func() { // ERROR "func literal does not escape$" + // defer so that it does not lnline. + defer func() {}() // ERROR "can inline closureNoDevirt3.func1.1$" "func literal does not escape$" + a.A() + }() + func() { // ERROR "can inline closureNoDevirt3.func2$" + a.A() + }() // ERROR "inlining call to closureNoDevirt3.func2$" + a = &Impl2{} // ERROR "&Impl2{} escapes to heap$" +} + //go:noinline func varDeclaredInClosureReferencesOuter() { var a A = &Impl{} // ERROR "&Impl{} does not escape$" From f33cdc356229759fe133b296f93d30823f342410 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Fri, 28 Feb 2025 13:34:03 +0100 Subject: [PATCH 62/74] add comment Change-Id: If5141619834f12ba064a81ee7d1cfae1e4da5d64 --- test/devirtualization.go | 1 + 1 file changed, 1 insertion(+) diff --git a/test/devirtualization.go b/test/devirtualization.go index 447b60f5fa8e72..ff88ae404a8750 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -518,6 +518,7 @@ func closureNoDevirt2() { func closureDevirt3() { var a A = &Impl{} // ERROR "&Impl{} does not escape$" func() { // ERROR "func literal does not escape$" + // defer so that it does not lnline. defer func() {}() // ERROR "can inline closureDevirt3.func1.1$" "func literal does not escape$" a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" }() From cc370fbf1bbf22a2dad9aa481705557e5a9775e4 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Fri, 28 Feb 2025 14:00:43 +0100 Subject: [PATCH 63/74] fix newinliner Change-Id: Iebb9c75dd10a4ee1a0c45c03069ade5887e9fe9d --- test/devirtualization.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/devirtualization.go b/test/devirtualization.go index ff88ae404a8750..90d15fd1c046f5 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -524,7 +524,7 @@ func closureDevirt3() { }() func() { // ERROR "can inline closureDevirt3.func2$" a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" - }() // ERROR "inlining call to closureDevirt3.func2$" "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" + }() // ERROR "inlining call to closureDevirt3.func2" "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" } //go:noinline @@ -537,7 +537,7 @@ func closureNoDevirt3() { }() func() { // ERROR "can inline closureNoDevirt3.func2$" a.A() - }() // ERROR "inlining call to closureNoDevirt3.func2$" + }() // ERROR "inlining call to closureNoDevirt3.func2" a = &Impl2{} // ERROR "&Impl2{} escapes to heap$" } From 56353018a45b3f55d789b7e7a2143a51eec3b341 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Fri, 28 Feb 2025 15:44:47 +0100 Subject: [PATCH 64/74] update Change-Id: Ic9c5b6d584b9fed42c06512c4ba69235efe21cce --- .../internal/devirtualize/devirtualize.go | 59 ++++++++++--------- .../inline/interleaved/interleaved.go | 14 +++-- 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index ae89974cd3e742..02912422244668 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -195,22 +195,27 @@ func concreteType(s *State, n ir.Node) (typ *types.Type) { fun = fun.ClosureParent } - if s.ifaceAssignments == nil { + if s.funDecls == nil { + // TODO: do not split this per *ir.Func. + // Then we could add an additional map containing all funcs that were + // analyzed, and only analyze names (here) that are of *ir.Func that we do + // not have here. + s.funDecls = make(map[*ir.Func]funcDeclState) + } + if _, ok := s.funDecls[fun]; !ok { if concreteTypeDebug { base.Warn("concreteType(): analyzing assignments in %v func", fun) } - s.fun = fun - s.ifaceAssignments = make(map[*ir.Name][]valOrTyp) - s.ifaceCallExprAssigns = make(map[*ir.CallExpr][]ifaceAssignRef) + s.funDecls[fun] = funcDeclState{ + ifaceAssignments: make(map[*ir.Name][]valOrTyp), + ifaceCallExprAssigns: make(map[*ir.CallExpr][]ifaceAssignRef), + } + s := s.funDecls[fun] s.analyze(fun.Init()) s.analyze(fun.Body) } - if s.fun != fun { - base.Fatalf("unexpected func = %v; want = %v", fun, s.fun) - } - - return s.ifaceAssignments[n] + return s.funDecls[fun].ifaceAssignments[n] }) if isNil && typ != nil { base.Fatalf("typ = %v; want = ", typ) @@ -364,14 +369,16 @@ type valOrTyp struct { // State holds precomputed state for use in (and filled by the first call to) [StaticCall]. type State struct { + funDecls map[*ir.Func]funcDeclState +} + +type funcDeclState struct { // ifaceAssignments stores all assignments to all interface variables, found in a func. ifaceAssignments map[*ir.Name][]valOrTyp // ifaceCallExprAssigns stores every [*ir.CallExpr] found in a func, which has // an interface result, that is assigned to a variable. ifaceCallExprAssigns map[*ir.CallExpr][]ifaceAssignRef - - fun *ir.Func } type ifaceAssignRef struct { @@ -381,26 +388,30 @@ type ifaceAssignRef struct { } // InlinedCall updates the [State] to take into account a newly inlined call. -func (s *State) InlinedCall(origCall *ir.CallExpr, newInlinedCall *ir.InlinedCallExpr) { - if s.ifaceAssignments == nil { - // Full analyze has not been yet executed, so we can skip it for now. +func (s *State) InlinedCall(fun *ir.Func, origCall *ir.CallExpr, newInlinedCall *ir.InlinedCallExpr) { + for fun.ClosureParent != nil { + fun = fun.ClosureParent + } + funcState, ok := s.funDecls[fun] + if !ok { + // Full analyze has not been yet executed for the provided function, so we can skip it for now. // When no devirtualization happens in a function, it is unnecessary to analyze it. return } // Analyze assignments in the newly inlined function. - s.analyze(newInlinedCall.Init()) - s.analyze(newInlinedCall.Body) + funcState.analyze(newInlinedCall.Init()) + funcState.analyze(newInlinedCall.Body) - v, ok := s.ifaceCallExprAssigns[origCall] + v, ok := funcState.ifaceCallExprAssigns[origCall] if !ok { return } - delete(s.ifaceCallExprAssigns, origCall) + delete(funcState.ifaceCallExprAssigns, origCall) // Update assignments to reference the new ReturnVars of the inlined call. for _, ni := range v { - vt := &s.ifaceAssignments[ni.name][ni.valOrTypeIndex] + vt := &funcState.ifaceAssignments[ni.name][ni.valOrTypeIndex] if vt.node != nil || vt.typ != nil { base.Fatalf("unexpected non-empty valOrTyp") } @@ -417,7 +428,7 @@ func (s *State) InlinedCall(origCall *ir.CallExpr, newInlinedCall *ir.InlinedCal } // analyze analyzes every assignment to interface variables in nodes, updating [State]. -func (s *State) analyze(nodes ir.Nodes) { +func (s *funcDeclState) analyze(nodes ir.Nodes) { assign := func(name ir.Node, value valOrTyp) (*ir.Name, int) { if name == nil || name.Op() != ir.ONAME || ir.IsBlank(name) { return nil, -1 @@ -428,14 +439,6 @@ func (s *State) analyze(nodes ir.Nodes) { return nil, -1 } - fun := n.Curfn - for fun.ClosureParent != nil { - fun = fun.ClosureParent - } - if s.fun != fun { - base.Fatalf("unexpected func = %v; want = %v", fun, s.fun) - } - // Do not track variables that are not of interface types. // For devirtualization they are unnecessary, we will not even look them up. if !n.Type().IsInterface() { diff --git a/src/cmd/compile/internal/inline/interleaved/interleaved.go b/src/cmd/compile/internal/inline/interleaved/interleaved.go index 12f9b8056a13ef..6a515efb84e8e6 100644 --- a/src/cmd/compile/internal/inline/interleaved/interleaved.go +++ b/src/cmd/compile/internal/inline/interleaved/interleaved.go @@ -45,6 +45,8 @@ func DevirtualizeAndInlinePackage(pkg *ir.Package, profile *pgoir.Profile) { inlState := make(map[*ir.Func]*inlClosureState) calleeUseCounts := make(map[*ir.Func]int) + var state devirtualize.State + // Pre-process all the functions, adding parentheses around call sites and starting their "inl state". for _, fn := range typecheck.Target.Funcs { bigCaller := base.Flag.LowerL != 0 && inline.IsBigFunc(fn) @@ -52,7 +54,7 @@ func DevirtualizeAndInlinePackage(pkg *ir.Package, profile *pgoir.Profile) { fmt.Printf("%v: function %v considered 'big'; reducing max cost of inlinees\n", ir.Line(fn), fn) } - s := &inlClosureState{bigCaller: bigCaller, profile: profile, fn: fn, callSites: make(map[*ir.ParenExpr]bool), useCounts: calleeUseCounts} + s := &inlClosureState{bigCaller: bigCaller, profile: profile, fn: fn, callSites: make(map[*ir.ParenExpr]bool), useCounts: calleeUseCounts, devirtState: &state} s.parenthesize() inlState[fn] = s @@ -106,7 +108,7 @@ func DevirtualizeAndInlinePackage(pkg *ir.Package, profile *pgoir.Profile) { // Update AST and recursively mark nodes. paren.X = inlinedCall ir.EditChildren(inlinedCall, s.mark) // mark may append to parens - s.devirtState.InlinedCall(origCall, inlinedCall) + s.devirtState.InlinedCall(s.fn, origCall, inlinedCall) done = false } } @@ -164,7 +166,7 @@ func DevirtualizeAndInlineFunc(fn *ir.Func, profile *pgoir.Profile) { fmt.Printf("%v: function %v considered 'big'; reducing max cost of inlinees\n", ir.Line(fn), fn) } - s := &inlClosureState{bigCaller: bigCaller, profile: profile, fn: fn, callSites: make(map[*ir.ParenExpr]bool), useCounts: make(map[*ir.Func]int)} + s := &inlClosureState{bigCaller: bigCaller, profile: profile, fn: fn, callSites: make(map[*ir.ParenExpr]bool), useCounts: make(map[*ir.Func]int), devirtState: new(devirtualize.State)} s.parenthesize() s.fixpoint() s.unparenthesize() @@ -178,7 +180,7 @@ type callSite struct { type inlClosureState struct { fn *ir.Func - devirtState devirtualize.State + devirtState *devirtualize.State profile *pgoir.Profile callSites map[*ir.ParenExpr]bool // callSites[p] == "p appears in parens" (do not append again) @@ -203,7 +205,7 @@ func (s *inlClosureState) resolve(i int) (*ir.Func, int) { if !ok { // previously inlined return nil, -1 } - devirtualize.StaticCall(&s.devirtState, call) + devirtualize.StaticCall(s.devirtState, call) if callee := inline.InlineCallTarget(s.fn, call, s.profile); callee != nil { for len(s.resolved) <= i { s.resolved = append(s.resolved, nil) @@ -351,7 +353,7 @@ func (s *inlClosureState) fixpoint() bool { // Update AST and recursively mark nodes. paren.X = inlinedCall ir.EditChildren(inlinedCall, s.mark) // mark may append to parens - s.devirtState.InlinedCall(origCall, inlinedCall) + s.devirtState.InlinedCall(s.fn, origCall, inlinedCall) done = false changed = true } From c4614e6ecb8248a069ef97d05471cb43f942803e Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Fri, 28 Feb 2025 17:01:24 +0100 Subject: [PATCH 65/74] simplify Change-Id: Ie7e0cceff262e231c925bd3c91203ba4c0baa841 --- .../internal/devirtualize/devirtualize.go | 63 +++++++++---------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 02912422244668..eb2e012d54de42 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -191,31 +191,27 @@ func concreteType(s *State, n ir.Node) (typ *types.Type) { } fun := n.Curfn - for fun.ClosureParent != nil { - fun = fun.ClosureParent + if fun == nil { + base.Fatalf("n.Curfn = ") } - if s.funDecls == nil { - // TODO: do not split this per *ir.Func. - // Then we could add an additional map containing all funcs that were - // analyzed, and only analyze names (here) that are of *ir.Func that we do - // not have here. - s.funDecls = make(map[*ir.Func]funcDeclState) - } - if _, ok := s.funDecls[fun]; !ok { + // Check whether the variable is declared in a function that we had + // analyzed before, if not then analyze its assignments. + if _, ok := s.analyzedFuncs[fun]; !ok { if concreteTypeDebug { base.Warn("concreteType(): analyzing assignments in %v func", fun) } - s.funDecls[fun] = funcDeclState{ - ifaceAssignments: make(map[*ir.Name][]valOrTyp), - ifaceCallExprAssigns: make(map[*ir.CallExpr][]ifaceAssignRef), + if s.analyzedFuncs == nil { + s.ifaceAssignments = make(map[*ir.Name][]valOrTyp) + s.ifaceCallExprAssigns = make(map[*ir.CallExpr][]ifaceAssignRef) + s.analyzedFuncs = make(map[*ir.Func]struct{}) } - s := s.funDecls[fun] + s.analyzedFuncs[fun] = struct{}{} s.analyze(fun.Init()) s.analyze(fun.Body) } - return s.funDecls[fun].ifaceAssignments[n] + return s.ifaceAssignments[n] }) if isNil && typ != nil { base.Fatalf("typ = %v; want = ", typ) @@ -367,18 +363,17 @@ type valOrTyp struct { node ir.Node } -// State holds precomputed state for use in (and filled by the first call to) [StaticCall]. +// State holds precomputed state for use in [StaticCall]. type State struct { - funDecls map[*ir.Func]funcDeclState -} - -type funcDeclState struct { - // ifaceAssignments stores all assignments to all interface variables, found in a func. + // ifaceAssignments stores all assignments to all interface variables. ifaceAssignments map[*ir.Name][]valOrTyp // ifaceCallExprAssigns stores every [*ir.CallExpr] found in a func, which has // an interface result, that is assigned to a variable. ifaceCallExprAssigns map[*ir.CallExpr][]ifaceAssignRef + + // analyzedFuncs is a set of Funcs that were analyzed for iface assignments. + analyzedFuncs map[*ir.Func]struct{} } type ifaceAssignRef struct { @@ -389,35 +384,31 @@ type ifaceAssignRef struct { // InlinedCall updates the [State] to take into account a newly inlined call. func (s *State) InlinedCall(fun *ir.Func, origCall *ir.CallExpr, newInlinedCall *ir.InlinedCallExpr) { - for fun.ClosureParent != nil { - fun = fun.ClosureParent - } - funcState, ok := s.funDecls[fun] - if !ok { + if _, ok := s.analyzedFuncs[fun]; !ok { // Full analyze has not been yet executed for the provided function, so we can skip it for now. // When no devirtualization happens in a function, it is unnecessary to analyze it. return } // Analyze assignments in the newly inlined function. - funcState.analyze(newInlinedCall.Init()) - funcState.analyze(newInlinedCall.Body) + s.analyze(newInlinedCall.Init()) + s.analyze(newInlinedCall.Body) - v, ok := funcState.ifaceCallExprAssigns[origCall] + v, ok := s.ifaceCallExprAssigns[origCall] if !ok { return } - delete(funcState.ifaceCallExprAssigns, origCall) + delete(s.ifaceCallExprAssigns, origCall) // Update assignments to reference the new ReturnVars of the inlined call. for _, ni := range v { - vt := &funcState.ifaceAssignments[ni.name][ni.valOrTypeIndex] + vt := &s.ifaceAssignments[ni.name][ni.valOrTypeIndex] if vt.node != nil || vt.typ != nil { base.Fatalf("unexpected non-empty valOrTyp") } if concreteTypeDebug { base.Warn( - "Invalidate(%v, %v): replacing interface node in (%v,%v) to %v (typ %v)", + "InlinedCall(%v, %v): replacing interface node in (%v,%v) to %v (typ %v)", origCall, newInlinedCall, ni.name, ni.valOrTypeIndex, newInlinedCall.ReturnVars[ni.returnIndex], newInlinedCall.ReturnVars[ni.returnIndex].Type(), @@ -428,7 +419,7 @@ func (s *State) InlinedCall(fun *ir.Func, origCall *ir.CallExpr, newInlinedCall } // analyze analyzes every assignment to interface variables in nodes, updating [State]. -func (s *funcDeclState) analyze(nodes ir.Nodes) { +func (s *State) analyze(nodes ir.Nodes) { assign := func(name ir.Node, value valOrTyp) (*ir.Name, int) { if name == nil || name.Op() != ir.ONAME || ir.IsBlank(name) { return nil, -1 @@ -579,7 +570,11 @@ func (s *funcDeclState) analyze(nodes ir.Nodes) { } } case ir.OCLOSURE: - ir.Visit(n.(*ir.ClosureExpr).Func, do) + n := n.(*ir.ClosureExpr) + if _, ok := s.analyzedFuncs[n.Func]; !ok { + s.analyzedFuncs[n.Func] = struct{}{} + ir.Visit(n.Func, do) + } } } ir.VisitList(nodes, do) From 91951dace85aa2bc5570c4ccfd33daa1f2017044 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Fri, 28 Feb 2025 17:04:55 +0100 Subject: [PATCH 66/74] remove devirtState field Change-Id: I7dd1a79963ab7f7930262b8463386faad53691b5 --- .../inline/interleaved/interleaved.go | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/cmd/compile/internal/inline/interleaved/interleaved.go b/src/cmd/compile/internal/inline/interleaved/interleaved.go index 6a515efb84e8e6..b74c3cb72d9c15 100644 --- a/src/cmd/compile/internal/inline/interleaved/interleaved.go +++ b/src/cmd/compile/internal/inline/interleaved/interleaved.go @@ -54,13 +54,13 @@ func DevirtualizeAndInlinePackage(pkg *ir.Package, profile *pgoir.Profile) { fmt.Printf("%v: function %v considered 'big'; reducing max cost of inlinees\n", ir.Line(fn), fn) } - s := &inlClosureState{bigCaller: bigCaller, profile: profile, fn: fn, callSites: make(map[*ir.ParenExpr]bool), useCounts: calleeUseCounts, devirtState: &state} + s := &inlClosureState{bigCaller: bigCaller, profile: profile, fn: fn, callSites: make(map[*ir.ParenExpr]bool), useCounts: calleeUseCounts} s.parenthesize() inlState[fn] = s // Do a first pass at counting call sites. for i := range s.parens { - s.resolve(i) + s.resolve(&state, i) } } @@ -104,11 +104,11 @@ func DevirtualizeAndInlinePackage(pkg *ir.Package, profile *pgoir.Profile) { for { for i := l0; i < l1; i++ { // can't use "range parens" here paren := s.parens[i] - if origCall, inlinedCall := s.edit(i); inlinedCall != nil { + if origCall, inlinedCall := s.edit(&state, i); inlinedCall != nil { // Update AST and recursively mark nodes. paren.X = inlinedCall ir.EditChildren(inlinedCall, s.mark) // mark may append to parens - s.devirtState.InlinedCall(s.fn, origCall, inlinedCall) + state.InlinedCall(s.fn, origCall, inlinedCall) done = false } } @@ -117,7 +117,7 @@ func DevirtualizeAndInlinePackage(pkg *ir.Package, profile *pgoir.Profile) { break } for i := l0; i < l1; i++ { - s.resolve(i) + s.resolve(&state, i) } } @@ -166,7 +166,7 @@ func DevirtualizeAndInlineFunc(fn *ir.Func, profile *pgoir.Profile) { fmt.Printf("%v: function %v considered 'big'; reducing max cost of inlinees\n", ir.Line(fn), fn) } - s := &inlClosureState{bigCaller: bigCaller, profile: profile, fn: fn, callSites: make(map[*ir.ParenExpr]bool), useCounts: make(map[*ir.Func]int), devirtState: new(devirtualize.State)} + s := &inlClosureState{bigCaller: bigCaller, profile: profile, fn: fn, callSites: make(map[*ir.ParenExpr]bool), useCounts: make(map[*ir.Func]int)} s.parenthesize() s.fixpoint() s.unparenthesize() @@ -179,9 +179,7 @@ type callSite struct { } type inlClosureState struct { - fn *ir.Func - devirtState *devirtualize.State - + fn *ir.Func profile *pgoir.Profile callSites map[*ir.ParenExpr]bool // callSites[p] == "p appears in parens" (do not append again) resolved []*ir.Func // for each call in parens, the resolved target of the call @@ -193,7 +191,7 @@ type inlClosureState struct { // resolve attempts to resolve a call to a potentially inlineable callee // and updates use counts on the callees. Returns the call site count // for that callee. -func (s *inlClosureState) resolve(i int) (*ir.Func, int) { +func (s *inlClosureState) resolve(state *devirtualize.State, i int) (*ir.Func, int) { p := s.parens[i] if i < len(s.resolved) { if callee := s.resolved[i]; callee != nil { @@ -205,7 +203,7 @@ func (s *inlClosureState) resolve(i int) (*ir.Func, int) { if !ok { // previously inlined return nil, -1 } - devirtualize.StaticCall(s.devirtState, call) + devirtualize.StaticCall(state, call) if callee := inline.InlineCallTarget(s.fn, call, s.profile); callee != nil { for len(s.resolved) <= i { s.resolved = append(s.resolved, nil) @@ -218,7 +216,7 @@ func (s *inlClosureState) resolve(i int) (*ir.Func, int) { return nil, 0 } -func (s *inlClosureState) edit(i int) (*ir.CallExpr, *ir.InlinedCallExpr) { +func (s *inlClosureState) edit(state *devirtualize.State, i int) (*ir.CallExpr, *ir.InlinedCallExpr) { n := s.parens[i].X call, ok := n.(*ir.CallExpr) if !ok { @@ -227,7 +225,7 @@ func (s *inlClosureState) edit(i int) (*ir.CallExpr, *ir.InlinedCallExpr) { // This is redundant with earlier calls to // resolve, but because things can change it // must be re-checked. - callee, count := s.resolve(i) + callee, count := s.resolve(state, i) if count <= 0 { return nil, nil } @@ -343,17 +341,18 @@ func (s *inlClosureState) unparenthesize() { // returns. func (s *inlClosureState) fixpoint() bool { changed := false + var state devirtualize.State ir.WithFunc(s.fn, func() { done := false for !done { done = true for i := 0; i < len(s.parens); i++ { // can't use "range parens" here paren := s.parens[i] - if origCall, inlinedCall := s.edit(i); inlinedCall != nil { + if origCall, inlinedCall := s.edit(&state, i); inlinedCall != nil { // Update AST and recursively mark nodes. paren.X = inlinedCall ir.EditChildren(inlinedCall, s.mark) // mark may append to parens - s.devirtState.InlinedCall(s.fn, origCall, inlinedCall) + state.InlinedCall(s.fn, origCall, inlinedCall) done = false changed = true } From 69f040d5ce36fbbeb8ca04301a5993c527749643 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Fri, 28 Feb 2025 17:07:33 +0100 Subject: [PATCH 67/74] update comment Change-Id: I19c5a3947ea331e6a7daa4fedaa1ff1e99c28203 --- src/cmd/compile/internal/devirtualize/devirtualize.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index eb2e012d54de42..2d92f406e2fa61 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -368,8 +368,8 @@ type State struct { // ifaceAssignments stores all assignments to all interface variables. ifaceAssignments map[*ir.Name][]valOrTyp - // ifaceCallExprAssigns stores every [*ir.CallExpr] found in a func, which has - // an interface result, that is assigned to a variable. + // ifaceCallExprAssigns stores every [*ir.CallExpr], which has an interface + // result, that is assigned to a variable. ifaceCallExprAssigns map[*ir.CallExpr][]ifaceAssignRef // analyzedFuncs is a set of Funcs that were analyzed for iface assignments. From 36bcb8ab842bfd35d1c344e8558a3a588156e421 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Fri, 28 Feb 2025 17:12:06 +0100 Subject: [PATCH 68/74] update Change-Id: I49603747594959eb93c5398c60d4076d1b14f3b8 --- .../compile/internal/devirtualize/devirtualize.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 2d92f406e2fa61..09a606d88b29ce 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -394,27 +394,27 @@ func (s *State) InlinedCall(fun *ir.Func, origCall *ir.CallExpr, newInlinedCall s.analyze(newInlinedCall.Init()) s.analyze(newInlinedCall.Body) - v, ok := s.ifaceCallExprAssigns[origCall] + refs, ok := s.ifaceCallExprAssigns[origCall] if !ok { return } delete(s.ifaceCallExprAssigns, origCall) // Update assignments to reference the new ReturnVars of the inlined call. - for _, ni := range v { - vt := &s.ifaceAssignments[ni.name][ni.valOrTypeIndex] + for _, ref := range refs { + vt := &s.ifaceAssignments[ref.name][ref.valOrTypeIndex] if vt.node != nil || vt.typ != nil { base.Fatalf("unexpected non-empty valOrTyp") } if concreteTypeDebug { base.Warn( "InlinedCall(%v, %v): replacing interface node in (%v,%v) to %v (typ %v)", - origCall, newInlinedCall, ni.name, ni.valOrTypeIndex, - newInlinedCall.ReturnVars[ni.returnIndex], - newInlinedCall.ReturnVars[ni.returnIndex].Type(), + origCall, newInlinedCall, ref.name, ref.valOrTypeIndex, + newInlinedCall.ReturnVars[ref.returnIndex], + newInlinedCall.ReturnVars[ref.returnIndex].Type(), ) } - *vt = valOrTyp{node: newInlinedCall.ReturnVars[ni.returnIndex]} + *vt = valOrTyp{node: newInlinedCall.ReturnVars[ref.returnIndex]} } } From ac6e30b3562e50debf4266058d0f04d7c9763384 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Sun, 2 Mar 2025 18:53:43 +0100 Subject: [PATCH 69/74] minor tweaks Change-Id: I1513a3e836190ebdbe41431abbf5f978a5cfa036 --- .../internal/devirtualize/devirtualize.go | 74 +++++++++---------- test/devirtualization.go | 4 +- 2 files changed, 35 insertions(+), 43 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 09a606d88b29ce..d21219471f6721 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -182,37 +182,7 @@ const concreteTypeDebug = false // Returns nil when the concrete type could not be determined, or when there are multiple // (different) types assigned to an interface. func concreteType(s *State, n ir.Node) (typ *types.Type) { - typ, isNil := concreteType1(n, make(map[*ir.Name]*types.Type), func(n *ir.Name) []valOrTyp { - if n.Curfn == nil { - base.Fatalf("n.Curfn == nil: %v", n) - } - if !n.Type().IsInterface() { - base.Fatalf("name passed to getAssignments is not of an interface type: %v", n.Type()) - } - - fun := n.Curfn - if fun == nil { - base.Fatalf("n.Curfn = ") - } - - // Check whether the variable is declared in a function that we had - // analyzed before, if not then analyze its assignments. - if _, ok := s.analyzedFuncs[fun]; !ok { - if concreteTypeDebug { - base.Warn("concreteType(): analyzing assignments in %v func", fun) - } - if s.analyzedFuncs == nil { - s.ifaceAssignments = make(map[*ir.Name][]valOrTyp) - s.ifaceCallExprAssigns = make(map[*ir.CallExpr][]ifaceAssignRef) - s.analyzedFuncs = make(map[*ir.Func]struct{}) - } - s.analyzedFuncs[fun] = struct{}{} - s.analyze(fun.Init()) - s.analyze(fun.Body) - } - - return s.ifaceAssignments[n] - }) + typ, isNil := concreteType1(s, n, make(map[*ir.Name]*types.Type)) if isNil && typ != nil { base.Fatalf("typ = %v; want = ", typ) } @@ -230,7 +200,7 @@ func concreteType(s *State, n ir.Node) (typ *types.Type) { // // If n is statically known to be nil, this function returns a nil Type with isNil == true. // However, if any concrete type is found, it is returned instead, even if n was assigned with nil. -func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignments func(*ir.Name) []valOrTyp) (t *types.Type, isNil bool) { +func concreteType1(s *State, n ir.Node, analyzed map[*ir.Name]*types.Type) (t *types.Type, isNil bool) { nn := n // for debug messages if concreteTypeDebug { @@ -314,19 +284,12 @@ func concreteType1(n ir.Node, analyzed map[*ir.Name]*types.Type, getAssignments base.Warn("concreteType1(%v): analyzing assignments to %v", nn, name) } - assignments := getAssignments(name) - if len(assignments) == 0 { - // Variable either declared with zero value, or only assigned - // with nil (getAssignments does not return such assignments). - return nil, true - } - var typ *types.Type - for _, v := range assignments { + for _, v := range s.assignments(name) { t := v.typ if v.node != nil { var isNil bool - t, isNil = concreteType1(v.node, analyzed, getAssignments) + t, isNil = concreteType1(s, v.node, analyzed) if isNil { if t != nil { base.Fatalf("t = %v; want = ", t) @@ -418,6 +381,35 @@ func (s *State) InlinedCall(fun *ir.Func, origCall *ir.CallExpr, newInlinedCall } } +// assignments returns all assignments to n. +func (s *State) assignments(n *ir.Name) []valOrTyp { + fun := n.Curfn + if fun == nil { + base.Fatalf("n.Curfn = ") + } + + if !n.Type().IsInterface() { + base.Fatalf("name passed to getAssignments is not of an interface type: %v", n.Type()) + } + + // Analyze assignments in func, if not analyzed before. + if _, ok := s.analyzedFuncs[fun]; !ok { + if concreteTypeDebug { + base.Warn("concreteType(): analyzing assignments in %v func", fun) + } + if s.analyzedFuncs == nil { + s.ifaceAssignments = make(map[*ir.Name][]valOrTyp) + s.ifaceCallExprAssigns = make(map[*ir.CallExpr][]ifaceAssignRef) + s.analyzedFuncs = make(map[*ir.Func]struct{}) + } + s.analyzedFuncs[fun] = struct{}{} + s.analyze(fun.Init()) + s.analyze(fun.Body) + } + + return s.ifaceAssignments[n] +} + // analyze analyzes every assignment to interface variables in nodes, updating [State]. func (s *State) analyze(nodes ir.Nodes) { assign := func(name ir.Node, value valOrTyp) (*ir.Name, int) { diff --git a/test/devirtualization.go b/test/devirtualization.go index 90d15fd1c046f5..de122688666f82 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -1129,13 +1129,13 @@ func (implWrapper) A() {} // ERROR "can inline implWrapper.A$" func devirtWrapperType() { { i := &Impl{} // ERROR "&Impl{} does not escape$" - // This is an OCONVNOP, so we have to be carefull, not to devirtualize it to Impl.A. + // This is an OCONVNOP, so we have to be careful, not to devirtualize it to Impl.A. var a A = (*implWrapper)(i) a.A() // ERROR "devirtualizing a.A to \*implWrapper$" "inlining call to implWrapper.A" } { i := Impl{} - // This is an OCONVNOP, so we have to be carefull, not to devirtualize it to Impl.A. + // This is an OCONVNOP, so we have to be careful, not to devirtualize it to Impl.A. var a A = (implWrapper)(i) // ERROR "implWrapper\(i\) does not escape$" a.A() // ERROR "devirtualizing a.A to implWrapper$" "inlining call to implWrapper.A" } From 5286ef99fa49b72d43027cad1ca6af9bd5f07c74 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Sun, 2 Mar 2025 20:28:01 +0100 Subject: [PATCH 70/74] minor Change-Id: I5e7b39f9d9bb0d41e7490ca139caf681bc7615b0 --- src/cmd/compile/internal/devirtualize/devirtualize.go | 4 ++-- src/cmd/compile/internal/ir/expr.go | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index d21219471f6721..62afe3f4f67098 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -389,7 +389,7 @@ func (s *State) assignments(n *ir.Name) []valOrTyp { } if !n.Type().IsInterface() { - base.Fatalf("name passed to getAssignments is not of an interface type: %v", n.Type()) + base.Fatalf("name passed to assignments is not of an interface type: %v", n.Type()) } // Analyze assignments in func, if not analyzed before. @@ -443,7 +443,7 @@ func (s *State) analyze(nodes ir.Nodes) { } if concreteTypeDebug { - base.Warn("populateIfaceAssignments(): assignment found %v = (%v;%v)", name, value.typ, value.node) + base.Warn("analyze(): assignment found %v = (%v;%v)", name, value.typ, value.node) } s.ifaceAssignments[n] = append(s.ifaceAssignments[n], value) diff --git a/src/cmd/compile/internal/ir/expr.go b/src/cmd/compile/internal/ir/expr.go index 08e61e9c772675..dd3e291de8e6ab 100644 --- a/src/cmd/compile/internal/ir/expr.go +++ b/src/cmd/compile/internal/ir/expr.go @@ -386,7 +386,6 @@ func (n *InlinedCallExpr) SingleResult() Node { if have := len(n.ReturnVars); have != 1 { base.FatalfAt(n.Pos(), "inlined call has %v results, expected 1", have) } - // TODO: do we need to do that also? if !n.Type().HasShape() && n.ReturnVars[0].Type().HasShape() { // If the type of the call is not a shape, but the type of the return value // is a shape, we need to do an implicit conversion, so the real type From 8eea6a6e318f697d412536782f16d2050df444ee Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Mon, 3 Mar 2025 19:19:37 +0100 Subject: [PATCH 71/74] self assignments Change-Id: Ic8164073baddc803e8e102c3088aec4724c7535f --- .../internal/devirtualize/devirtualize.go | 40 +++++++++---------- test/devirtualization.go | 37 +++++++++++------ 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 62afe3f4f67098..2b8ab754c99be6 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -182,7 +182,7 @@ const concreteTypeDebug = false // Returns nil when the concrete type could not be determined, or when there are multiple // (different) types assigned to an interface. func concreteType(s *State, n ir.Node) (typ *types.Type) { - typ, isNil := concreteType1(s, n, make(map[*ir.Name]*types.Type)) + typ, isNil := concreteType1(s, n, make(map[*ir.Name]struct{})) if isNil && typ != nil { base.Fatalf("typ = %v; want = ", typ) } @@ -200,7 +200,7 @@ func concreteType(s *State, n ir.Node) (typ *types.Type) { // // If n is statically known to be nil, this function returns a nil Type with isNil == true. // However, if any concrete type is found, it is returned instead, even if n was assigned with nil. -func concreteType1(s *State, n ir.Node, analyzed map[*ir.Name]*types.Type) (t *types.Type, isNil bool) { +func concreteType1(s *State, n ir.Node, seen map[*ir.Name]struct{}) (t *types.Type, isNil bool) { nn := n // for debug messages if concreteTypeDebug { @@ -270,15 +270,13 @@ func concreteType1(s *State, n ir.Node, analyzed map[*ir.Name]*types.Type) (t *t return nil, false // conservatively assume it's reassigned with a different type indirectly } - if typ, ok := analyzed[name]; ok { - return typ, false + if _, ok := seen[name]; ok { + // Self assignment, treat is the same as a nil assignment. + // In case this is the only assignment then we are not going to devirtualize anything. + // In case there are other assignment, we still preserve the correct type. + return nil, true } - - // For now set the Type to nil, as we don't know it yet, we will update - // it at the end of this function, if we find a concrete type. - // This is not ideal, as in-process concreteType1 calls (that this function also - // executes) will get a nil (from the map lookup above), where we could determine the type. - analyzed[name] = nil + seen[name] = struct{}{} if concreteTypeDebug { base.Warn("concreteType1(%v): analyzing assignments to %v", nn, name) @@ -289,7 +287,7 @@ func concreteType1(s *State, n ir.Node, analyzed map[*ir.Name]*types.Type) (t *t t := v.typ if v.node != nil { var isNil bool - t, isNil = concreteType1(s, v.node, analyzed) + t, isNil = concreteType1(s, v.node, seen) if isNil { if t != nil { base.Fatalf("t = %v; want = ", t) @@ -303,15 +301,13 @@ func concreteType1(s *State, n ir.Node, analyzed map[*ir.Name]*types.Type) (t *t typ = t } + delete(seen, name) + if typ == nil { // Variable either declared with zero value, or only assigned with nil. - // For now don't bother storing the information that we could have - // assigned nil in the analyzed map, if we access the same name again we will - // get an result as if an unknown concrete type was assigned. return nil, true } - analyzed[name] = typ return typ, false } @@ -346,7 +342,7 @@ type ifaceAssignRef struct { } // InlinedCall updates the [State] to take into account a newly inlined call. -func (s *State) InlinedCall(fun *ir.Func, origCall *ir.CallExpr, newInlinedCall *ir.InlinedCallExpr) { +func (s *State) InlinedCall(fun *ir.Func, origCall *ir.CallExpr, inlinedCall *ir.InlinedCallExpr) { if _, ok := s.analyzedFuncs[fun]; !ok { // Full analyze has not been yet executed for the provided function, so we can skip it for now. // When no devirtualization happens in a function, it is unnecessary to analyze it. @@ -354,8 +350,8 @@ func (s *State) InlinedCall(fun *ir.Func, origCall *ir.CallExpr, newInlinedCall } // Analyze assignments in the newly inlined function. - s.analyze(newInlinedCall.Init()) - s.analyze(newInlinedCall.Body) + s.analyze(inlinedCall.Init()) + s.analyze(inlinedCall.Body) refs, ok := s.ifaceCallExprAssigns[origCall] if !ok { @@ -372,12 +368,12 @@ func (s *State) InlinedCall(fun *ir.Func, origCall *ir.CallExpr, newInlinedCall if concreteTypeDebug { base.Warn( "InlinedCall(%v, %v): replacing interface node in (%v,%v) to %v (typ %v)", - origCall, newInlinedCall, ref.name, ref.valOrTypeIndex, - newInlinedCall.ReturnVars[ref.returnIndex], - newInlinedCall.ReturnVars[ref.returnIndex].Type(), + origCall, inlinedCall, ref.name, ref.valOrTypeIndex, + inlinedCall.ReturnVars[ref.returnIndex], + inlinedCall.ReturnVars[ref.returnIndex].Type(), ) } - *vt = valOrTyp{node: newInlinedCall.ReturnVars[ref.returnIndex]} + *vt = valOrTyp{node: inlinedCall.ReturnVars[ref.returnIndex]} } } diff --git a/test/devirtualization.go b/test/devirtualization.go index de122688666f82..6f809957642857 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -1143,39 +1143,50 @@ func devirtWrapperType() { func selfAssigns() { { - var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" + var a A = &Impl{} // ERROR "&Impl{} does not escape$" a = a - a.A() + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" } { - var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" + var a A = &Impl{} // ERROR "&Impl{} does not escape" var asAny any = a asAny = asAny - asAny.(A).A() + asAny.(A).A() // ERROR "devirtualizing asAny.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A" } { - var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" + var a A = &Impl{} // ERROR "&Impl{} does not escape" var asAny any = a a = asAny.(A) - asAny.(A).A() - a.(A).A() + asAny.(A).A() // ERROR "devirtualizing asAny.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A" + a.(A).A() // ERROR "devirtualizing a.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A" b := a - b.(A).A() + b.(A).A() // ERROR "devirtualizing b.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A" } { - var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" + var a A = &Impl{} // ERROR "&Impl{} does not escape" var asAny any = a asAny = asAny a = asAny.(A) asAny = a - asAny.(A).A() - asAny.(M).M() + asAny.(A).A() // ERROR "devirtualizing asAny.\(A\).A to \*Impl$" "inlining call to \(\*Impl\).A" + asAny.(M).M() // ERROR "devirtualizing asAny.\(M\).M to \*Impl$" "inlining call to \(\*Impl\).M" } { - var a A = &Impl{} // ERROR "&Impl{} escapes to heap$" + var a A = &Impl{} // ERROR "&Impl{} does not escape" var asAny A = a a = asAny.(A) - a.A() + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" + } + { + var a, b, c A + c = &Impl{} // ERROR "&Impl{} does not escape$" + a = c + c = b + b = c + a = b + b = a + c = a + a.A() // ERROR "devirtualizing a.A to \*Impl$" "inlining call to \(\*Impl\).A" } } From 1d45dfa64e54c4529a42f4e7b35e09e4a85ffc66 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Mon, 3 Mar 2025 19:27:12 +0100 Subject: [PATCH 72/74] typo Change-Id: I961be6ae172f8f4b82f4d5b84d45d00f1c5ac05d --- src/cmd/compile/internal/devirtualize/devirtualize.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 2b8ab754c99be6..279d9d1e281afa 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -271,7 +271,7 @@ func concreteType1(s *State, n ir.Node, seen map[*ir.Name]struct{}) (t *types.Ty } if _, ok := seen[name]; ok { - // Self assignment, treat is the same as a nil assignment. + // Self assignment, treat it the same as a nil assignment. // In case this is the only assignment then we are not going to devirtualize anything. // In case there are other assignment, we still preserve the correct type. return nil, true From 7fea0a5989bb9b24d90e7519e054a715e030a32b Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 11 Mar 2025 20:52:52 +0100 Subject: [PATCH 73/74] review update Change-Id: I25f4ecbd6b96496aad681684e75c95b3d8f2a930 --- .../internal/devirtualize/devirtualize.go | 156 ++++++++++-------- test/devirtualization.go | 12 ++ 2 files changed, 95 insertions(+), 73 deletions(-) diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go index 279d9d1e281afa..2b907b2e3d1c24 100644 --- a/src/cmd/compile/internal/devirtualize/devirtualize.go +++ b/src/cmd/compile/internal/devirtualize/devirtualize.go @@ -182,30 +182,32 @@ const concreteTypeDebug = false // Returns nil when the concrete type could not be determined, or when there are multiple // (different) types assigned to an interface. func concreteType(s *State, n ir.Node) (typ *types.Type) { - typ, isNil := concreteType1(s, n, make(map[*ir.Name]struct{})) - if isNil && typ != nil { - base.Fatalf("typ = %v; want = ", typ) - } + typ = concreteType1(s, n, make(map[*ir.Name]struct{})) if typ != nil && typ.IsInterface() { base.Fatalf("typ.IsInterface() = true; want = false; typ = %v", typ) } + if typ == &noType { + return nil + } return typ } +// noType is a sentinel value returned by [concreteType1]. +var noType types.Type + // concreteType1 analyzes the node n and returns its concrete type if it is statically known. // Otherwise, it returns a nil Type, indicating that a concrete type was not determined. -// This can happen in cases where n is assigned an interface type and the concrete type of that -// interface is not statically known (e.g. a non-inlined function call returning an interface type) -// or when multiple distinct concrete types are assigned. -// -// If n is statically known to be nil, this function returns a nil Type with isNil == true. -// However, if any concrete type is found, it is returned instead, even if n was assigned with nil. -func concreteType1(s *State, n ir.Node, seen map[*ir.Name]struct{}) (t *types.Type, isNil bool) { +// When n is known to be statically nil or a self-assignment is detected, in returns a sentinel [noType] type instead. +func concreteType1(s *State, n ir.Node, seen map[*ir.Name]struct{}) (outT *types.Type) { nn := n // for debug messages if concreteTypeDebug { defer func() { - base.Warn("concreteType1(%v) -> (%v;%v)", nn, t, isNil) + t := "&noType" + if outT != &noType { + t = outT.String() + } + base.Warn("concreteType1(%v) -> %v", nn, t) }() } @@ -215,7 +217,7 @@ func concreteType1(s *State, n ir.Node, seen map[*ir.Name]struct{}) (t *types.Ty } if !n.Type().IsInterface() { - return n.Type(), false + return n.Type() } switch n1 := n.(type) { @@ -249,12 +251,12 @@ func concreteType1(s *State, n ir.Node, seen map[*ir.Name]struct{}) (t *types.Ty } if n.Op() != ir.ONAME { - return nil, false + return nil } name := n.(*ir.Name).Canonical() if name.Class != ir.PAUTO { - return nil, false + return nil } if name.Op() != ir.ONAME { @@ -267,14 +269,14 @@ func concreteType1(s *State, n ir.Node, seen map[*ir.Name]struct{}) (t *types.Ty } if name.Addrtaken() { - return nil, false // conservatively assume it's reassigned with a different type indirectly + return nil // conservatively assume it's reassigned with a different type indirectly } if _, ok := seen[name]; ok { // Self assignment, treat it the same as a nil assignment. // In case this is the only assignment then we are not going to devirtualize anything. // In case there are other assignment, we still preserve the correct type. - return nil, true + return &noType } seen[name] = struct{}{} @@ -284,19 +286,18 @@ func concreteType1(s *State, n ir.Node, seen map[*ir.Name]struct{}) (t *types.Ty var typ *types.Type for _, v := range s.assignments(name) { - t := v.typ - if v.node != nil { - var isNil bool - t, isNil = concreteType1(s, v.node, seen) - if isNil { - if t != nil { - base.Fatalf("t = %v; want = ", t) - } + var t *types.Type + switch v := v.(type) { + case *types.Type: + t = v + case ir.Node: + t = concreteType1(s, v, seen) + if t == &noType { continue } } if t == nil || (typ != nil && !types.Identical(typ, t)) { - return nil, false + return nil } typ = t } @@ -305,27 +306,30 @@ func concreteType1(s *State, n ir.Node, seen map[*ir.Name]struct{}) (t *types.Ty if typ == nil { // Variable either declared with zero value, or only assigned with nil. - return nil, true + return &noType } - return typ, false + return typ } -// valOrTyp stores either node or a type that is assigned to a variable. -// Never both of these fields are populated. -// If both are nil, then either an interface type was assigned (e.g. a non-inlined -// function call returning an interface type, in such case we don't know the -// concrete type) or a basic type (i.e. int), which we know that does not have any -// methods, thus not possible to devirtualize. -type valOrTyp struct { - typ *types.Type - node ir.Node -} +// assignment can be one of: +// - nil - assignment to an interface type. +// - *types.Type - assignment to a concrete type (non-interface). +// - ir.Node - assignment to a ir.Node. +// +// In most cases assignment should be an [ir.Node], but in cases where we +// do not follow the data-flow, we return either a concrete type (*types.Type) or a nil. +// For example in range over a slice, if the slice elem is of an interface type, then we return +// a nil, otherwise the elem's concrete type (We do so because we do not analyze assignment to the +// slice being ranged-over). +type assignment any // State holds precomputed state for use in [StaticCall]. type State struct { - // ifaceAssignments stores all assignments to all interface variables. - ifaceAssignments map[*ir.Name][]valOrTyp + // ifaceAssignments maps interface variables to all their assignments + // defined inside functions stored in the analyzedFuncs set. + // Note: it does not include direct assignments to nil. + ifaceAssignments map[*ir.Name][]assignment // ifaceCallExprAssigns stores every [*ir.CallExpr], which has an interface // result, that is assigned to a variable. @@ -362,8 +366,8 @@ func (s *State) InlinedCall(fun *ir.Func, origCall *ir.CallExpr, inlinedCall *ir // Update assignments to reference the new ReturnVars of the inlined call. for _, ref := range refs { vt := &s.ifaceAssignments[ref.name][ref.valOrTypeIndex] - if vt.node != nil || vt.typ != nil { - base.Fatalf("unexpected non-empty valOrTyp") + if *vt != nil { + base.Fatalf("unexpected non-nil assignment") } if concreteTypeDebug { base.Warn( @@ -373,12 +377,12 @@ func (s *State) InlinedCall(fun *ir.Func, origCall *ir.CallExpr, inlinedCall *ir inlinedCall.ReturnVars[ref.returnIndex].Type(), ) } - *vt = valOrTyp{node: inlinedCall.ReturnVars[ref.returnIndex]} + *vt = inlinedCall.ReturnVars[ref.returnIndex] } } // assignments returns all assignments to n. -func (s *State) assignments(n *ir.Name) []valOrTyp { +func (s *State) assignments(n *ir.Name) []assignment { fun := n.Curfn if fun == nil { base.Fatalf("n.Curfn = ") @@ -394,7 +398,7 @@ func (s *State) assignments(n *ir.Name) []valOrTyp { base.Warn("concreteType(): analyzing assignments in %v func", fun) } if s.analyzedFuncs == nil { - s.ifaceAssignments = make(map[*ir.Name][]valOrTyp) + s.ifaceAssignments = make(map[*ir.Name][]assignment) s.ifaceCallExprAssigns = make(map[*ir.CallExpr][]ifaceAssignRef) s.analyzedFuncs = make(map[*ir.Func]struct{}) } @@ -408,7 +412,7 @@ func (s *State) assignments(n *ir.Name) []valOrTyp { // analyze analyzes every assignment to interface variables in nodes, updating [State]. func (s *State) analyze(nodes ir.Nodes) { - assign := func(name ir.Node, value valOrTyp) (*ir.Name, int) { + assign := func(name ir.Node, assignment assignment) (*ir.Name, int) { if name == nil || name.Op() != ir.ONAME || ir.IsBlank(name) { return nil, -1 } @@ -429,20 +433,26 @@ func (s *State) analyze(nodes ir.Nodes) { base.Fatalf("reassigned %v", n) } - // n is assigned with nil, we can safely ignore them, see [StaticCall]. - if ir.IsNil(value.node) { - return nil, -1 - } - - if value.typ != nil && value.typ.IsInterface() { - value.typ = nil + switch a := assignment.(type) { + case nil: + case *types.Type: + if a != nil && a.IsInterface() { + assignment = nil // non-concrete type + } + case ir.Node: + // nil assignment, we can safely ignore them, see [StaticCall]. + if ir.IsNil(a) { + return nil, -1 + } + default: + base.Fatalf("unexpected type: %v", assignment) } if concreteTypeDebug { - base.Warn("analyze(): assignment found %v = (%v;%v)", name, value.typ, value.node) + base.Warn("analyze(): assignment found %v = %v", name, assignment) } - s.ifaceAssignments[n] = append(s.ifaceAssignments[n], value) + s.ifaceAssignments[n] = append(s.ifaceAssignments[n], assignment) return n, len(s.ifaceAssignments[n]) - 1 } @@ -462,19 +472,19 @@ func (s *State) analyze(nodes ir.Nodes) { } if call, ok := rhs.(*ir.CallExpr); ok && call.Fun != nil { retTyp := call.Fun.Type().Results()[0].Type - n, idx := assign(n.X, valOrTyp{typ: retTyp}) + n, idx := assign(n.X, retTyp) if n != nil && retTyp.IsInterface() { s.ifaceCallExprAssigns[call] = append(s.ifaceCallExprAssigns[call], ifaceAssignRef{n, idx, 0}) } } else { - assign(n.X, valOrTyp{node: rhs}) + assign(n.X, rhs) } } case ir.OAS2: n := n.(*ir.AssignListStmt) for i, p := range n.Lhs { if n.Rhs[i] != nil { - assign(p, valOrTyp{node: n.Rhs[i]}) + assign(p, n.Rhs[i]) } } case ir.OAS2DOTTYPE: @@ -482,15 +492,15 @@ func (s *State) analyze(nodes ir.Nodes) { if n.Rhs[0] == nil { base.Fatalf("n.Rhs[0] == nil; n = %v", n) } - assign(n.Lhs[0], valOrTyp{node: n.Rhs[0]}) - assign(n.Lhs[1], valOrTyp{}) // boolean does not have methods to devirtualize + assign(n.Lhs[0], n.Rhs[0]) + assign(n.Lhs[1], nil) // boolean does not have methods to devirtualize case ir.OAS2MAPR, ir.OAS2RECV, ir.OSELRECV2: n := n.(*ir.AssignListStmt) if n.Rhs[0] == nil { base.Fatalf("n.Rhs[0] == nil; n = %v", n) } - assign(n.Lhs[0], valOrTyp{typ: n.Rhs[0].Type()}) - assign(n.Lhs[1], valOrTyp{}) // boolean does not have methods to devirtualize + assign(n.Lhs[0], n.Rhs[0].Type()) + assign(n.Lhs[1], nil) // boolean does not have methods to devirtualize case ir.OAS2FUNC: n := n.(*ir.AssignListStmt) rhs := n.Rhs[0] @@ -504,19 +514,19 @@ func (s *State) analyze(nodes ir.Nodes) { if call, ok := rhs.(*ir.CallExpr); ok { for i, p := range n.Lhs { retTyp := call.Fun.Type().Results()[i].Type - n, idx := assign(p, valOrTyp{typ: retTyp}) + n, idx := assign(p, retTyp) if n != nil && retTyp.IsInterface() { s.ifaceCallExprAssigns[call] = append(s.ifaceCallExprAssigns[call], ifaceAssignRef{n, idx, i}) } } } else if call, ok := rhs.(*ir.InlinedCallExpr); ok { for i, p := range n.Lhs { - assign(p, valOrTyp{node: call.ReturnVars[i]}) + assign(p, call.ReturnVars[i]) } } else { // TODO: can we reach here? for _, p := range n.Lhs { - assign(p, valOrTyp{}) + assign(p, nil) } } case ir.ORANGE: @@ -529,18 +539,18 @@ func (s *State) analyze(nodes ir.Nodes) { } if xTyp.IsArray() || xTyp.IsSlice() { - assign(n.Key, valOrTyp{}) // boolean - assign(n.Value, valOrTyp{typ: xTyp.Elem()}) + assign(n.Key, nil) // inteager does not have methods to devirtualize + assign(n.Value, xTyp.Elem()) } else if xTyp.IsChan() { - assign(n.Key, valOrTyp{typ: xTyp.Elem()}) + assign(n.Key, xTyp.Elem()) base.Assertf(n.Value == nil, "n.Value != nil in range over chan") } else if xTyp.IsMap() { - assign(n.Key, valOrTyp{typ: xTyp.Key()}) - assign(n.Value, valOrTyp{typ: xTyp.Elem()}) + assign(n.Key, xTyp.Key()) + assign(n.Value, xTyp.Elem()) } else if xTyp.IsInteger() || xTyp.IsString() { // Range over int/string, results do not have methods, so nothing to devirtualize. - assign(n.Key, valOrTyp{}) - assign(n.Value, valOrTyp{}) + assign(n.Key, nil) + assign(n.Value, nil) } else { // We will not reach here in case of an range-over-func, as it is // rewrtten to function calls in the noder package. @@ -554,7 +564,7 @@ func (s *State) analyze(nodes ir.Nodes) { base.Assert(guard.Tag == nil) continue } - assign(v.Var, valOrTyp{node: guard.X}) + assign(v.Var, guard.X) } } case ir.OCLOSURE: diff --git a/test/devirtualization.go b/test/devirtualization.go index 6f809957642857..9500ec3ff530ac 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -1262,3 +1262,15 @@ func testInvalidAsserts() { a.(any).(M).(*Impl).M() // ERROR "inlining call to \(\*Impl\).M" } } + +type namedBool bool + +func (namedBool) M() {} // ERROR "can inline namedBool.M$" + +func namedBoolTest() { + m := map[int]int{} // ERROR "map\[int\]int{} does not escape" + var ok namedBool + _, ok = m[5] + var i M = ok // ERROR "ok does not escape" + i.M() // ERROR "devirtualizing i.M to namedBool$" "inlining call to namedBool.M" +} From 2d1205731c246e18e66dd65694fff172c3e743e1 Mon Sep 17 00:00:00 2001 From: Mateusz Poliwczak Date: Tue, 11 Mar 2025 21:23:41 +0100 Subject: [PATCH 74/74] add noinline for newinliner Change-Id: I038a591a6b70864416e391b052ebb8b3a7f4710f --- test/devirtualization.go | 1 + 1 file changed, 1 insertion(+) diff --git a/test/devirtualization.go b/test/devirtualization.go index 9500ec3ff530ac..e3319052945e00 100644 --- a/test/devirtualization.go +++ b/test/devirtualization.go @@ -1267,6 +1267,7 @@ type namedBool bool func (namedBool) M() {} // ERROR "can inline namedBool.M$" +//go:noinline func namedBoolTest() { m := map[int]int{} // ERROR "map\[int\]int{} does not escape" var ok namedBool