]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/compile/internal/devirtualize/pgo_test.go
cmd/compile: initial function value devirtualization
[gostls13.git] / src / cmd / compile / internal / devirtualize / pgo_test.go
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         "cmd/compile/internal/base"
9         "cmd/compile/internal/ir"
10         "cmd/compile/internal/pgo"
11         "cmd/compile/internal/typecheck"
12         "cmd/compile/internal/types"
13         "cmd/internal/obj"
14         "cmd/internal/src"
15         "testing"
16 )
17
18 func init() {
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).
22         types.PtrSize = 8
23         types.RegSize = 8
24         types.MaxWidth = 1 << 50
25         typecheck.InitUniverse()
26         base.Ctxt = &obj.Link{}
27         base.Debug.PGODebug = 3
28 }
29
30 func makePos(b *src.PosBase, line, col uint) src.XPos {
31         return base.Ctxt.PosTable.XPos(src.MakePos(b, line, col))
32 }
33
34 type profileBuilder struct {
35         p *pgo.Profile
36 }
37
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{
42                 p: &pgo.Profile{
43                         WeightedCG: &pgo.IRGraph{
44                                 IRNodes: make(map[string]*pgo.IRNode),
45                         },
46                 },
47         }
48 }
49
50 // Profile returns the constructed profile.
51 func (p *profileBuilder) Profile() *pgo.Profile {
52         return p.p
53 }
54
55 // NewNode creates a new IRNode and adds it to the profile.
56 //
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 {
59         n := &pgo.IRNode{
60                 OutEdges: make(map[pgo.NamedCallEdge]*pgo.IREdge),
61         }
62         if fn != nil {
63                 n.AST = fn
64         } else {
65                 n.LinkerSymbolName = name
66         }
67         p.p.WeightedCG.IRNodes[name] = n
68         return n
69 }
70
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,
77         }
78         irEdge := &pgo.IREdge{
79                 Src:            caller,
80                 Dst:            callee,
81                 CallSiteOffset: offset,
82                 Weight:         weight,
83         }
84         caller.OutEdges[namedEdge] = irEdge
85 }
86
87 // Create a new struct type named structName with a method named methName and
88 // return the method.
89 func makeStructWithMethod(pkg *types.Pkg, structName, methName string) *ir.Func {
90         // type structName struct{}
91         structType := types.NewStruct(nil)
92
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)
97
98         // Add the method to the struct.
99         structType.SetMethods([]*types.Field{types.NewField(src.NoXPos, typecheck.Lookup(methName), sig)})
100
101         return fn
102 }
103
104 func TestFindHotConcreteInterfaceCallee(t *testing.T) {
105         p := newProfileBuilder()
106
107         pkgFoo := types.NewPkg("example.com/foo", "foo")
108         basePos := src.NewFileBase("foo.go", "/foo.go")
109
110         const (
111                 // Caller start line.
112                 callerStart = 42
113
114                 // The line offset of the call we care about.
115                 callOffset = 1
116
117                 // The line offset of some other call we don't care about.
118                 wrongCallOffset = 2
119         )
120
121         // type IFace interface {
122         //      Foo()
123         // }
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})
127
128         callerFn := ir.NewFunc(makePos(basePos, callerStart, 1), src.NoXPos, pkgFoo.Lookup("Caller"), types.NewSignature(nil, nil, nil))
129
130         hotCalleeFn := makeStructWithMethod(pkgFoo, "HotCallee", "Foo")
131         coldCalleeFn := makeStructWithMethod(pkgFoo, "ColdCallee", "Foo")
132         wrongLineCalleeFn := makeStructWithMethod(pkgFoo, "WrongLineCallee", "Foo")
133         wrongMethodCalleeFn := makeStructWithMethod(pkgFoo, "WrongMethodCallee", "Bar")
134
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)
140
141         hotMissingCalleeNode := p.NewNode("example.com/bar.HotMissingCallee.Foo", nil)
142
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)
147
148         // Equal weight, but IR missing.
149         //
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)
154
155         // IFace.Foo()
156         sel := typecheck.NewMethodExpr(src.NoXPos, iface, typecheck.Lookup("Foo"))
157         call := ir.NewCallExpr(makePos(basePos, callerStart+callOffset, 1), ir.OCALLINTER, sel, nil)
158
159         gotFn, gotWeight := findHotConcreteInterfaceCallee(p.Profile(), callerFn, call)
160         if gotFn != hotCalleeFn {
161                 t.Errorf("findHotConcreteInterfaceCallee func got %v want %v", gotFn, hotCalleeFn)
162         }
163         if gotWeight != 10 {
164                 t.Errorf("findHotConcreteInterfaceCallee weight got %v want 10", gotWeight)
165         }
166 }
167
168 func TestFindHotConcreteFunctionCallee(t *testing.T) {
169         // TestFindHotConcreteInterfaceCallee already covered basic weight
170         // comparisons, which is shared logic. Here we just test type signature
171         // disambiguation.
172
173         p := newProfileBuilder()
174
175         pkgFoo := types.NewPkg("example.com/foo", "foo")
176         basePos := src.NewFileBase("foo.go", "/foo.go")
177
178         const (
179                 // Caller start line.
180                 callerStart = 42
181
182                 // The line offset of the call we care about.
183                 callOffset = 1
184         )
185
186         callerFn := ir.NewFunc(makePos(basePos, callerStart, 1), src.NoXPos, pkgFoo.Lookup("Caller"), types.NewSignature(nil, nil, nil))
187
188         // func HotCallee()
189         hotCalleeFn := ir.NewFunc(src.NoXPos, src.NoXPos, pkgFoo.Lookup("HotCallee"), types.NewSignature(nil, nil, nil))
190
191         // func WrongCallee() bool
192         wrongCalleeFn := ir.NewFunc(src.NoXPos, src.NoXPos, pkgFoo.Lookup("WrongCallee"), types.NewSignature(nil, nil,
193                 []*types.Field{
194                         types.NewField(src.NoXPos, nil, types.Types[types.TBOOL]),
195                 },
196         ))
197
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)
201
202         addEdge(callerNode, wrongCalleeNode, callOffset, 100) // Really hot, but wrong function type.
203         addEdge(callerNode, hotCalleeNode, callOffset, 10)
204
205         // var fn func()
206         name := ir.NewNameAt(src.NoXPos, typecheck.Lookup("fn"), types.NewSignature(nil, nil, nil))
207         // fn()
208         call := ir.NewCallExpr(makePos(basePos, callerStart+callOffset, 1), ir.OCALL, name, nil)
209
210         gotFn, gotWeight := findHotConcreteFunctionCallee(p.Profile(), callerFn, call)
211         if gotFn != hotCalleeFn {
212                 t.Errorf("findHotConcreteFunctionCallee func got %v want %v", gotFn, hotCalleeFn)
213         }
214         if gotWeight != 10 {
215                 t.Errorf("findHotConcreteFunctionCallee weight got %v want 10", gotWeight)
216         }
217 }