| // Copyright 2023 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 devirtualize |
| |
| import ( |
| "cmd/compile/internal/base" |
| "cmd/compile/internal/ir" |
| "cmd/compile/internal/pgoir" |
| "cmd/compile/internal/typecheck" |
| "cmd/compile/internal/types" |
| "cmd/internal/obj" |
| "cmd/internal/pgo" |
| "cmd/internal/src" |
| "cmd/internal/sys" |
| "testing" |
| ) |
| |
| func init() { |
| // These are the few constants that need to be initialized in order to use |
| // the types package without using the typecheck package by calling |
| // typecheck.InitUniverse() (the normal way to initialize the types package). |
| types.PtrSize = 8 |
| types.RegSize = 8 |
| types.MaxWidth = 1 << 50 |
| base.Ctxt = &obj.Link{Arch: &obj.LinkArch{Arch: &sys.Arch{Alignment: 1, CanMergeLoads: true}}} |
| typecheck.InitUniverse() |
| base.Debug.PGODebug = 3 |
| } |
| |
| func makePos(b *src.PosBase, line, col uint) src.XPos { |
| return base.Ctxt.PosTable.XPos(src.MakePos(b, line, col)) |
| } |
| |
| type profileBuilder struct { |
| p *pgoir.Profile |
| } |
| |
| func newProfileBuilder() *profileBuilder { |
| // findHotConcreteCallee only uses pgoir.Profile.WeightedCG, so we're |
| // going to take a shortcut and only construct that. |
| return &profileBuilder{ |
| p: &pgoir.Profile{ |
| WeightedCG: &pgoir.IRGraph{ |
| IRNodes: make(map[string]*pgoir.IRNode), |
| }, |
| }, |
| } |
| } |
| |
| // Profile returns the constructed profile. |
| func (p *profileBuilder) Profile() *pgoir.Profile { |
| return p.p |
| } |
| |
| // NewNode creates a new IRNode and adds it to the profile. |
| // |
| // fn may be nil, in which case the node will set LinkerSymbolName. |
| func (p *profileBuilder) NewNode(name string, fn *ir.Func) *pgoir.IRNode { |
| n := &pgoir.IRNode{ |
| OutEdges: make(map[pgo.NamedCallEdge]*pgoir.IREdge), |
| } |
| if fn != nil { |
| n.AST = fn |
| } else { |
| n.LinkerSymbolName = name |
| } |
| p.p.WeightedCG.IRNodes[name] = n |
| return n |
| } |
| |
| // Add a new call edge from caller to callee. |
| func addEdge(caller, callee *pgoir.IRNode, offset int, weight int64) { |
| namedEdge := pgo.NamedCallEdge{ |
| CallerName: caller.Name(), |
| CalleeName: callee.Name(), |
| CallSiteOffset: offset, |
| } |
| irEdge := &pgoir.IREdge{ |
| Src: caller, |
| Dst: callee, |
| CallSiteOffset: offset, |
| Weight: weight, |
| } |
| caller.OutEdges[namedEdge] = irEdge |
| } |
| |
| // Create a new struct type named structName with a method named methName and |
| // return the method. |
| func makeStructWithMethod(pkg *types.Pkg, structName, methName string) *ir.Func { |
| // type structName struct{} |
| structType := types.NewStruct(nil) |
| |
| // func (structName) methodName() |
| recv := types.NewField(src.NoXPos, typecheck.Lookup(structName), structType) |
| sig := types.NewSignature(recv, nil, nil) |
| fn := ir.NewFunc(src.NoXPos, src.NoXPos, pkg.Lookup(structName+"."+methName), sig) |
| |
| // Add the method to the struct. |
| structType.SetMethods([]*types.Field{types.NewField(src.NoXPos, typecheck.Lookup(methName), sig)}) |
| |
| return fn |
| } |
| |
| func TestFindHotConcreteInterfaceCallee(t *testing.T) { |
| p := newProfileBuilder() |
| |
| pkgFoo := types.NewPkg("example.com/foo", "foo") |
| basePos := src.NewFileBase("foo.go", "/foo.go") |
| |
| const ( |
| // Caller start line. |
| callerStart = 42 |
| |
| // The line offset of the call we care about. |
| callOffset = 1 |
| |
| // The line offset of some other call we don't care about. |
| wrongCallOffset = 2 |
| ) |
| |
| // type IFace interface { |
| // Foo() |
| // } |
| fooSig := types.NewSignature(types.FakeRecv(), nil, nil) |
| method := types.NewField(src.NoXPos, typecheck.Lookup("Foo"), fooSig) |
| iface := types.NewInterface([]*types.Field{method}) |
| |
| callerFn := ir.NewFunc(makePos(basePos, callerStart, 1), src.NoXPos, pkgFoo.Lookup("Caller"), types.NewSignature(nil, nil, nil)) |
| |
| hotCalleeFn := makeStructWithMethod(pkgFoo, "HotCallee", "Foo") |
| coldCalleeFn := makeStructWithMethod(pkgFoo, "ColdCallee", "Foo") |
| wrongLineCalleeFn := makeStructWithMethod(pkgFoo, "WrongLineCallee", "Foo") |
| wrongMethodCalleeFn := makeStructWithMethod(pkgFoo, "WrongMethodCallee", "Bar") |
| |
| callerNode := p.NewNode("example.com/foo.Caller", callerFn) |
| hotCalleeNode := p.NewNode("example.com/foo.HotCallee.Foo", hotCalleeFn) |
| coldCalleeNode := p.NewNode("example.com/foo.ColdCallee.Foo", coldCalleeFn) |
| wrongLineCalleeNode := p.NewNode("example.com/foo.WrongCalleeLine.Foo", wrongLineCalleeFn) |
| wrongMethodCalleeNode := p.NewNode("example.com/foo.WrongCalleeMethod.Foo", wrongMethodCalleeFn) |
| |
| hotMissingCalleeNode := p.NewNode("example.com/bar.HotMissingCallee.Foo", nil) |
| |
| addEdge(callerNode, wrongLineCalleeNode, wrongCallOffset, 100) // Really hot, but wrong line. |
| addEdge(callerNode, wrongMethodCalleeNode, callOffset, 100) // Really hot, but wrong method type. |
| addEdge(callerNode, hotCalleeNode, callOffset, 10) |
| addEdge(callerNode, coldCalleeNode, callOffset, 1) |
| |
| // Equal weight, but IR missing. |
| // |
| // N.B. example.com/bar sorts lexicographically before example.com/foo, |
| // so if the IR availability of hotCalleeNode doesn't get precedence, |
| // this would be mistakenly selected. |
| addEdge(callerNode, hotMissingCalleeNode, callOffset, 10) |
| |
| // IFace.Foo() |
| sel := typecheck.NewMethodExpr(src.NoXPos, iface, typecheck.Lookup("Foo")) |
| call := ir.NewCallExpr(makePos(basePos, callerStart+callOffset, 1), ir.OCALLINTER, sel, nil) |
| |
| gotFn, gotWeight := findHotConcreteInterfaceCallee(p.Profile(), callerFn, call) |
| if gotFn != hotCalleeFn { |
| t.Errorf("findHotConcreteInterfaceCallee func got %v want %v", gotFn, hotCalleeFn) |
| } |
| if gotWeight != 10 { |
| t.Errorf("findHotConcreteInterfaceCallee weight got %v want 10", gotWeight) |
| } |
| } |
| |
| func TestFindHotConcreteFunctionCallee(t *testing.T) { |
| // TestFindHotConcreteInterfaceCallee already covered basic weight |
| // comparisons, which is shared logic. Here we just test type signature |
| // disambiguation. |
| |
| p := newProfileBuilder() |
| |
| pkgFoo := types.NewPkg("example.com/foo", "foo") |
| basePos := src.NewFileBase("foo.go", "/foo.go") |
| |
| const ( |
| // Caller start line. |
| callerStart = 42 |
| |
| // The line offset of the call we care about. |
| callOffset = 1 |
| ) |
| |
| callerFn := ir.NewFunc(makePos(basePos, callerStart, 1), src.NoXPos, pkgFoo.Lookup("Caller"), types.NewSignature(nil, nil, nil)) |
| |
| // func HotCallee() |
| hotCalleeFn := ir.NewFunc(src.NoXPos, src.NoXPos, pkgFoo.Lookup("HotCallee"), types.NewSignature(nil, nil, nil)) |
| |
| // func WrongCallee() bool |
| wrongCalleeFn := ir.NewFunc(src.NoXPos, src.NoXPos, pkgFoo.Lookup("WrongCallee"), types.NewSignature(nil, nil, |
| []*types.Field{ |
| types.NewField(src.NoXPos, nil, types.Types[types.TBOOL]), |
| }, |
| )) |
| |
| callerNode := p.NewNode("example.com/foo.Caller", callerFn) |
| hotCalleeNode := p.NewNode("example.com/foo.HotCallee", hotCalleeFn) |
| wrongCalleeNode := p.NewNode("example.com/foo.WrongCallee", wrongCalleeFn) |
| |
| addEdge(callerNode, wrongCalleeNode, callOffset, 100) // Really hot, but wrong function type. |
| addEdge(callerNode, hotCalleeNode, callOffset, 10) |
| |
| // var fn func() |
| name := ir.NewNameAt(src.NoXPos, typecheck.Lookup("fn"), types.NewSignature(nil, nil, nil)) |
| // fn() |
| call := ir.NewCallExpr(makePos(basePos, callerStart+callOffset, 1), ir.OCALL, name, nil) |
| |
| gotFn, gotWeight := findHotConcreteFunctionCallee(p.Profile(), callerFn, call) |
| if gotFn != hotCalleeFn { |
| t.Errorf("findHotConcreteFunctionCallee func got %v want %v", gotFn, hotCalleeFn) |
| } |
| if gotWeight != 10 { |
| t.Errorf("findHotConcreteFunctionCallee weight got %v want 10", gotWeight) |
| } |
| } |