mirror of https://github.com/golang/go.git
220 lines
7.2 KiB
Go
220 lines
7.2 KiB
Go
// 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)
|
|
}
|
|
}
|