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

View as plain text