1
2
3
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 type devirtualization struct {
18 pos string
19 callee string
20 }
21
22 const profFileName = "devirt.pprof"
23 const preProfFileName = "devirt.pprof.node_map"
24
25
26 func testPGODevirtualize(t *testing.T, dir string, want []devirtualization, pgoProfileName string) {
27 testenv.MustHaveGoRun(t)
28 t.Parallel()
29
30 const pkg = "example.com/pgo/devirtualize"
31
32
33 goMod := fmt.Sprintf(`module %s
34 go 1.21
35 `, pkg)
36 if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte(goMod), 0644); err != nil {
37 t.Fatalf("error writing go.mod: %v", err)
38 }
39
40
41
42 cmd := testenv.CleanCmdEnv(testenv.Command(t, testenv.GoToolPath(t), "test", "."))
43 cmd.Dir = dir
44 b, err := cmd.CombinedOutput()
45 t.Logf("Test without PGO:\n%s", b)
46 if err != nil {
47 t.Fatalf("Test failed without PGO: %v", err)
48 }
49
50
51 pprof := filepath.Join(dir, pgoProfileName)
52 gcflag := fmt.Sprintf("-gcflags=-m=2 -pgoprofile=%s -d=pgodebug=3", pprof)
53 out := filepath.Join(dir, "test.exe")
54 cmd = testenv.CleanCmdEnv(testenv.Command(t, testenv.GoToolPath(t), "test", "-o", out, gcflag, "."))
55 cmd.Dir = dir
56
57 pr, pw, err := os.Pipe()
58 if err != nil {
59 t.Fatalf("error creating pipe: %v", err)
60 }
61 defer pr.Close()
62 cmd.Stdout = pw
63 cmd.Stderr = pw
64
65 err = cmd.Start()
66 pw.Close()
67 if err != nil {
68 t.Fatalf("error starting go test: %v", err)
69 }
70
71 got := make(map[devirtualization]struct{})
72
73 devirtualizedLine := regexp.MustCompile(`(.*): PGO devirtualizing \w+ call .* to (.*)`)
74
75 scanner := bufio.NewScanner(pr)
76 for scanner.Scan() {
77 line := scanner.Text()
78 t.Logf("child: %s", line)
79
80 m := devirtualizedLine.FindStringSubmatch(line)
81 if m == nil {
82 continue
83 }
84
85 d := devirtualization{
86 pos: m[1],
87 callee: m[2],
88 }
89 got[d] = struct{}{}
90 }
91 if err := cmd.Wait(); err != nil {
92 t.Fatalf("error running go test: %v", err)
93 }
94 if err := scanner.Err(); err != nil {
95 t.Fatalf("error reading go test output: %v", err)
96 }
97
98 if len(got) != len(want) {
99 t.Errorf("mismatched devirtualization count; got %v want %v", got, want)
100 }
101 for _, w := range want {
102 if _, ok := got[w]; ok {
103 continue
104 }
105 t.Errorf("devirtualization %v missing; got %v", w, got)
106 }
107
108
109 cmd = testenv.CleanCmdEnv(testenv.Command(t, out))
110 cmd.Dir = dir
111 b, err = cmd.CombinedOutput()
112 t.Logf("Test with PGO:\n%s", b)
113 if err != nil {
114 t.Fatalf("Test failed without PGO: %v", err)
115 }
116 }
117
118
119
120 func TestPGODevirtualize(t *testing.T) {
121 wd, err := os.Getwd()
122 if err != nil {
123 t.Fatalf("error getting wd: %v", err)
124 }
125 srcDir := filepath.Join(wd, "testdata", "pgo", "devirtualize")
126
127
128 dir := t.TempDir()
129 if err := os.Mkdir(filepath.Join(dir, "mult.pkg"), 0755); err != nil {
130 t.Fatalf("error creating dir: %v", err)
131 }
132 for _, file := range []string{"devirt.go", "devirt_test.go", profFileName, filepath.Join("mult.pkg", "mult.go")} {
133 if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
134 t.Fatalf("error copying %s: %v", file, err)
135 }
136 }
137
138 want := []devirtualization{
139
140 {
141 pos: "./devirt.go:101:20",
142 callee: "mult.Mult.Multiply",
143 },
144 {
145 pos: "./devirt.go:101:39",
146 callee: "Add.Add",
147 },
148
149 {
150 pos: "./devirt.go:173:36",
151 callee: "AddFn",
152 },
153 {
154 pos: "./devirt.go:173:15",
155 callee: "mult.MultFn",
156 },
157
158 {
159 pos: "./devirt.go:207:35",
160 callee: "AddFn",
161 },
162 {
163 pos: "./devirt.go:207:19",
164 callee: "mult.MultFn",
165 },
166
167
168
169
170
171
172
173
174
175
176 }
177
178 testPGODevirtualize(t, dir, want, profFileName)
179 }
180
181
182
183 func TestPGOPreprocessDevirtualize(t *testing.T) {
184 wd, err := os.Getwd()
185 if err != nil {
186 t.Fatalf("error getting wd: %v", err)
187 }
188 srcDir := filepath.Join(wd, "testdata", "pgo", "devirtualize")
189
190
191 dir := t.TempDir()
192 if err := os.Mkdir(filepath.Join(dir, "mult.pkg"), 0755); err != nil {
193 t.Fatalf("error creating dir: %v", err)
194 }
195 for _, file := range []string{"devirt.go", "devirt_test.go", preProfFileName, filepath.Join("mult.pkg", "mult.go")} {
196 if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
197 t.Fatalf("error copying %s: %v", file, err)
198 }
199 }
200
201 want := []devirtualization{
202
203 {
204 pos: "./devirt.go:101:20",
205 callee: "mult.Mult.Multiply",
206 },
207 {
208 pos: "./devirt.go:101:39",
209 callee: "Add.Add",
210 },
211
212 {
213 pos: "./devirt.go:173:36",
214 callee: "AddFn",
215 },
216 {
217 pos: "./devirt.go:173:15",
218 callee: "mult.MultFn",
219 },
220
221 {
222 pos: "./devirt.go:207:35",
223 callee: "AddFn",
224 },
225 {
226 pos: "./devirt.go:207:19",
227 callee: "mult.MultFn",
228 },
229
230
231
232
233
234
235
236
237
238
239 }
240
241 testPGODevirtualize(t, dir, want, preProfFileName)
242 }
243
244
245
246
247 func TestLookupFuncGeneric(t *testing.T) {
248 wd, err := os.Getwd()
249 if err != nil {
250 t.Fatalf("error getting wd: %v", err)
251 }
252 srcDir := filepath.Join(wd, "testdata", "pgo", "devirtualize")
253
254
255 dir := t.TempDir()
256 if err := os.Mkdir(filepath.Join(dir, "mult.pkg"), 0755); err != nil {
257 t.Fatalf("error creating dir: %v", err)
258 }
259 for _, file := range []string{"devirt.go", "devirt_test.go", profFileName, filepath.Join("mult.pkg", "mult.go")} {
260 if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
261 t.Fatalf("error copying %s: %v", file, err)
262 }
263 }
264
265
266 if err := convertMultToGeneric(filepath.Join(dir, "mult.pkg", "mult.go")); err != nil {
267 t.Fatalf("error editing mult.go: %v", err)
268 }
269
270
271
272
273
274
275 want := []devirtualization{
276
277 {
278 pos: "./devirt.go:101:20",
279 callee: "mult.Mult.Multiply",
280 },
281 {
282 pos: "./devirt.go:101:39",
283 callee: "Add.Add",
284 },
285
286 {
287 pos: "./devirt.go:173:36",
288 callee: "AddFn",
289 },
290
291 {
292 pos: "./devirt.go:207:35",
293 callee: "AddFn",
294 },
295
296
297
298
299
300
301
302
303
304
305 }
306
307 testPGODevirtualize(t, dir, want, profFileName)
308 }
309
310 var multFnRe = regexp.MustCompile(`func MultFn\(a, b int64\) int64`)
311
312 func convertMultToGeneric(path string) error {
313 content, err := os.ReadFile(path)
314 if err != nil {
315 return fmt.Errorf("error opening: %w", err)
316 }
317
318 if !multFnRe.Match(content) {
319 return fmt.Errorf("MultFn not found; update regexp?")
320 }
321
322
323
324 content = multFnRe.ReplaceAll(content, []byte(`func MultFn[T int32|int64](a, b T) T`))
325
326 return os.WriteFile(path, content, 0644)
327 }
328
View as plain text