]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/compile/internal/test/pgo_devirtualize_test.go
3e264a3f41cb6752d9b363ed27f7b5ac2e943d26
[gostls13.git] / src / cmd / compile / internal / test / pgo_devirtualize_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 test
6
7 import (
8         "bufio"
9         "fmt"
10         "internal/testenv"
11         "os"
12         "path/filepath"
13         "regexp"
14         "testing"
15 )
16
17 // testPGODevirtualize tests that specific PGO devirtualize rewrites are performed.
18 func testPGODevirtualize(t *testing.T, dir string) {
19         testenv.MustHaveGoRun(t)
20         t.Parallel()
21
22         const pkg = "example.com/pgo/devirtualize"
23
24         // Add a go.mod so we have a consistent symbol names in this temp dir.
25         goMod := fmt.Sprintf(`module %s
26 go 1.19
27 `, pkg)
28         if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte(goMod), 0644); err != nil {
29                 t.Fatalf("error writing go.mod: %v", err)
30         }
31
32         // Run the test without PGO to ensure that the test assertions are
33         // correct even in the non-optimized version.
34         cmd := testenv.CleanCmdEnv(testenv.Command(t, testenv.GoToolPath(t), "test", "."))
35         cmd.Dir = dir
36         b, err := cmd.CombinedOutput()
37         t.Logf("Test without PGO:\n%s", b)
38         if err != nil {
39                 t.Fatalf("Test failed without PGO: %v", err)
40         }
41
42         // Build the test with the profile.
43         pprof := filepath.Join(dir, "devirt.pprof")
44         gcflag := fmt.Sprintf("-gcflags=-m=2 -pgoprofile=%s -d=pgodebug=3", pprof)
45         out := filepath.Join(dir, "test.exe")
46         cmd = testenv.CleanCmdEnv(testenv.Command(t, testenv.GoToolPath(t), "test", "-o", out, gcflag, "."))
47         cmd.Dir = dir
48
49         pr, pw, err := os.Pipe()
50         if err != nil {
51                 t.Fatalf("error creating pipe: %v", err)
52         }
53         defer pr.Close()
54         cmd.Stdout = pw
55         cmd.Stderr = pw
56
57         err = cmd.Start()
58         pw.Close()
59         if err != nil {
60                 t.Fatalf("error starting go test: %v", err)
61         }
62
63         type devirtualization struct {
64                 pos    string
65                 callee string
66         }
67
68         want := []devirtualization{
69                 // ExerciseIface
70                 {
71                         pos:    "./devirt.go:101:20",
72                         callee: "mult.Mult.Multiply",
73                 },
74                 {
75                         pos:    "./devirt.go:101:39",
76                         callee: "Add.Add",
77                 },
78                 // ExerciseFuncConcrete
79                 {
80                         pos:    "./devirt.go:178:18",
81                         callee: "AddFn",
82                 },
83                 // TODO(prattmic): Export data lookup for function value callees not implemented.
84                 //{
85                 //      pos:    "./devirt.go:179:15",
86                 //      callee: "mult.MultFn",
87                 //},
88                 // ExerciseFuncField
89                 {
90                         pos:    "./devirt.go:218:13",
91                         callee: "AddFn",
92                 },
93                 // TODO(prattmic): Export data lookup for function value callees not implemented.
94                 //{
95                 //      pos:    "./devirt.go:219:19",
96                 //      callee: "mult.MultFn",
97                 //},
98                 // ExerciseFuncClosure
99                 // TODO(prattmic): Closure callees not implemented.
100                 //{
101                 //      pos:    "./devirt.go:266:9",
102                 //      callee: "AddClosure.func1",
103                 //},
104                 //{
105                 //      pos:    "./devirt.go:267:15",
106                 //      callee: "mult.MultClosure.func1",
107                 //},
108         }
109
110         got := make(map[devirtualization]struct{})
111
112         devirtualizedLine := regexp.MustCompile(`(.*): PGO devirtualizing \w+ call .* to (.*)`)
113
114         scanner := bufio.NewScanner(pr)
115         for scanner.Scan() {
116                 line := scanner.Text()
117                 t.Logf("child: %s", line)
118
119                 m := devirtualizedLine.FindStringSubmatch(line)
120                 if m == nil {
121                         continue
122                 }
123
124                 d := devirtualization{
125                         pos:    m[1],
126                         callee: m[2],
127                 }
128                 got[d] = struct{}{}
129         }
130         if err := cmd.Wait(); err != nil {
131                 t.Fatalf("error running go test: %v", err)
132         }
133         if err := scanner.Err(); err != nil {
134                 t.Fatalf("error reading go test output: %v", err)
135         }
136
137         if len(got) != len(want) {
138                 t.Errorf("mismatched devirtualization count; got %v want %v", got, want)
139         }
140         for _, w := range want {
141                 if _, ok := got[w]; ok {
142                         continue
143                 }
144                 t.Errorf("devirtualization %v missing; got %v", w, got)
145         }
146
147         // Run test with PGO to ensure the assertions are still true.
148         cmd = testenv.CleanCmdEnv(testenv.Command(t, out))
149         cmd.Dir = dir
150         b, err = cmd.CombinedOutput()
151         t.Logf("Test with PGO:\n%s", b)
152         if err != nil {
153                 t.Fatalf("Test failed without PGO: %v", err)
154         }
155 }
156
157 // TestPGODevirtualize tests that specific functions are devirtualized when PGO
158 // is applied to the exact source that was profiled.
159 func TestPGODevirtualize(t *testing.T) {
160         wd, err := os.Getwd()
161         if err != nil {
162                 t.Fatalf("error getting wd: %v", err)
163         }
164         srcDir := filepath.Join(wd, "testdata", "pgo", "devirtualize")
165
166         // Copy the module to a scratch location so we can add a go.mod.
167         dir := t.TempDir()
168         if err := os.Mkdir(filepath.Join(dir, "mult.pkg"), 0755); err != nil {
169                 t.Fatalf("error creating dir: %v", err)
170         }
171         for _, file := range []string{"devirt.go", "devirt_test.go", "devirt.pprof", filepath.Join("mult.pkg", "mult.go")} {
172                 if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
173                         t.Fatalf("error copying %s: %v", file, err)
174                 }
175         }
176
177         testPGODevirtualize(t, dir)
178 }