cmd/compile/internal/pgo: match on call line offsets

Rather than matching calls to edges in the profile based directly on
line number in the source file, use the line offset from the start of
the function. This makes matching robust to changes in the source file
above the function containing the call.

The start line in the profile comes from Function.start_line, which is
included in Go pprof output since CL 438255.

Currently it is an error if no samples set start_line to help users
detect profiles missing this information. In the future, we should
fallback to using absolute lines, which is better than nothing.

For #55022.

Change-Id: Ie621950cfee1fef8fb200907a2a3f1ded41d04fa
Reviewed-on: https://go-review.googlesource.com/c/go/+/447315
Reviewed-by: Cherry Mui <cherryyz@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Michael Pratt <mpratt@google.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
This commit is contained in:
Michael Pratt 2022-11-02 11:11:03 -04:00 committed by Gopher Robot
parent f187c6b08e
commit bdd1e283a9
6 changed files with 186 additions and 83 deletions

View file

@ -8,6 +8,7 @@ import (
"bufio"
"fmt"
"internal/testenv"
"io"
"os"
"os/exec"
"path/filepath"
@ -16,12 +17,20 @@ import (
"testing"
)
// TestPGOIntendedInlining tests that specific functions are inlined.
func TestPGOIntendedInlining(t *testing.T) {
// testPGOIntendedInlining tests that specific functions are inlined.
func testPGOIntendedInlining(t *testing.T, dir string) {
testenv.MustHaveGoRun(t)
t.Parallel()
const pkg = "cmd/compile/internal/test/testdata/pgo/inline"
const pkg = "example.com/pgo/inline"
// Add a go.mod so we have a consistent symbol names in this temp dir.
goMod := fmt.Sprintf(`module %s
go 1.19
`, pkg)
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte(goMod), 0644); err != nil {
t.Fatalf("error writing go.mod: %v", err)
}
want := []string{
"(*BS).NS",
@ -58,14 +67,12 @@ func TestPGOIntendedInlining(t *testing.T) {
expectedNotInlinedList[fullName] = struct{}{}
}
// go test -c -o /tmp/test.exe -cpuprofile testdata/pgo/inline/inline_hot.pprof cmd/compile/internal/test/testdata/pgo/inline
curdir, err := os.Getwd()
if err != nil {
t.Fatalf("error getting wd: %v", err)
}
gcflag := fmt.Sprintf("-gcflags=-m -m -pgoprofile %s/testdata/pgo/inline/inline_hot.pprof", curdir)
out := filepath.Join(t.TempDir(), "test.exe")
cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "test", "-c", "-o", out, gcflag, pkg))
// go test -c -o /tmp/test.exe -cpuprofile inline_hot.pprof
pprof := filepath.Join(dir, "inline_hot.pprof")
gcflag := fmt.Sprintf("-gcflags=-m -m -pgoprofile %s", pprof)
out := filepath.Join(dir, "test.exe")
cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), "test", "-c", "-o", out, gcflag, "."))
cmd.Dir = dir
pr, pw, err := os.Pipe()
if err != nil {
@ -136,3 +143,89 @@ func TestPGOIntendedInlining(t *testing.T) {
t.Errorf("%s was expected not inlined", fullName)
}
}
// TestPGOIntendedInlining tests that specific functions are inlined when PGO
// is applied to the exact source that was profiled.
func TestPGOIntendedInlining(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Fatalf("error getting wd: %v", err)
}
srcDir := filepath.Join(wd, "testdata/pgo/inline")
// Copy the module to a scratch location so we can add a go.mod.
dir := t.TempDir()
for _, file := range []string{"inline_hot.go", "inline_hot_test.go", "inline_hot.pprof"} {
if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
t.Fatalf("error copying %s: %v", file, err)
}
}
testPGOIntendedInlining(t, dir)
}
// TestPGOIntendedInlining tests that specific functions are inlined when PGO
// is applied to the modified source.
func TestPGOIntendedInliningShiftedLines(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Fatalf("error getting wd: %v", err)
}
srcDir := filepath.Join(wd, "testdata/pgo/inline")
// Copy the module to a scratch location so we can modify the source.
dir := t.TempDir()
// Copy most of the files unmodified.
for _, file := range []string{"inline_hot_test.go", "inline_hot.pprof"} {
if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
t.Fatalf("error copying %s : %v", file, err)
}
}
// Add some comments to the top of inline_hot.go. This adjusts the line
// numbers of all of the functions without changing the semantics.
src, err := os.Open(filepath.Join(srcDir, "inline_hot.go"))
if err != nil {
t.Fatalf("error opening src inline_hot.go: %v", err)
}
defer src.Close()
dst, err := os.Create(filepath.Join(dir, "inline_hot.go"))
if err != nil {
t.Fatalf("error creating dst inline_hot.go: %v", err)
}
defer dst.Close()
if _, err := io.WriteString(dst, `// Autogenerated
// Lines
`); err != nil {
t.Fatalf("error writing comments to dst: %v", err)
}
if _, err := io.Copy(dst, src); err != nil {
t.Fatalf("error copying inline_hot.go: %v", err)
}
dst.Close()
testPGOIntendedInlining(t, dir)
}
func copyFile(dst, src string) error {
s, err := os.Open(src)
if err != nil {
return err
}
defer s.Close()
d, err := os.Create(dst)
if err != nil {
return err
}
defer d.Close()
_, err = io.Copy(d, s)
return err
}