github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/devirtualize/pgo_test.go (about) 1 // Copyright 2023 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package devirtualize 6 7 import ( 8 "testing" 9 10 "github.com/go-asm/go/cmd/compile/base" 11 "github.com/go-asm/go/cmd/compile/ir" 12 "github.com/go-asm/go/cmd/compile/pgo" 13 "github.com/go-asm/go/cmd/compile/typecheck" 14 "github.com/go-asm/go/cmd/compile/types" 15 "github.com/go-asm/go/cmd/obj" 16 "github.com/go-asm/go/cmd/src" 17 ) 18 19 func init() { 20 // These are the few constants that need to be initialized in order to use 21 // the types package without using the typecheck package by calling 22 // typecheck.InitUniverse() (the normal way to initialize the types package). 23 types.PtrSize = 8 24 types.RegSize = 8 25 types.MaxWidth = 1 << 50 26 typecheck.InitUniverse() 27 base.Ctxt = &obj.Link{} 28 base.Debug.PGODebug = 3 29 } 30 31 func makePos(b *src.PosBase, line, col uint) src.XPos { 32 return base.Ctxt.PosTable.XPos(src.MakePos(b, line, col)) 33 } 34 35 type profileBuilder struct { 36 p *pgo.Profile 37 } 38 39 func newProfileBuilder() *profileBuilder { 40 // findHotConcreteCallee only uses pgo.Profile.WeightedCG, so we're 41 // going to take a shortcut and only construct that. 42 return &profileBuilder{ 43 p: &pgo.Profile{ 44 WeightedCG: &pgo.IRGraph{ 45 IRNodes: make(map[string]*pgo.IRNode), 46 }, 47 }, 48 } 49 } 50 51 // Profile returns the constructed profile. 52 func (p *profileBuilder) Profile() *pgo.Profile { 53 return p.p 54 } 55 56 // NewNode creates a new IRNode and adds it to the profile. 57 // 58 // fn may be nil, in which case the node will set LinkerSymbolName. 59 func (p *profileBuilder) NewNode(name string, fn *ir.Func) *pgo.IRNode { 60 n := &pgo.IRNode{ 61 OutEdges: make(map[pgo.NamedCallEdge]*pgo.IREdge), 62 } 63 if fn != nil { 64 n.AST = fn 65 } else { 66 n.LinkerSymbolName = name 67 } 68 p.p.WeightedCG.IRNodes[name] = n 69 return n 70 } 71 72 // Add a new call edge from caller to callee. 73 func addEdge(caller, callee *pgo.IRNode, offset int, weight int64) { 74 namedEdge := pgo.NamedCallEdge{ 75 CallerName: caller.Name(), 76 CalleeName: callee.Name(), 77 CallSiteOffset: offset, 78 } 79 irEdge := &pgo.IREdge{ 80 Src: caller, 81 Dst: callee, 82 CallSiteOffset: offset, 83 Weight: weight, 84 } 85 caller.OutEdges[namedEdge] = irEdge 86 } 87 88 // Create a new struct type named structName with a method named methName and 89 // return the method. 90 func makeStructWithMethod(pkg *types.Pkg, structName, methName string) *ir.Func { 91 // type structName struct{} 92 structType := types.NewStruct(nil) 93 94 // func (structName) methodName() 95 recv := types.NewField(src.NoXPos, typecheck.Lookup(structName), structType) 96 sig := types.NewSignature(recv, nil, nil) 97 fn := ir.NewFunc(src.NoXPos, src.NoXPos, pkg.Lookup(structName+"."+methName), sig) 98 99 // Add the method to the struct. 100 structType.SetMethods([]*types.Field{types.NewField(src.NoXPos, typecheck.Lookup(methName), sig)}) 101 102 return fn 103 } 104 105 func TestFindHotConcreteInterfaceCallee(t *testing.T) { 106 p := newProfileBuilder() 107 108 pkgFoo := types.NewPkg("example.com/foo", "foo") 109 basePos := src.NewFileBase("foo.go", "/foo.go") 110 111 const ( 112 // Caller start line. 113 callerStart = 42 114 115 // The line offset of the call we care about. 116 callOffset = 1 117 118 // The line offset of some other call we don't care about. 119 wrongCallOffset = 2 120 ) 121 122 // type IFace interface { 123 // Foo() 124 // } 125 fooSig := types.NewSignature(types.FakeRecv(), nil, nil) 126 method := types.NewField(src.NoXPos, typecheck.Lookup("Foo"), fooSig) 127 iface := types.NewInterface([]*types.Field{method}) 128 129 callerFn := ir.NewFunc(makePos(basePos, callerStart, 1), src.NoXPos, pkgFoo.Lookup("Caller"), types.NewSignature(nil, nil, nil)) 130 131 hotCalleeFn := makeStructWithMethod(pkgFoo, "HotCallee", "Foo") 132 coldCalleeFn := makeStructWithMethod(pkgFoo, "ColdCallee", "Foo") 133 wrongLineCalleeFn := makeStructWithMethod(pkgFoo, "WrongLineCallee", "Foo") 134 wrongMethodCalleeFn := makeStructWithMethod(pkgFoo, "WrongMethodCallee", "Bar") 135 136 callerNode := p.NewNode("example.com/foo.Caller", callerFn) 137 hotCalleeNode := p.NewNode("example.com/foo.HotCallee.Foo", hotCalleeFn) 138 coldCalleeNode := p.NewNode("example.com/foo.ColdCallee.Foo", coldCalleeFn) 139 wrongLineCalleeNode := p.NewNode("example.com/foo.WrongCalleeLine.Foo", wrongLineCalleeFn) 140 wrongMethodCalleeNode := p.NewNode("example.com/foo.WrongCalleeMethod.Foo", wrongMethodCalleeFn) 141 142 hotMissingCalleeNode := p.NewNode("example.com/bar.HotMissingCallee.Foo", nil) 143 144 addEdge(callerNode, wrongLineCalleeNode, wrongCallOffset, 100) // Really hot, but wrong line. 145 addEdge(callerNode, wrongMethodCalleeNode, callOffset, 100) // Really hot, but wrong method type. 146 addEdge(callerNode, hotCalleeNode, callOffset, 10) 147 addEdge(callerNode, coldCalleeNode, callOffset, 1) 148 149 // Equal weight, but IR missing. 150 // 151 // N.B. example.com/bar sorts lexicographically before example.com/foo, 152 // so if the IR availability of hotCalleeNode doesn't get precedence, 153 // this would be mistakenly selected. 154 addEdge(callerNode, hotMissingCalleeNode, callOffset, 10) 155 156 // IFace.Foo() 157 sel := typecheck.NewMethodExpr(src.NoXPos, iface, typecheck.Lookup("Foo")) 158 call := ir.NewCallExpr(makePos(basePos, callerStart+callOffset, 1), ir.OCALLINTER, sel, nil) 159 160 gotFn, gotWeight := findHotConcreteInterfaceCallee(p.Profile(), callerFn, call) 161 if gotFn != hotCalleeFn { 162 t.Errorf("findHotConcreteInterfaceCallee func got %v want %v", gotFn, hotCalleeFn) 163 } 164 if gotWeight != 10 { 165 t.Errorf("findHotConcreteInterfaceCallee weight got %v want 10", gotWeight) 166 } 167 } 168 169 func TestFindHotConcreteFunctionCallee(t *testing.T) { 170 // TestFindHotConcreteInterfaceCallee already covered basic weight 171 // comparisons, which is shared logic. Here we just test type signature 172 // disambiguation. 173 174 p := newProfileBuilder() 175 176 pkgFoo := types.NewPkg("example.com/foo", "foo") 177 basePos := src.NewFileBase("foo.go", "/foo.go") 178 179 const ( 180 // Caller start line. 181 callerStart = 42 182 183 // The line offset of the call we care about. 184 callOffset = 1 185 ) 186 187 callerFn := ir.NewFunc(makePos(basePos, callerStart, 1), src.NoXPos, pkgFoo.Lookup("Caller"), types.NewSignature(nil, nil, nil)) 188 189 // func HotCallee() 190 hotCalleeFn := ir.NewFunc(src.NoXPos, src.NoXPos, pkgFoo.Lookup("HotCallee"), types.NewSignature(nil, nil, nil)) 191 192 // func WrongCallee() bool 193 wrongCalleeFn := ir.NewFunc(src.NoXPos, src.NoXPos, pkgFoo.Lookup("WrongCallee"), types.NewSignature(nil, nil, 194 []*types.Field{ 195 types.NewField(src.NoXPos, nil, types.Types[types.TBOOL]), 196 }, 197 )) 198 199 callerNode := p.NewNode("example.com/foo.Caller", callerFn) 200 hotCalleeNode := p.NewNode("example.com/foo.HotCallee", hotCalleeFn) 201 wrongCalleeNode := p.NewNode("example.com/foo.WrongCallee", wrongCalleeFn) 202 203 addEdge(callerNode, wrongCalleeNode, callOffset, 100) // Really hot, but wrong function type. 204 addEdge(callerNode, hotCalleeNode, callOffset, 10) 205 206 // var fn func() 207 name := ir.NewNameAt(src.NoXPos, typecheck.Lookup("fn"), types.NewSignature(nil, nil, nil)) 208 // fn() 209 call := ir.NewCallExpr(makePos(basePos, callerStart+callOffset, 1), ir.OCALL, name, nil) 210 211 gotFn, gotWeight := findHotConcreteFunctionCallee(p.Profile(), callerFn, call) 212 if gotFn != hotCalleeFn { 213 t.Errorf("findHotConcreteFunctionCallee func got %v want %v", gotFn, hotCalleeFn) 214 } 215 if gotWeight != 10 { 216 t.Errorf("findHotConcreteFunctionCallee weight got %v want 10", gotWeight) 217 } 218 }