Source file 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  }
   218  

View as plain text