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, nowant []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 gotNoHot := make(map[devirtualization]struct{})
73
74 devirtualizedLine := regexp.MustCompile(`(.*): PGO devirtualizing \w+ call .* to (.*)`)
75 noHotLine := regexp.MustCompile(`(.*): call .*: no hot callee`)
76
77 scanner := bufio.NewScanner(pr)
78 for scanner.Scan() {
79 line := scanner.Text()
80 t.Logf("child: %s", line)
81
82 m := devirtualizedLine.FindStringSubmatch(line)
83 if m != nil {
84 d := devirtualization{
85 pos: m[1],
86 callee: m[2],
87 }
88 got[d] = struct{}{}
89 continue
90 }
91 m = noHotLine.FindStringSubmatch(line)
92 if m != nil {
93 d := devirtualization{
94 pos: m[1],
95 }
96 gotNoHot[d] = struct{}{}
97 }
98 }
99 if err := cmd.Wait(); err != nil {
100 t.Fatalf("error running go test: %v", err)
101 }
102 if err := scanner.Err(); err != nil {
103 t.Fatalf("error reading go test output: %v", err)
104 }
105
106 if len(got) != len(want) {
107 t.Errorf("mismatched devirtualization count; got %v want %v", got, want)
108 }
109 for _, w := range want {
110 if _, ok := got[w]; ok {
111 continue
112 }
113 t.Errorf("devirtualization %v missing; got %v", w, got)
114 }
115 for _, nw := range nowant {
116 if _, ok := gotNoHot[nw]; !ok {
117 t.Errorf("unwanted devirtualization %v; got %v", nw, got)
118 }
119 }
120
121
122 cmd = testenv.CleanCmdEnv(testenv.Command(t, out))
123 cmd.Dir = dir
124 b, err = cmd.CombinedOutput()
125 t.Logf("Test with PGO:\n%s", b)
126 if err != nil {
127 t.Fatalf("Test failed without PGO: %v", err)
128 }
129 }
130
131
132
133 func TestPGODevirtualize(t *testing.T) {
134 wd, err := os.Getwd()
135 if err != nil {
136 t.Fatalf("error getting wd: %v", err)
137 }
138 srcDir := filepath.Join(wd, "testdata", "pgo", "devirtualize")
139
140
141 dir := t.TempDir()
142 if err := os.Mkdir(filepath.Join(dir, "mult.pkg"), 0755); err != nil {
143 t.Fatalf("error creating dir: %v", err)
144 }
145 for _, file := range []string{"devirt.go", "devirt_test.go", profFileName, filepath.Join("mult.pkg", "mult.go")} {
146 if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
147 t.Fatalf("error copying %s: %v", file, err)
148 }
149 }
150
151 want := []devirtualization{
152
153 {
154 pos: "./devirt.go:101:20",
155 callee: "mult.Mult.Multiply",
156 },
157 {
158 pos: "./devirt.go:101:39",
159 callee: "Add.Add",
160 },
161
162 {
163 pos: "./devirt.go:173:36",
164 callee: "AddFn",
165 },
166 {
167 pos: "./devirt.go:173:15",
168 callee: "mult.MultFn",
169 },
170
171 {
172 pos: "./devirt.go:207:35",
173 callee: "AddFn",
174 },
175 {
176 pos: "./devirt.go:207:19",
177 callee: "mult.MultFn",
178 },
179
180
181
182
183
184
185
186
187
188
189 }
190 nowant := []devirtualization{
191
192 {
193 pos: "./devirt.go:256:29",
194 },
195
196 {
197 pos: "./devirt.go:282:37",
198 },
199 }
200
201 testPGODevirtualize(t, dir, want, nowant, profFileName)
202 }
203
204
205
206 func TestPGOPreprocessDevirtualize(t *testing.T) {
207 wd, err := os.Getwd()
208 if err != nil {
209 t.Fatalf("error getting wd: %v", err)
210 }
211 srcDir := filepath.Join(wd, "testdata", "pgo", "devirtualize")
212
213
214 dir := t.TempDir()
215 if err := os.Mkdir(filepath.Join(dir, "mult.pkg"), 0755); err != nil {
216 t.Fatalf("error creating dir: %v", err)
217 }
218 for _, file := range []string{"devirt.go", "devirt_test.go", preProfFileName, filepath.Join("mult.pkg", "mult.go")} {
219 if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
220 t.Fatalf("error copying %s: %v", file, err)
221 }
222 }
223
224 want := []devirtualization{
225
226 {
227 pos: "./devirt.go:101:20",
228 callee: "mult.Mult.Multiply",
229 },
230 {
231 pos: "./devirt.go:101:39",
232 callee: "Add.Add",
233 },
234
235 {
236 pos: "./devirt.go:173:36",
237 callee: "AddFn",
238 },
239 {
240 pos: "./devirt.go:173:15",
241 callee: "mult.MultFn",
242 },
243
244 {
245 pos: "./devirt.go:207:35",
246 callee: "AddFn",
247 },
248 {
249 pos: "./devirt.go:207:19",
250 callee: "mult.MultFn",
251 },
252
253
254
255
256
257
258
259
260
261
262 }
263 nowant := []devirtualization{
264
265 {
266 pos: "./devirt.go:256:29",
267 },
268
269 {
270 pos: "./devirt.go:282:37",
271 },
272 }
273
274 testPGODevirtualize(t, dir, want, nowant, preProfFileName)
275 }
276
277
278
279
280 func TestLookupFuncGeneric(t *testing.T) {
281 wd, err := os.Getwd()
282 if err != nil {
283 t.Fatalf("error getting wd: %v", err)
284 }
285 srcDir := filepath.Join(wd, "testdata", "pgo", "devirtualize")
286
287
288 dir := t.TempDir()
289 if err := os.Mkdir(filepath.Join(dir, "mult.pkg"), 0755); err != nil {
290 t.Fatalf("error creating dir: %v", err)
291 }
292 for _, file := range []string{"devirt.go", "devirt_test.go", profFileName, filepath.Join("mult.pkg", "mult.go")} {
293 if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
294 t.Fatalf("error copying %s: %v", file, err)
295 }
296 }
297
298
299 if err := convertMultToGeneric(filepath.Join(dir, "mult.pkg", "mult.go")); err != nil {
300 t.Fatalf("error editing mult.go: %v", err)
301 }
302
303
304
305
306
307
308 want := []devirtualization{
309
310 {
311 pos: "./devirt.go:101:20",
312 callee: "mult.Mult.Multiply",
313 },
314 {
315 pos: "./devirt.go:101:39",
316 callee: "Add.Add",
317 },
318
319 {
320 pos: "./devirt.go:173:36",
321 callee: "AddFn",
322 },
323
324 {
325 pos: "./devirt.go:207:35",
326 callee: "AddFn",
327 },
328
329
330
331
332
333
334
335
336
337
338 }
339 nowant := []devirtualization{
340
341 {
342 pos: "./devirt.go:256:29",
343 },
344
345 {
346 pos: "./devirt.go:282:37",
347 },
348 }
349
350 testPGODevirtualize(t, dir, want, nowant, profFileName)
351 }
352
353 var multFnRe = regexp.MustCompile(`func MultFn\(a, b int64\) int64`)
354
355 func convertMultToGeneric(path string) error {
356 content, err := os.ReadFile(path)
357 if err != nil {
358 return fmt.Errorf("error opening: %w", err)
359 }
360
361 if !multFnRe.Match(content) {
362 return fmt.Errorf("MultFn not found; update regexp?")
363 }
364
365
366
367 content = multFnRe.ReplaceAll(content, []byte(`func MultFn[T int32|int64](a, b T) T`))
368
369 return os.WriteFile(path, content, 0644)
370 }
371
View as plain text