blob: ac238f6dea42a90c375cc32c7d09c8d8e735024e [file] [log] [blame]
// 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.
// WARNING: Please avoid updating this file. If this file needs to be updated,
// then a new devirt.pprof file should be generated:
//
// $ cd $GOROOT/src/cmd/compile/internal/test/testdata/pgo/devirtualize/
// $ go mod init example.com/pgo/devirtualize
// $ go test -bench=. -cpuprofile ./devirt.pprof
package devirt
// Devirtualization of callees from transitive dependencies should work even if
// they aren't directly referenced in the package. See #61577.
//
// Dots in the last package path component are escaped in symbol names. Use one
// to ensure the escaping doesn't break lookup.
import (
"fmt"
"example.com/pgo/devirtualize/mult.pkg"
)
var sink int
type Adder interface {
Add(a, b int) int
}
type Add struct{}
func (Add) Add(a, b int) int {
for i := 0; i < 1000; i++ {
sink++
}
return a + b
}
type Sub struct{}
func (Sub) Add(a, b int) int {
for i := 0; i < 1000; i++ {
sink++
}
return a - b
}
// ExerciseIface calls mostly a1 and m1.
//
//go:noinline
func ExerciseIface(iter int, a1, a2 Adder, m1, m2 mult.Multiplier) int {
// The call below must evaluate selectA() to determine the receiver to
// use. This should happen exactly once per iteration. Assert that is
// the case to ensure the IR manipulation does not result in over- or
// under-evaluation.
selectI := 0
selectA := func(gotI int) Adder {
if gotI != selectI {
panic(fmt.Sprintf("selectA not called once per iteration; got i %d want %d", gotI, selectI))
}
selectI++
if gotI%10 == 0 {
return a2
}
return a1
}
oneI := 0
one := func(gotI int) int {
if gotI != oneI {
panic(fmt.Sprintf("one not called once per iteration; got i %d want %d", gotI, oneI))
}
oneI++
// The function value must be evaluated before arguments, so
// selectI must have been incremented already.
if selectI != oneI {
panic(fmt.Sprintf("selectA not called before not called before one; got i %d want %d", selectI, oneI))
}
return 1
}
val := 0
for i := 0; i < iter; i++ {
m := m1
if i%10 == 0 {
m = m2
}
// N.B. Profiles only distinguish calls on a per-line level,
// making the two calls ambiguous. However because the
// interfaces and implementations are mutually exclusive,
// devirtualization can still select the correct callee for
// each.
//
// If they were not mutually exclusive (for example, two Add
// calls), then we could not definitively select the correct
// callee.
val += m.Multiply(42, selectA(i).Add(one(i), 2))
}
return val
}
type AddFunc func(int, int) int
func AddFn(a, b int) int {
for i := 0; i < 1000; i++ {
sink++
}
return a + b
}
func SubFn(a, b int) int {
for i := 0; i < 1000; i++ {
sink++
}
return a - b
}
// ExerciseFuncConcrete calls mostly a1 and m1.
//
//go:noinline
func ExerciseFuncConcrete(iter int, a1, a2 AddFunc, m1, m2 mult.MultFunc) int {
// The call below must evaluate selectA() to determine the function to
// call. This should happen exactly once per iteration. Assert that is
// the case to ensure the IR manipulation does not result in over- or
// under-evaluation.
selectI := 0
selectA := func(gotI int) AddFunc {
if gotI != selectI {
panic(fmt.Sprintf("selectA not called once per iteration; got i %d want %d", gotI, selectI))
}
selectI++
if gotI%10 == 0 {
return a2
}
return a1
}
oneI := 0
one := func(gotI int) int {
if gotI != oneI {
panic(fmt.Sprintf("one not called once per iteration; got i %d want %d", gotI, oneI))
}
oneI++
// The function value must be evaluated before arguments, so
// selectI must have been incremented already.
if selectI != oneI {
panic(fmt.Sprintf("selectA not called before not called before one; got i %d want %d", selectI, oneI))
}
return 1
}
val := 0
for i := 0; i < iter; i++ {
m := m1
if i%10 == 0 {
m = m2
}
// N.B. Profiles only distinguish calls on a per-line level,
// making the two calls ambiguous. However because the
// function types are mutually exclusive, devirtualization can
// still select the correct callee for each.
//
// If they were not mutually exclusive (for example, two
// AddFunc calls), then we could not definitively select the
// correct callee.
val += int(m(42, int64(selectA(i)(one(i), 2))))
}
return val
}
// ExerciseFuncField calls mostly a1 and m1.
//
// This is a simplified version of ExerciseFuncConcrete, but accessing the
// function values via a struct field.
//
//go:noinline
func ExerciseFuncField(iter int, a1, a2 AddFunc, m1, m2 mult.MultFunc) int {
ops := struct {
a AddFunc
m mult.MultFunc
}{}
val := 0
for i := 0; i < iter; i++ {
ops.a = a1
ops.m = m1
if i%10 == 0 {
ops.a = a2
ops.m = m2
}
// N.B. Profiles only distinguish calls on a per-line level,
// making the two calls ambiguous. However because the
// function types are mutually exclusive, devirtualization can
// still select the correct callee for each.
//
// If they were not mutually exclusive (for example, two
// AddFunc calls), then we could not definitively select the
// correct callee.
val += int(ops.m(42, int64(ops.a(1, 2))))
}
return val
}
//go:noinline
func AddClosure() AddFunc {
// Implicit closure by capturing the receiver.
var a Add
return a.Add
}
//go:noinline
func SubClosure() AddFunc {
var s Sub
return s.Add
}
// ExerciseFuncClosure calls mostly a1 and m1.
//
// This is a simplified version of ExerciseFuncConcrete, but we need two
// distinct call sites to test two different types of function values.
//
//go:noinline
func ExerciseFuncClosure(iter int, a1, a2 AddFunc, m1, m2 mult.MultFunc) int {
val := 0
for i := 0; i < iter; i++ {
a := a1
m := m1
if i%10 == 0 {
a = a2
m = m2
}
// N.B. Profiles only distinguish calls on a per-line level,
// making the two calls ambiguous. However because the
// function types are mutually exclusive, devirtualization can
// still select the correct callee for each.
//
// If they were not mutually exclusive (for example, two
// AddFunc calls), then we could not definitively select the
// correct callee.
val += int(m(42, int64(a(1, 2))))
}
return val
}