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