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.
8 "cmd/compile/internal/base"
9 "cmd/compile/internal/ir"
10 "cmd/compile/internal/pgo"
11 "cmd/compile/internal/typecheck"
12 "cmd/compile/internal/types"
19 // These are the few constants that need to be initialized in order to use
20 // the types package without using the typecheck package by calling
21 // typecheck.InitUniverse() (the normal way to initialize the types package).
24 types.MaxWidth = 1 << 50
25 typecheck.InitUniverse()
26 base.Ctxt = &obj.Link{}
27 base.Debug.PGODebug = 3
30 func makePos(b *src.PosBase, line, col uint) src.XPos {
31 return base.Ctxt.PosTable.XPos(src.MakePos(b, line, col))
34 type profileBuilder struct {
38 func newProfileBuilder() *profileBuilder {
39 // findHotConcreteCallee only uses pgo.Profile.WeightedCG, so we're
40 // going to take a shortcut and only construct that.
41 return &profileBuilder{
43 WeightedCG: &pgo.IRGraph{
44 IRNodes: make(map[string]*pgo.IRNode),
50 // Profile returns the constructed profile.
51 func (p *profileBuilder) Profile() *pgo.Profile {
55 // NewNode creates a new IRNode and adds it to the profile.
57 // fn may be nil, in which case the node will set LinkerSymbolName.
58 func (p *profileBuilder) NewNode(name string, fn *ir.Func) *pgo.IRNode {
60 OutEdges: make(map[pgo.NamedCallEdge]*pgo.IREdge),
65 n.LinkerSymbolName = name
67 p.p.WeightedCG.IRNodes[name] = n
71 // Add a new call edge from caller to callee.
72 func addEdge(caller, callee *pgo.IRNode, offset int, weight int64) {
73 namedEdge := pgo.NamedCallEdge{
74 CallerName: caller.Name(),
75 CalleeName: callee.Name(),
76 CallSiteOffset: offset,
78 irEdge := &pgo.IREdge{
81 CallSiteOffset: offset,
84 caller.OutEdges[namedEdge] = irEdge
87 // Create a new struct type named structName with a method named methName and
89 func makeStructWithMethod(pkg *types.Pkg, structName, methName string) *ir.Func {
90 // type structName struct{}
91 structType := types.NewStruct(nil)
93 // func (structName) methodName()
94 recv := types.NewField(src.NoXPos, typecheck.Lookup(structName), structType)
95 sig := types.NewSignature(recv, nil, nil)
96 fn := ir.NewFunc(src.NoXPos, src.NoXPos, pkg.Lookup(structName+"."+methName), sig)
98 // Add the method to the struct.
99 structType.SetMethods([]*types.Field{types.NewField(src.NoXPos, typecheck.Lookup(methName), sig)})
104 func TestFindHotConcreteInterfaceCallee(t *testing.T) {
105 p := newProfileBuilder()
107 pkgFoo := types.NewPkg("example.com/foo", "foo")
108 basePos := src.NewFileBase("foo.go", "/foo.go")
111 // Caller start line.
114 // The line offset of the call we care about.
117 // The line offset of some other call we don't care about.
121 // type IFace interface {
124 fooSig := types.NewSignature(types.FakeRecv(), nil, nil)
125 method := types.NewField(src.NoXPos, typecheck.Lookup("Foo"), fooSig)
126 iface := types.NewInterface([]*types.Field{method})
128 callerFn := ir.NewFunc(makePos(basePos, callerStart, 1), src.NoXPos, pkgFoo.Lookup("Caller"), types.NewSignature(nil, nil, nil))
130 hotCalleeFn := makeStructWithMethod(pkgFoo, "HotCallee", "Foo")
131 coldCalleeFn := makeStructWithMethod(pkgFoo, "ColdCallee", "Foo")
132 wrongLineCalleeFn := makeStructWithMethod(pkgFoo, "WrongLineCallee", "Foo")
133 wrongMethodCalleeFn := makeStructWithMethod(pkgFoo, "WrongMethodCallee", "Bar")
135 callerNode := p.NewNode("example.com/foo.Caller", callerFn)
136 hotCalleeNode := p.NewNode("example.com/foo.HotCallee.Foo", hotCalleeFn)
137 coldCalleeNode := p.NewNode("example.com/foo.ColdCallee.Foo", coldCalleeFn)
138 wrongLineCalleeNode := p.NewNode("example.com/foo.WrongCalleeLine.Foo", wrongLineCalleeFn)
139 wrongMethodCalleeNode := p.NewNode("example.com/foo.WrongCalleeMethod.Foo", wrongMethodCalleeFn)
141 hotMissingCalleeNode := p.NewNode("example.com/bar.HotMissingCallee.Foo", nil)
143 addEdge(callerNode, wrongLineCalleeNode, wrongCallOffset, 100) // Really hot, but wrong line.
144 addEdge(callerNode, wrongMethodCalleeNode, callOffset, 100) // Really hot, but wrong method type.
145 addEdge(callerNode, hotCalleeNode, callOffset, 10)
146 addEdge(callerNode, coldCalleeNode, callOffset, 1)
148 // Equal weight, but IR missing.
150 // N.B. example.com/bar sorts lexicographically before example.com/foo,
151 // so if the IR availability of hotCalleeNode doesn't get precedence,
152 // this would be mistakenly selected.
153 addEdge(callerNode, hotMissingCalleeNode, callOffset, 10)
156 sel := typecheck.NewMethodExpr(src.NoXPos, iface, typecheck.Lookup("Foo"))
157 call := ir.NewCallExpr(makePos(basePos, callerStart+callOffset, 1), ir.OCALLINTER, sel, nil)
159 gotFn, gotWeight := findHotConcreteInterfaceCallee(p.Profile(), callerFn, call)
160 if gotFn != hotCalleeFn {
161 t.Errorf("findHotConcreteInterfaceCallee func got %v want %v", gotFn, hotCalleeFn)
164 t.Errorf("findHotConcreteInterfaceCallee weight got %v want 10", gotWeight)
168 func TestFindHotConcreteFunctionCallee(t *testing.T) {
169 // TestFindHotConcreteInterfaceCallee already covered basic weight
170 // comparisons, which is shared logic. Here we just test type signature
173 p := newProfileBuilder()
175 pkgFoo := types.NewPkg("example.com/foo", "foo")
176 basePos := src.NewFileBase("foo.go", "/foo.go")
179 // Caller start line.
182 // The line offset of the call we care about.
186 callerFn := ir.NewFunc(makePos(basePos, callerStart, 1), src.NoXPos, pkgFoo.Lookup("Caller"), types.NewSignature(nil, nil, nil))
189 hotCalleeFn := ir.NewFunc(src.NoXPos, src.NoXPos, pkgFoo.Lookup("HotCallee"), types.NewSignature(nil, nil, nil))
191 // func WrongCallee() bool
192 wrongCalleeFn := ir.NewFunc(src.NoXPos, src.NoXPos, pkgFoo.Lookup("WrongCallee"), types.NewSignature(nil, nil,
194 types.NewField(src.NoXPos, nil, types.Types[types.TBOOL]),
198 callerNode := p.NewNode("example.com/foo.Caller", callerFn)
199 hotCalleeNode := p.NewNode("example.com/foo.HotCallee", hotCalleeFn)
200 wrongCalleeNode := p.NewNode("example.com/foo.WrongCallee", wrongCalleeFn)
202 addEdge(callerNode, wrongCalleeNode, callOffset, 100) // Really hot, but wrong function type.
203 addEdge(callerNode, hotCalleeNode, callOffset, 10)
206 name := ir.NewNameAt(src.NoXPos, typecheck.Lookup("fn"), types.NewSignature(nil, nil, nil))
208 call := ir.NewCallExpr(makePos(basePos, callerStart+callOffset, 1), ir.OCALL, name, nil)
210 gotFn, gotWeight := findHotConcreteFunctionCallee(p.Profile(), callerFn, call)
211 if gotFn != hotCalleeFn {
212 t.Errorf("findHotConcreteFunctionCallee func got %v want %v", gotFn, hotCalleeFn)
215 t.Errorf("findHotConcreteFunctionCallee weight got %v want 10", gotWeight)