aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/compile/internal/test
diff options
context:
space:
mode:
authorCherry Mui <cherryyz@google.com>2023-09-15 12:13:09 -0400
committerCherry Mui <cherryyz@google.com>2023-09-19 15:42:58 +0000
commit3fb86fb8645ceb6163d1a9f573c2b4eec2a310f9 (patch)
tree92c0f9ea7adc8d2a042971fabdb56396f050a9bc /src/cmd/compile/internal/test
parenteca5a97340e6b475268a522012f30e8e25bb8b8f (diff)
downloadgo-3fb86fb8645ceb6163d1a9f573c2b4eec2a310f9.tar.xz
cmd/compile: add pgohash for debugging/bisecting PGO optimizations
When a PGO build fails or produces incorrect program, it is often unclear what the problem is. Add pgo hash so we can bisect to individual optimization decisions, which often helps debugging. Related to #58153. Change-Id: I651ffd9c53bad60f2f28c8ec2a90a3f532982712 Reviewed-on: https://go-review.googlesource.com/c/go/+/528400 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Michael Pratt <mpratt@google.com>
Diffstat (limited to 'src/cmd/compile/internal/test')
-rw-r--r--src/cmd/compile/internal/test/pgo_inl_test.go96
1 files changed, 69 insertions, 27 deletions
diff --git a/src/cmd/compile/internal/test/pgo_inl_test.go b/src/cmd/compile/internal/test/pgo_inl_test.go
index 4d6b5a134a..7aabf8b010 100644
--- a/src/cmd/compile/internal/test/pgo_inl_test.go
+++ b/src/cmd/compile/internal/test/pgo_inl_test.go
@@ -6,6 +6,7 @@ package test
import (
"bufio"
+ "bytes"
"fmt"
"internal/profile"
"internal/testenv"
@@ -17,11 +18,7 @@ import (
"testing"
)
-// testPGOIntendedInlining tests that specific functions are inlined.
-func testPGOIntendedInlining(t *testing.T, dir string) {
- testenv.MustHaveGoRun(t)
- t.Parallel()
-
+func buildPGOInliningTest(t *testing.T, dir string, flags ...string) []byte {
const pkg = "example.com/pgo/inline"
// Add a go.mod so we have a consistent symbol names in this temp dir.
@@ -32,6 +29,25 @@ go 1.19
t.Fatalf("error writing go.mod: %v", err)
}
+ exe := filepath.Join(dir, "test.exe")
+ args := []string{"test", "-c", "-o", exe}
+ args = append(args, flags...)
+ cmd := testenv.CleanCmdEnv(testenv.Command(t, testenv.GoToolPath(t), args...))
+ cmd.Dir = dir
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("build failed: %v, output:\n%s", err, out)
+ }
+ return out
+}
+
+// testPGOIntendedInlining tests that specific functions are inlined.
+func testPGOIntendedInlining(t *testing.T, dir string) {
+ testenv.MustHaveGoRun(t)
+ t.Parallel()
+
+ const pkg = "example.com/pgo/inline"
+
want := []string{
"(*BS).NS",
}
@@ -71,25 +87,9 @@ go 1.19
// TODO: maybe adjust the test to work with default threshold.
pprof := filepath.Join(dir, "inline_hot.pprof")
gcflag := fmt.Sprintf("-gcflags=-m -m -pgoprofile=%s -d=pgoinlinebudget=160,pgoinlinecdfthreshold=90", pprof)
- out := filepath.Join(dir, "test.exe")
- cmd := testenv.CleanCmdEnv(testenv.Command(t, testenv.GoToolPath(t), "test", "-c", "-o", out, gcflag, "."))
- cmd.Dir = dir
-
- pr, pw, err := os.Pipe()
- if err != nil {
- t.Fatalf("error creating pipe: %v", err)
- }
- defer pr.Close()
- cmd.Stdout = pw
- cmd.Stderr = pw
-
- err = cmd.Start()
- pw.Close()
- if err != nil {
- t.Fatalf("error starting go test: %v", err)
- }
+ out := buildPGOInliningTest(t, dir, gcflag)
- scanner := bufio.NewScanner(pr)
+ scanner := bufio.NewScanner(bytes.NewReader(out))
curPkg := ""
canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
haveInlined := regexp.MustCompile(`: inlining call to ([^ ]*)`)
@@ -128,11 +128,8 @@ go 1.19
continue
}
}
- if err := cmd.Wait(); err != nil {
- t.Fatalf("error running go test: %v", err)
- }
if err := scanner.Err(); err != nil {
- t.Fatalf("error reading go test output: %v", err)
+ t.Fatalf("error reading output: %v", err)
}
for fullName, reason := range notInlinedReason {
t.Errorf("%s was not inlined: %s", fullName, reason)
@@ -297,3 +294,48 @@ func copyFile(dst, src string) error {
_, err = io.Copy(d, s)
return err
}
+
+// TestPGOHash tests that PGO optimization decisions can be selected by pgohash.
+func TestPGOHash(t *testing.T) {
+ testenv.MustHaveGoRun(t)
+ t.Parallel()
+
+ wd, err := os.Getwd()
+ if err != nil {
+ t.Fatalf("error getting wd: %v", err)
+ }
+ srcDir := filepath.Join(wd, "testdata/pgo/inline")
+
+ // Copy the module to a scratch location so we can add a go.mod.
+ dir := t.TempDir()
+
+ for _, file := range []string{"inline_hot.go", "inline_hot_test.go", "inline_hot.pprof"} {
+ if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
+ t.Fatalf("error copying %s: %v", file, err)
+ }
+ }
+
+ pprof := filepath.Join(dir, "inline_hot.pprof")
+ gcflag0 := fmt.Sprintf("-gcflags=-pgoprofile=%s -d=pgoinlinebudget=160,pgoinlinecdfthreshold=90,pgodebug=1,", pprof)
+
+ // Check that a hash match allows PGO inlining.
+ const srcPos = "example.com/pgo/inline/inline_hot.go:81:19"
+ const hashMatch = "pgohash triggered " + srcPos + " (inline)"
+ pgoDebugRE := regexp.MustCompile(`hot-budget check allows inlining for call .* at ` + strings.ReplaceAll(srcPos, ".", "\\."))
+ hash := "v1" // 1 matches srcPos, v for verbose (print source location)
+ gcflag := gcflag0 + ",pgohash=" + hash
+ // build with -trimpath so the source location (thus the hash)
+ // does not depend on the temporary directory path.
+ out := buildPGOInliningTest(t, dir, gcflag, "-trimpath")
+ if !bytes.Contains(out, []byte(hashMatch)) || !pgoDebugRE.Match(out) {
+ t.Errorf("output does not contain expected source line, out:\n%s", out)
+ }
+
+ // Check that a hash mismatch turns off PGO inlining.
+ hash = "v0" // 0 should not match srcPos
+ gcflag = gcflag0 + ",pgohash=" + hash
+ out = buildPGOInliningTest(t, dir, gcflag, "-trimpath")
+ if bytes.Contains(out, []byte(hashMatch)) || pgoDebugRE.Match(out) {
+ t.Errorf("output contains unexpected source line, out:\n%s", out)
+ }
+}