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/pgoir"
    11  	"cmd/compile/internal/typecheck"
    12  	"cmd/compile/internal/types"
    13  	"cmd/internal/obj"
    14  	"cmd/internal/pgo"
    15  	"cmd/internal/src"
    16  	"testing"
    17  )
    18  
    19  func init() {
    20  	// These are the few constants that need to be initialized in order to use
    21  	// the types package without using the typecheck package by calling
    22  	// typecheck.InitUniverse() (the normal way to initialize the types package).
    23  	types.PtrSize = 8
    24  	types.RegSize = 8
    25  	types.MaxWidth = 1 << 50
    26  	typecheck.InitUniverse()
    27  	base.Ctxt = &obj.Link{}
    28  	base.Debug.PGODebug = 3
    29  }
    30  
    31  func makePos(b *src.PosBase, line, col uint) src.XPos {
    32  	return base.Ctxt.PosTable.XPos(src.MakePos(b, line, col))
    33  }
    34  
    35  type profileBuilder struct {
    36  	p *pgoir.Profile
    37  }
    38  
    39  func newProfileBuilder() *profileBuilder {
    40  	// findHotConcreteCallee only uses pgoir.Profile.WeightedCG, so we're
    41  	// going to take a shortcut and only construct that.
    42  	return &profileBuilder{
    43  		p: &pgoir.Profile{
    44  			WeightedCG: &pgoir.IRGraph{
    45  				IRNodes: make(map[string]*pgoir.IRNode),
    46  			},
    47  		},
    48  	}
    49  }
    50  
    51  // Profile returns the constructed profile.
    52  func (p *profileBuilder) Profile() *pgoir.Profile {
    53  	return p.p
    54  }
    55  
    56  // NewNode creates a new IRNode and adds it to the profile.
    57  //
    58  // fn may be nil, in which case the node will set LinkerSymbolName.
    59  func (p *profileBuilder) NewNode(name string, fn *ir.Func) *pgoir.IRNode {
    60  	n := &pgoir.IRNode{
    61  		OutEdges: make(map[pgo.NamedCallEdge]*pgoir.IREdge),
    62  	}
    63  	if fn != nil {
    64  		n.AST = fn
    65  	} else {
    66  		n.LinkerSymbolName = name
    67  	}
    68  	p.p.WeightedCG.IRNodes[name] = n
    69  	return n
    70  }
    71  
    72  // Add a new call edge from caller to callee.
    73  func addEdge(caller, callee *pgoir.IRNode, offset int, weight int64) {
    74  	namedEdge := pgo.NamedCallEdge{
    75  		CallerName:     caller.Name(),
    76  		CalleeName:     callee.Name(),
    77  		CallSiteOffset: offset,
    78  	}
    79  	irEdge := &pgoir.IREdge{
    80  		Src:            caller,
    81  		Dst:            callee,
    82  		CallSiteOffset: offset,
    83  		Weight:         weight,
    84  	}
    85  	caller.OutEdges[namedEdge] = irEdge
    86  }
    87  
    88  // Create a new struct type named structName with a method named methName and
    89  // return the method.
    90  func makeStructWithMethod(pkg *types.Pkg, structName, methName string) *ir.Func {
    91  	// type structName struct{}
    92  	structType := types.NewStruct(nil)
    93  
    94  	// func (structName) methodName()
    95  	recv := types.NewField(src.NoXPos, typecheck.Lookup(structName), structType)
    96  	sig := types.NewSignature(recv, nil, nil)
    97  	fn := ir.NewFunc(src.NoXPos, src.NoXPos, pkg.Lookup(structName+"."+methName), sig)
    98  
    99  	// Add the method to the struct.
   100  	structType.SetMethods([]*types.Field{types.NewField(src.NoXPos, typecheck.Lookup(methName), sig)})
   101  
   102  	return fn
   103  }
   104  
   105  func TestFindHotConcreteInterfaceCallee(t *testing.T) {
   106  	p := newProfileBuilder()
   107  
   108  	pkgFoo := types.NewPkg("example.com/foo", "foo")
   109  	basePos := src.NewFileBase("foo.go", "/foo.go")
   110  
   111  	const (
   112  		// Caller start line.
   113  		callerStart = 42
   114  
   115  		// The line offset of the call we care about.
   116  		callOffset = 1
   117  
   118  		// The line offset of some other call we don't care about.
   119  		wrongCallOffset = 2
   120  	)
   121  
   122  	// type IFace interface {
   123  	//	Foo()
   124  	// }
   125  	fooSig := types.NewSignature(types.FakeRecv(), nil, nil)
   126  	method := types.NewField(src.NoXPos, typecheck.Lookup("Foo"), fooSig)
   127  	iface := types.NewInterface([]*types.Field{method})
   128  
   129  	callerFn := ir.NewFunc(makePos(basePos, callerStart, 1), src.NoXPos, pkgFoo.Lookup("Caller"), types.NewSignature(nil, nil, nil))
   130  
   131  	hotCalleeFn := makeStructWithMethod(pkgFoo, "HotCallee", "Foo")
   132  	coldCalleeFn := makeStructWithMethod(pkgFoo, "ColdCallee", "Foo")
   133  	wrongLineCalleeFn := makeStructWithMethod(pkgFoo, "WrongLineCallee", "Foo")
   134  	wrongMethodCalleeFn := makeStructWithMethod(pkgFoo, "WrongMethodCallee", "Bar")
   135  
   136  	callerNode := p.NewNode("example.com/foo.Caller", callerFn)
   137  	hotCalleeNode := p.NewNode("example.com/foo.HotCallee.Foo", hotCalleeFn)
   138  	coldCalleeNode := p.NewNode("example.com/foo.ColdCallee.Foo", coldCalleeFn)
   139  	wrongLineCalleeNode := p.NewNode("example.com/foo.WrongCalleeLine.Foo", wrongLineCalleeFn)
   140  	wrongMethodCalleeNode := p.NewNode("example.com/foo.WrongCalleeMethod.Foo", wrongMethodCalleeFn)
   141  
   142  	hotMissingCalleeNode := p.NewNode("example.com/bar.HotMissingCallee.Foo", nil)
   143  
   144  	addEdge(callerNode, wrongLineCalleeNode, wrongCallOffset, 100) // Really hot, but wrong line.
   145  	addEdge(callerNode, wrongMethodCalleeNode, callOffset, 100)    // Really hot, but wrong method type.
   146  	addEdge(callerNode, hotCalleeNode, callOffset, 10)
   147  	addEdge(callerNode, coldCalleeNode, callOffset, 1)
   148  
   149  	// Equal weight, but IR missing.
   150  	//
   151  	// N.B. example.com/bar sorts lexicographically before example.com/foo,
   152  	// so if the IR availability of hotCalleeNode doesn't get precedence,
   153  	// this would be mistakenly selected.
   154  	addEdge(callerNode, hotMissingCalleeNode, callOffset, 10)
   155  
   156  	// IFace.Foo()
   157  	sel := typecheck.NewMethodExpr(src.NoXPos, iface, typecheck.Lookup("Foo"))
   158  	call := ir.NewCallExpr(makePos(basePos, callerStart+callOffset, 1), ir.OCALLINTER, sel, nil)
   159  
   160  	gotFn, gotWeight := findHotConcreteInterfaceCallee(p.Profile(), callerFn, call)
   161  	if gotFn != hotCalleeFn {
   162  		t.Errorf("findHotConcreteInterfaceCallee func got %v want %v", gotFn, hotCalleeFn)
   163  	}
   164  	if gotWeight != 10 {
   165  		t.Errorf("findHotConcreteInterfaceCallee weight got %v want 10", gotWeight)
   166  	}
   167  }
   168  
   169  func TestFindHotConcreteFunctionCallee(t *testing.T) {
   170  	// TestFindHotConcreteInterfaceCallee already covered basic weight
   171  	// comparisons, which is shared logic. Here we just test type signature
   172  	// disambiguation.
   173  
   174  	p := newProfileBuilder()
   175  
   176  	pkgFoo := types.NewPkg("example.com/foo", "foo")
   177  	basePos := src.NewFileBase("foo.go", "/foo.go")
   178  
   179  	const (
   180  		// Caller start line.
   181  		callerStart = 42
   182  
   183  		// The line offset of the call we care about.
   184  		callOffset = 1
   185  	)
   186  
   187  	callerFn := ir.NewFunc(makePos(basePos, callerStart, 1), src.NoXPos, pkgFoo.Lookup("Caller"), types.NewSignature(nil, nil, nil))
   188  
   189  	// func HotCallee()
   190  	hotCalleeFn := ir.NewFunc(src.NoXPos, src.NoXPos, pkgFoo.Lookup("HotCallee"), types.NewSignature(nil, nil, nil))
   191  
   192  	// func WrongCallee() bool
   193  	wrongCalleeFn := ir.NewFunc(src.NoXPos, src.NoXPos, pkgFoo.Lookup("WrongCallee"), types.NewSignature(nil, nil,
   194  		[]*types.Field{
   195  			types.NewField(src.NoXPos, nil, types.Types[types.TBOOL]),
   196  		},
   197  	))
   198  
   199  	callerNode := p.NewNode("example.com/foo.Caller", callerFn)
   200  	hotCalleeNode := p.NewNode("example.com/foo.HotCallee", hotCalleeFn)
   201  	wrongCalleeNode := p.NewNode("example.com/foo.WrongCallee", wrongCalleeFn)
   202  
   203  	addEdge(callerNode, wrongCalleeNode, callOffset, 100) // Really hot, but wrong function type.
   204  	addEdge(callerNode, hotCalleeNode, callOffset, 10)
   205  
   206  	// var fn func()
   207  	name := ir.NewNameAt(src.NoXPos, typecheck.Lookup("fn"), types.NewSignature(nil, nil, nil))
   208  	// fn()
   209  	call := ir.NewCallExpr(makePos(basePos, callerStart+callOffset, 1), ir.OCALL, name, nil)
   210  
   211  	gotFn, gotWeight := findHotConcreteFunctionCallee(p.Profile(), callerFn, call)
   212  	if gotFn != hotCalleeFn {
   213  		t.Errorf("findHotConcreteFunctionCallee func got %v want %v", gotFn, hotCalleeFn)
   214  	}
   215  	if gotWeight != 10 {
   216  		t.Errorf("findHotConcreteFunctionCallee weight got %v want 10", gotWeight)
   217  	}
   218  }
   219  

View as plain text