aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/compile/internal/test/testdata
diff options
context:
space:
mode:
authorMichael Pratt <mpratt@google.com>2023-10-12 16:01:34 -0400
committerMichael Pratt <mpratt@google.com>2023-11-13 18:17:47 +0000
commitfb6ff1e4caaece9be61c45518ffb51081e892a73 (patch)
tree85b672308e7601987dfd2d3ed418c937016f4b8b /src/cmd/compile/internal/test/testdata
parent0c66ae5c27706d4fe5c43fc71f92b52052b24497 (diff)
downloadgo-fb6ff1e4caaece9be61c45518ffb51081e892a73.tar.xz
cmd/compile: initial function value devirtualization
Today, PGO-based devirtualization only applies to interface calls. This CL extends initial support to function values (i.e., function/closure pointers passed as arguments or stored in a struct). This CL is a minimal implementation with several limitations. * Export data lookup of function value callees not implemented (equivalent of CL 497175; done in CL 540258). * Callees must be standard static functions. Callees that are closures (requiring closure context) are not supported. For #61577. Change-Id: I7d328859035249e176294cd0d9885b2d08c853f6 Reviewed-on: https://go-review.googlesource.com/c/go/+/539699 Reviewed-by: Matthew Dempsky <mdempsky@google.com> Reviewed-by: Cherry Mui <cherryyz@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Diffstat (limited to 'src/cmd/compile/internal/test/testdata')
-rw-r--r--src/cmd/compile/internal/test/testdata/pgo/devirtualize/devirt.go214
-rw-r--r--src/cmd/compile/internal/test/testdata/pgo/devirtualize/devirt.pprofbin890 -> 1411 bytes
-rw-r--r--src/cmd/compile/internal/test/testdata/pgo/devirtualize/devirt_test.go48
-rw-r--r--src/cmd/compile/internal/test/testdata/pgo/devirtualize/mult.pkg/mult.go28
4 files changed, 282 insertions, 8 deletions
diff --git a/src/cmd/compile/internal/test/testdata/pgo/devirtualize/devirt.go b/src/cmd/compile/internal/test/testdata/pgo/devirtualize/devirt.go
index 4748e19e10..63de3d3c3f 100644
--- a/src/cmd/compile/internal/test/testdata/pgo/devirtualize/devirt.go
+++ b/src/cmd/compile/internal/test/testdata/pgo/devirtualize/devirt.go
@@ -16,7 +16,11 @@ package devirt
//
// Dots in the last package path component are escaped in symbol names. Use one
// to ensure the escaping doesn't break lookup.
-import "example.com/pgo/devirtualize/mult.pkg"
+import (
+ "fmt"
+
+ "example.com/pgo/devirtualize/mult.pkg"
+)
var sink int
@@ -42,15 +46,46 @@ func (Sub) Add(a, b int) int {
return a - b
}
-// Exercise calls mostly a1 and m1.
+// ExerciseIface calls mostly a1 and m1.
//
//go:noinline
-func Exercise(iter int, a1, a2 Adder, m1, m2 mult.Multiplier) {
+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++ {
- a := a1
m := m1
if i%10 == 0 {
- a = a2
m = m2
}
@@ -63,6 +98,173 @@ func Exercise(iter int, a1, a2 Adder, m1, m2 mult.Multiplier) {
// If they were not mutually exclusive (for example, two Add
// calls), then we could not definitively select the correct
// callee.
- sink += m.Multiply(42, a.Add(1, 2))
+ 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.
+ //
+ // TODO(prattmic): Export data lookup for function value
+ // callees not implemented, meaning the type is unavailable.
+ //sink += int(m(42, int64(a(1, 2))))
+
+ v := selectA(i)(one(i), 2)
+ val += int(m(42, int64(v)))
+ }
+ 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.
+ //
+ // TODO(prattmic): Export data lookup for function value
+ // callees not implemented, meaning the type is unavailable.
+ //sink += int(ops.m(42, int64(ops.a(1, 2))))
+
+ v := ops.a(1, 2)
+ val += int(ops.m(42, int64(v)))
+ }
+ 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.
+ //
+ // TODO(prattmic): Export data lookup for function value
+ // callees not implemented, meaning the type is unavailable.
+ //sink += int(m(42, int64(a(1, 2))))
+
+ v := a(1, 2)
+ val += int(m(42, int64(v)))
}
+ return val
}
diff --git a/src/cmd/compile/internal/test/testdata/pgo/devirtualize/devirt.pprof b/src/cmd/compile/internal/test/testdata/pgo/devirtualize/devirt.pprof
index 87e7b62736..de064582ff 100644
--- a/src/cmd/compile/internal/test/testdata/pgo/devirtualize/devirt.pprof
+++ b/src/cmd/compile/internal/test/testdata/pgo/devirtualize/devirt.pprof
Binary files differ
diff --git a/src/cmd/compile/internal/test/testdata/pgo/devirtualize/devirt_test.go b/src/cmd/compile/internal/test/testdata/pgo/devirtualize/devirt_test.go
index ef637a876b..59b565d77f 100644
--- a/src/cmd/compile/internal/test/testdata/pgo/devirtualize/devirt_test.go
+++ b/src/cmd/compile/internal/test/testdata/pgo/devirtualize/devirt_test.go
@@ -17,7 +17,7 @@ import (
"example.com/pgo/devirtualize/mult.pkg"
)
-func BenchmarkDevirt(b *testing.B) {
+func BenchmarkDevirtIface(b *testing.B) {
var (
a1 Add
a2 Sub
@@ -25,5 +25,49 @@ func BenchmarkDevirt(b *testing.B) {
m2 mult.NegMult
)
- Exercise(b.N, a1, a2, m1, m2)
+ ExerciseIface(b.N, a1, a2, m1, m2)
+}
+
+// Verify that devirtualization doesn't result in calls or side effects applying more than once.
+func TestDevirtIface(t *testing.T) {
+ var (
+ a1 Add
+ a2 Sub
+ m1 mult.Mult
+ m2 mult.NegMult
+ )
+
+ if v := ExerciseIface(10, a1, a2, m1, m2); v != 1176 {
+ t.Errorf("ExerciseIface(10) got %d want 1176", v)
+ }
+}
+
+func BenchmarkDevirtFuncConcrete(b *testing.B) {
+ ExerciseFuncConcrete(b.N, AddFn, SubFn, mult.MultFn, mult.NegMultFn)
+}
+
+func TestDevirtFuncConcrete(t *testing.T) {
+ if v := ExerciseFuncConcrete(10, AddFn, SubFn, mult.MultFn, mult.NegMultFn); v != 1176 {
+ t.Errorf("ExerciseFuncConcrete(10) got %d want 1176", v)
+ }
+}
+
+func BenchmarkDevirtFuncField(b *testing.B) {
+ ExerciseFuncField(b.N, AddFn, SubFn, mult.MultFn, mult.NegMultFn)
+}
+
+func TestDevirtFuncField(t *testing.T) {
+ if v := ExerciseFuncField(10, AddFn, SubFn, mult.MultFn, mult.NegMultFn); v != 1176 {
+ t.Errorf("ExerciseFuncField(10) got %d want 1176", v)
+ }
+}
+
+func BenchmarkDevirtFuncClosure(b *testing.B) {
+ ExerciseFuncClosure(b.N, AddClosure(), SubClosure(), mult.MultClosure(), mult.NegMultClosure())
+}
+
+func TestDevirtFuncClosure(t *testing.T) {
+ if v := ExerciseFuncClosure(10, AddClosure(), SubClosure(), mult.MultClosure(), mult.NegMultClosure()); v != 1176 {
+ t.Errorf("ExerciseFuncClosure(10) got %d want 1176", v)
+ }
}
diff --git a/src/cmd/compile/internal/test/testdata/pgo/devirtualize/mult.pkg/mult.go b/src/cmd/compile/internal/test/testdata/pgo/devirtualize/mult.pkg/mult.go
index 8a026a52f5..64f405ff9e 100644
--- a/src/cmd/compile/internal/test/testdata/pgo/devirtualize/mult.pkg/mult.go
+++ b/src/cmd/compile/internal/test/testdata/pgo/devirtualize/mult.pkg/mult.go
@@ -30,3 +30,31 @@ func (NegMult) Multiply(a, b int) int {
}
return -1 * a * b
}
+
+// N.B. Different types than AddFunc to test intra-line disambiguation.
+type MultFunc func(int64, int64) int64
+
+func MultFn(a, b int64) int64 {
+ return a * b
+}
+
+func NegMultFn(a, b int64) int64 {
+ return -1 * a * b
+}
+
+//go:noinline
+func MultClosure() MultFunc {
+ // Explicit closure to differentiate from AddClosure.
+ c := 1
+ return func(a, b int64) int64 {
+ return a * b * int64(c)
+ }
+}
+
+//go:noinline
+func NegMultClosure() MultFunc {
+ c := 1
+ return func(a, b int64) int64 {
+ return -1 * a * b * int64(c)
+ }
+}