| | |
| | |
| | |
| |
|
| | package test |
| |
|
| | import ( |
| | "bufio" |
| | "fmt" |
| | "internal/testenv" |
| | "os" |
| | "path/filepath" |
| | "regexp" |
| | "testing" |
| | ) |
| |
|
| | type devirtualization struct { |
| | pos string |
| | callee string |
| | } |
| |
|
| | const profFileName = "devirt.pprof" |
| | const preProfFileName = "devirt.pprof.node_map" |
| |
|
| | |
| | func testPGODevirtualize(t *testing.T, dir string, want, nowant []devirtualization, pgoProfileName string) { |
| | testenv.MustHaveGoRun(t) |
| | t.Parallel() |
| |
|
| | const pkg = "example.com/pgo/devirtualize" |
| |
|
| | |
| | goMod := fmt.Sprintf(`module %s |
| | go 1.21 |
| | `, pkg) |
| | if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte(goMod), 0644); err != nil { |
| | t.Fatalf("error writing go.mod: %v", err) |
| | } |
| |
|
| | |
| | |
| | cmd := testenv.CleanCmdEnv(testenv.Command(t, testenv.GoToolPath(t), "test", ".")) |
| | cmd.Dir = dir |
| | b, err := cmd.CombinedOutput() |
| | t.Logf("Test without PGO:\n%s", b) |
| | if err != nil { |
| | t.Fatalf("Test failed without PGO: %v", err) |
| | } |
| |
|
| | |
| | pprof := filepath.Join(dir, pgoProfileName) |
| | gcflag := fmt.Sprintf("-gcflags=-m=2 -pgoprofile=%s -d=pgodebug=3", pprof) |
| | out := filepath.Join(dir, "test.exe") |
| | cmd = testenv.CleanCmdEnv(testenv.Command(t, testenv.GoToolPath(t), "test", "-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) |
| | } |
| |
|
| | got := make(map[devirtualization]struct{}) |
| | gotNoHot := make(map[devirtualization]struct{}) |
| |
|
| | devirtualizedLine := regexp.MustCompile(`(.*): PGO devirtualizing \w+ call .* to (.*)`) |
| | noHotLine := regexp.MustCompile(`(.*): call .*: no hot callee`) |
| |
|
| | scanner := bufio.NewScanner(pr) |
| | for scanner.Scan() { |
| | line := scanner.Text() |
| | t.Logf("child: %s", line) |
| |
|
| | m := devirtualizedLine.FindStringSubmatch(line) |
| | if m != nil { |
| | d := devirtualization{ |
| | pos: m[1], |
| | callee: m[2], |
| | } |
| | got[d] = struct{}{} |
| | continue |
| | } |
| | m = noHotLine.FindStringSubmatch(line) |
| | if m != nil { |
| | d := devirtualization{ |
| | pos: m[1], |
| | } |
| | gotNoHot[d] = struct{}{} |
| | } |
| | } |
| | 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) |
| | } |
| |
|
| | if len(got) != len(want) { |
| | t.Errorf("mismatched devirtualization count; got %v want %v", got, want) |
| | } |
| | for _, w := range want { |
| | if _, ok := got[w]; ok { |
| | continue |
| | } |
| | t.Errorf("devirtualization %v missing; got %v", w, got) |
| | } |
| | for _, nw := range nowant { |
| | if _, ok := gotNoHot[nw]; !ok { |
| | t.Errorf("unwanted devirtualization %v; got %v", nw, got) |
| | } |
| | } |
| |
|
| | |
| | cmd = testenv.CleanCmdEnv(testenv.Command(t, out)) |
| | cmd.Dir = dir |
| | b, err = cmd.CombinedOutput() |
| | t.Logf("Test with PGO:\n%s", b) |
| | if err != nil { |
| | t.Fatalf("Test failed without PGO: %v", err) |
| | } |
| | } |
| |
|
| | |
| | |
| | func TestPGODevirtualize(t *testing.T) { |
| | wd, err := os.Getwd() |
| | if err != nil { |
| | t.Fatalf("error getting wd: %v", err) |
| | } |
| | srcDir := filepath.Join(wd, "testdata", "pgo", "devirtualize") |
| |
|
| | |
| | dir := t.TempDir() |
| | if err := os.Mkdir(filepath.Join(dir, "mult.pkg"), 0755); err != nil { |
| | t.Fatalf("error creating dir: %v", err) |
| | } |
| | for _, file := range []string{"devirt.go", "devirt_test.go", profFileName, filepath.Join("mult.pkg", "mult.go")} { |
| | if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil { |
| | t.Fatalf("error copying %s: %v", file, err) |
| | } |
| | } |
| |
|
| | want := []devirtualization{ |
| | |
| | { |
| | pos: "./devirt.go:101:20", |
| | callee: "mult.Mult.Multiply", |
| | }, |
| | { |
| | pos: "./devirt.go:101:39", |
| | callee: "Add.Add", |
| | }, |
| | |
| | { |
| | pos: "./devirt.go:173:36", |
| | callee: "AddFn", |
| | }, |
| | { |
| | pos: "./devirt.go:173:15", |
| | callee: "mult.MultFn", |
| | }, |
| | |
| | { |
| | pos: "./devirt.go:207:35", |
| | callee: "AddFn", |
| | }, |
| | { |
| | pos: "./devirt.go:207:19", |
| | callee: "mult.MultFn", |
| | }, |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | } |
| | nowant := []devirtualization{ |
| | |
| | { |
| | pos: "./devirt.go:256:29", |
| | }, |
| | |
| | { |
| | pos: "./devirt.go:282:37", |
| | }, |
| | } |
| |
|
| | testPGODevirtualize(t, dir, want, nowant, profFileName) |
| | } |
| |
|
| | |
| | |
| | func TestPGOPreprocessDevirtualize(t *testing.T) { |
| | wd, err := os.Getwd() |
| | if err != nil { |
| | t.Fatalf("error getting wd: %v", err) |
| | } |
| | srcDir := filepath.Join(wd, "testdata", "pgo", "devirtualize") |
| |
|
| | |
| | dir := t.TempDir() |
| | if err := os.Mkdir(filepath.Join(dir, "mult.pkg"), 0755); err != nil { |
| | t.Fatalf("error creating dir: %v", err) |
| | } |
| | for _, file := range []string{"devirt.go", "devirt_test.go", preProfFileName, filepath.Join("mult.pkg", "mult.go")} { |
| | if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil { |
| | t.Fatalf("error copying %s: %v", file, err) |
| | } |
| | } |
| |
|
| | want := []devirtualization{ |
| | |
| | { |
| | pos: "./devirt.go:101:20", |
| | callee: "mult.Mult.Multiply", |
| | }, |
| | { |
| | pos: "./devirt.go:101:39", |
| | callee: "Add.Add", |
| | }, |
| | |
| | { |
| | pos: "./devirt.go:173:36", |
| | callee: "AddFn", |
| | }, |
| | { |
| | pos: "./devirt.go:173:15", |
| | callee: "mult.MultFn", |
| | }, |
| | |
| | { |
| | pos: "./devirt.go:207:35", |
| | callee: "AddFn", |
| | }, |
| | { |
| | pos: "./devirt.go:207:19", |
| | callee: "mult.MultFn", |
| | }, |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | } |
| | nowant := []devirtualization{ |
| | |
| | { |
| | pos: "./devirt.go:256:29", |
| | }, |
| | |
| | { |
| | pos: "./devirt.go:282:37", |
| | }, |
| | } |
| |
|
| | testPGODevirtualize(t, dir, want, nowant, preProfFileName) |
| | } |
| |
|
| | |
| | |
| | |
| | func TestLookupFuncGeneric(t *testing.T) { |
| | wd, err := os.Getwd() |
| | if err != nil { |
| | t.Fatalf("error getting wd: %v", err) |
| | } |
| | srcDir := filepath.Join(wd, "testdata", "pgo", "devirtualize") |
| |
|
| | |
| | dir := t.TempDir() |
| | if err := os.Mkdir(filepath.Join(dir, "mult.pkg"), 0755); err != nil { |
| | t.Fatalf("error creating dir: %v", err) |
| | } |
| | for _, file := range []string{"devirt.go", "devirt_test.go", profFileName, filepath.Join("mult.pkg", "mult.go")} { |
| | if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil { |
| | t.Fatalf("error copying %s: %v", file, err) |
| | } |
| | } |
| |
|
| | |
| | if err := convertMultToGeneric(filepath.Join(dir, "mult.pkg", "mult.go")); err != nil { |
| | t.Fatalf("error editing mult.go: %v", err) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | want := []devirtualization{ |
| | |
| | { |
| | pos: "./devirt.go:101:20", |
| | callee: "mult.Mult.Multiply", |
| | }, |
| | { |
| | pos: "./devirt.go:101:39", |
| | callee: "Add.Add", |
| | }, |
| | |
| | { |
| | pos: "./devirt.go:173:36", |
| | callee: "AddFn", |
| | }, |
| | |
| | { |
| | pos: "./devirt.go:207:35", |
| | callee: "AddFn", |
| | }, |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | } |
| | nowant := []devirtualization{ |
| | |
| | { |
| | pos: "./devirt.go:256:29", |
| | }, |
| | |
| | { |
| | pos: "./devirt.go:282:37", |
| | }, |
| | } |
| |
|
| | testPGODevirtualize(t, dir, want, nowant, profFileName) |
| | } |
| |
|
| | var multFnRe = regexp.MustCompile(`func MultFn\(a, b int64\) int64`) |
| |
|
| | func convertMultToGeneric(path string) error { |
| | content, err := os.ReadFile(path) |
| | if err != nil { |
| | return fmt.Errorf("error opening: %w", err) |
| | } |
| |
|
| | if !multFnRe.Match(content) { |
| | return fmt.Errorf("MultFn not found; update regexp?") |
| | } |
| |
|
| | |
| | |
| | content = multFnRe.ReplaceAll(content, []byte(`func MultFn[T int32|int64](a, b T) T`)) |
| |
|
| | return os.WriteFile(path, content, 0644) |
| | } |
| |
|