mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
149 lines
4 KiB
Go
149 lines
4 KiB
Go
|
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||
|
|
// Use of this source code is governed by a BSD-style
|
||
|
|
// license that can be found in the LICENSE file.
|
||
|
|
|
||
|
|
package test
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bufio"
|
||
|
|
"fmt"
|
||
|
|
"internal/testenv"
|
||
|
|
"io"
|
||
|
|
"io/ioutil"
|
||
|
|
"os"
|
||
|
|
"os/exec"
|
||
|
|
"regexp"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
// TestPGOIntendedInlining tests that specific functions are inlined.
|
||
|
|
func TestPGOIntendedInlining(t *testing.T) {
|
||
|
|
testenv.MustHaveGoRun(t)
|
||
|
|
t.Parallel()
|
||
|
|
|
||
|
|
// Make a temporary directory to work in.
|
||
|
|
tmpdir, err := ioutil.TempDir("", "TestCode")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Failed to create temporary directory: %v", err)
|
||
|
|
}
|
||
|
|
defer os.RemoveAll(tmpdir)
|
||
|
|
|
||
|
|
want := map[string][]string{
|
||
|
|
"cmd/compile/internal/test/testdata/pgo/inline": {
|
||
|
|
"(*BS).NS",
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
// The functions which are not expected to be inlined are as follows.
|
||
|
|
wantNot := map[string][]string{
|
||
|
|
"cmd/compile/internal/test/testdata/pgo/inline": {
|
||
|
|
// The calling edge main->A is hot and the cost of A is large than
|
||
|
|
// inlineHotCalleeMaxBudget.
|
||
|
|
"A",
|
||
|
|
// The calling edge BenchmarkA" -> benchmarkB is cold
|
||
|
|
// and the cost of A is large than inlineMaxBudget.
|
||
|
|
"benchmarkB",
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
must := map[string]bool{
|
||
|
|
"(*BS).NS": true,
|
||
|
|
}
|
||
|
|
|
||
|
|
notInlinedReason := make(map[string]string)
|
||
|
|
pkgs := make([]string, 0, len(want))
|
||
|
|
for pname, fnames := range want {
|
||
|
|
pkgs = append(pkgs, pname)
|
||
|
|
for _, fname := range fnames {
|
||
|
|
fullName := pname + "." + fname
|
||
|
|
if _, ok := notInlinedReason[fullName]; ok {
|
||
|
|
t.Errorf("duplicate func: %s", fullName)
|
||
|
|
}
|
||
|
|
notInlinedReason[fullName] = "unknown reason"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// If the compiler emit "cannot inline for function A", the entry A
|
||
|
|
// in expectedNotInlinedList will be removed.
|
||
|
|
expectedNotInlinedList := make(map[string]struct{})
|
||
|
|
for pname, fnames := range wantNot {
|
||
|
|
for _, fname := range fnames {
|
||
|
|
fullName := pname + "." + fname
|
||
|
|
expectedNotInlinedList[fullName] = struct{}{}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// go test -bench=. -cpuprofile testdata/pgo/inline/inline_hot.pprof cmd/compile/internal/test/testdata/pgo/inline
|
||
|
|
curdir, err1 := os.Getwd()
|
||
|
|
if err1 != nil {
|
||
|
|
t.Fatal(err1)
|
||
|
|
}
|
||
|
|
gcflag_option := "-gcflags=-m -m -pgoprofile %s/testdata/pgo/inline/inline_hot.pprof"
|
||
|
|
gcflag := fmt.Sprintf(gcflag_option, curdir)
|
||
|
|
args := append([]string{"test", "-run=nope", gcflag}, pkgs...)
|
||
|
|
cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), args...))
|
||
|
|
|
||
|
|
pr, pw := io.Pipe()
|
||
|
|
cmd.Stdout = pw
|
||
|
|
cmd.Stderr = pw
|
||
|
|
cmdErr := make(chan error, 1)
|
||
|
|
go func() {
|
||
|
|
cmdErr <- cmd.Run()
|
||
|
|
pw.Close()
|
||
|
|
}()
|
||
|
|
scanner := bufio.NewScanner(pr)
|
||
|
|
curPkg := ""
|
||
|
|
canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
|
||
|
|
haveInlined := regexp.MustCompile(`: inlining call to ([^ ]*)`)
|
||
|
|
cannotInline := regexp.MustCompile(`: cannot inline ([^ ]*): (.*)`)
|
||
|
|
for scanner.Scan() {
|
||
|
|
line := scanner.Text()
|
||
|
|
if strings.HasPrefix(line, "# ") {
|
||
|
|
curPkg = line[2:]
|
||
|
|
splits := strings.Split(curPkg, " ")
|
||
|
|
curPkg = splits[0]
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if m := haveInlined.FindStringSubmatch(line); m != nil {
|
||
|
|
fname := m[1]
|
||
|
|
delete(notInlinedReason, curPkg+"."+fname)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if m := canInline.FindStringSubmatch(line); m != nil {
|
||
|
|
fname := m[1]
|
||
|
|
fullname := curPkg + "." + fname
|
||
|
|
// If function must be inlined somewhere, being inlinable is not enough
|
||
|
|
if _, ok := must[fullname]; !ok {
|
||
|
|
delete(notInlinedReason, fullname)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if m := cannotInline.FindStringSubmatch(line); m != nil {
|
||
|
|
fname, reason := m[1], m[2]
|
||
|
|
fullName := curPkg + "." + fname
|
||
|
|
if _, ok := notInlinedReason[fullName]; ok {
|
||
|
|
// cmd/compile gave us a reason why
|
||
|
|
notInlinedReason[fullName] = reason
|
||
|
|
}
|
||
|
|
delete(expectedNotInlinedList, fullName)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if err := <-cmdErr; err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if err := scanner.Err(); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
for fullName, reason := range notInlinedReason {
|
||
|
|
t.Errorf("%s was not inlined: %s", fullName, reason)
|
||
|
|
}
|
||
|
|
|
||
|
|
// If the list expectedNotInlinedList is not empty, it indicates
|
||
|
|
// the functions in the expectedNotInlinedList are marked with caninline.
|
||
|
|
for fullName, _ := range expectedNotInlinedList {
|
||
|
|
t.Errorf("%s was expected not inlined", fullName)
|
||
|
|
}
|
||
|
|
}
|