mirror of
https://github.com/golang/go
synced 2024-11-17 13:14:56 -07:00
cmd/compile: extend dump-to-file to handle "genssa" (asm) case.
Extend the existing dump-to-file to also do assembly output to make it easier to write debug-information tests that check for line-numbering in particular orders. Includes POC test (which is silent w/o -v): go test -v -run TestDebugLines cmd/compile/internal/ssa === RUN TestDebugLines Preserving temporary directory /var/folders/v6/xyzzy/T/debug_lines_test321 About to run (cd /var/folders/v6/xyzzy/T/debug_lines_test321; \ GOSSADIR=/var/folders/v6/xyzzy/T/debug_lines_test321 \ /Users/drchase/work/go/bin/go build -o foo.o \ '-gcflags=-N -l -d=ssa/genssa/dump=sayhi' \ /Users/drchase/work/go/src/cmd/compile/internal/ssa/testdata/sayhi.go ) Saw stmt# 8 for submatch '8' on dump line #7 = ' v107 00005 (+8) MOVQ AX, "".n(SP)' Saw stmt# 9 for submatch '9' on dump line #9 = ' v87 00007 (+9) MOVUPS X15, ""..autotmp_2-32(SP)' Saw stmt# 10 for submatch '10' on dump line #46 = ' v65 00044 (+10) MOVUPS X15, ""..autotmp_2-32(SP)' Saw stmt# 11 for submatch '11' on dump line #83 = ' v131 00081 (+11) MOVQ "".wg+8(SP), AX' --- PASS: TestDebugLines (4.95s) PASS ok cmd/compile/internal/ssa 5.685s Includes a test to ensure that inlining information is printed correctly. Updates #47880. Change-Id: I83b596476a88687d71d5b65dbb94641a576d747e Reviewed-on: https://go-review.googlesource.com/c/go/+/348970 Trust: David Chase <drchase@google.com> Run-TryBot: David Chase <drchase@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
parent
3c764babe7
commit
af72ddfcd7
@ -10,9 +10,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
"internal/buildcfg"
|
"internal/buildcfg"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
@ -59,7 +61,7 @@ func Compile(f *Func) {
|
|||||||
printFunc(f)
|
printFunc(f)
|
||||||
}
|
}
|
||||||
f.HTMLWriter.WritePhase("start", "start")
|
f.HTMLWriter.WritePhase("start", "start")
|
||||||
if BuildDump != "" && BuildDump == f.Name {
|
if BuildDump[f.Name] {
|
||||||
f.dumpFile("build")
|
f.dumpFile("build")
|
||||||
}
|
}
|
||||||
if checkEnabled {
|
if checkEnabled {
|
||||||
@ -163,25 +165,37 @@ func Compile(f *Func) {
|
|||||||
phaseName = ""
|
phaseName = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// dumpFile creates a file from the phase name and function name
|
// DumpFileForPhase creates a file from the function name and phase name,
|
||||||
// Dumping is done to files to avoid buffering huge strings before
|
// warning and returning nil if this is not possible.
|
||||||
// output.
|
func (f *Func) DumpFileForPhase(phaseName string) io.WriteCloser {
|
||||||
func (f *Func) dumpFile(phaseName string) {
|
|
||||||
f.dumpFileSeq++
|
f.dumpFileSeq++
|
||||||
fname := fmt.Sprintf("%s_%02d__%s.dump", f.Name, int(f.dumpFileSeq), phaseName)
|
fname := fmt.Sprintf("%s_%02d__%s.dump", f.Name, int(f.dumpFileSeq), phaseName)
|
||||||
fname = strings.Replace(fname, " ", "_", -1)
|
fname = strings.Replace(fname, " ", "_", -1)
|
||||||
fname = strings.Replace(fname, "/", "_", -1)
|
fname = strings.Replace(fname, "/", "_", -1)
|
||||||
fname = strings.Replace(fname, ":", "_", -1)
|
fname = strings.Replace(fname, ":", "_", -1)
|
||||||
|
|
||||||
|
if ssaDir := os.Getenv("GOSSADIR"); ssaDir != "" {
|
||||||
|
fname = filepath.Join(ssaDir, fname)
|
||||||
|
}
|
||||||
|
|
||||||
fi, err := os.Create(fname)
|
fi, err := os.Create(fname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.Warnl(src.NoXPos, "Unable to create after-phase dump file %s", fname)
|
f.Warnl(src.NoXPos, "Unable to create after-phase dump file %s", fname)
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
return fi
|
||||||
|
}
|
||||||
|
|
||||||
p := stringFuncPrinter{w: fi}
|
// dumpFile creates a file from the phase name and function name
|
||||||
fprintFunc(p, f)
|
// Dumping is done to files to avoid buffering huge strings before
|
||||||
fi.Close()
|
// output.
|
||||||
|
func (f *Func) dumpFile(phaseName string) {
|
||||||
|
fi := f.DumpFileForPhase(phaseName)
|
||||||
|
if fi != nil {
|
||||||
|
p := stringFuncPrinter{w: fi}
|
||||||
|
fprintFunc(p, f)
|
||||||
|
fi.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type pass struct {
|
type pass struct {
|
||||||
@ -224,7 +238,9 @@ var IntrinsicsDisable bool
|
|||||||
var BuildDebug int
|
var BuildDebug int
|
||||||
var BuildTest int
|
var BuildTest int
|
||||||
var BuildStats int
|
var BuildStats int
|
||||||
var BuildDump string // name of function to dump after initial build of ssa
|
var BuildDump map[string]bool = make(map[string]bool) // names of functions to dump after initial build of ssa
|
||||||
|
|
||||||
|
var GenssaDump map[string]bool = make(map[string]bool) // names of functions to dump after ssa has been converted to asm
|
||||||
|
|
||||||
// PhaseOption sets the specified flag in the specified ssa phase,
|
// PhaseOption sets the specified flag in the specified ssa phase,
|
||||||
// returning empty string if this was successful or a string explaining
|
// returning empty string if this was successful or a string explaining
|
||||||
@ -248,7 +264,7 @@ func PhaseOption(phase, flag string, val int, valString string) string {
|
|||||||
switch phase {
|
switch phase {
|
||||||
case "", "help":
|
case "", "help":
|
||||||
lastcr := 0
|
lastcr := 0
|
||||||
phasenames := " check, all, build, intrinsics"
|
phasenames := " check, all, build, intrinsics, genssa"
|
||||||
for _, p := range passes {
|
for _, p := range passes {
|
||||||
pn := strings.Replace(p.name, " ", "_", -1)
|
pn := strings.Replace(p.name, " ", "_", -1)
|
||||||
if len(pn)+len(phasenames)-lastcr > 70 {
|
if len(pn)+len(phasenames)-lastcr > 70 {
|
||||||
@ -278,6 +294,7 @@ where:
|
|||||||
|
|
||||||
Phase "all" supports flags "time", "mem", and "dump".
|
Phase "all" supports flags "time", "mem", and "dump".
|
||||||
Phase "intrinsics" supports flags "on", "off", and "debug".
|
Phase "intrinsics" supports flags "on", "off", and "debug".
|
||||||
|
Phase "genssa" (assembly generation) supports the flag "dump".
|
||||||
|
|
||||||
If the "dump" flag is specified, the output is written on a file named
|
If the "dump" flag is specified, the output is written on a file named
|
||||||
<phase>__<function_name>_<seq>.dump; otherwise it is directed to stdout.
|
<phase>__<function_name>_<seq>.dump; otherwise it is directed to stdout.
|
||||||
@ -339,10 +356,11 @@ commas. For example:
|
|||||||
case "dump":
|
case "dump":
|
||||||
alldump = val != 0
|
alldump = val != 0
|
||||||
if alldump {
|
if alldump {
|
||||||
BuildDump = valString
|
BuildDump[valString] = true
|
||||||
|
GenssaDump[valString] = true
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("Did not find a flag matching %s in -d=ssa/%s debug option", flag, phase)
|
return fmt.Sprintf("Did not find a flag matching %s in -d=ssa/%s debug option (expected ssa/all/{time,mem,dump=function_name})", flag, phase)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,7 +373,7 @@ commas. For example:
|
|||||||
case "debug":
|
case "debug":
|
||||||
IntrinsicsDebug = val
|
IntrinsicsDebug = val
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("Did not find a flag matching %s in -d=ssa/%s debug option", flag, phase)
|
return fmt.Sprintf("Did not find a flag matching %s in -d=ssa/%s debug option (expected ssa/intrinsics/{on,off,debug})", flag, phase)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -368,9 +386,18 @@ commas. For example:
|
|||||||
case "stats":
|
case "stats":
|
||||||
BuildStats = val
|
BuildStats = val
|
||||||
case "dump":
|
case "dump":
|
||||||
BuildDump = valString
|
BuildDump[valString] = true
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("Did not find a flag matching %s in -d=ssa/%s debug option", flag, phase)
|
return fmt.Sprintf("Did not find a flag matching %s in -d=ssa/%s debug option (expected ssa/build/{debug,test,stats,dump=function_name})", flag, phase)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if phase == "genssa" {
|
||||||
|
switch flag {
|
||||||
|
case "dump":
|
||||||
|
GenssaDump[valString] = true
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("Did not find a flag matching %s in -d=ssa/%s debug option (expected ssa/genssa/dump=function_name)", flag, phase)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
213
src/cmd/compile/internal/ssa/debug_lines_test.go
Normal file
213
src/cmd/compile/internal/ssa/debug_lines_test.go
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
// Copyright 2021 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 ssa_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
// "flag"
|
||||||
|
"fmt"
|
||||||
|
"internal/testenv"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Matches lines in genssa output that are marked "isstmt", and the parenthesized plus-prefixed line number is a submatch
|
||||||
|
var asmLine *regexp.Regexp = regexp.MustCompile(`^\s[vb][0-9]+\s+[0-9]+\s\(\+([0-9]+)\)`)
|
||||||
|
|
||||||
|
// this matches e.g. ` v123456789 000007 (+9876654310) MOVUPS X15, ""..autotmp_2-32(SP)`
|
||||||
|
|
||||||
|
// Matches lines in genssa output that describe an inlined file (on a Unix filesystem). Note it expects an unadventurous choice of basename.
|
||||||
|
var inlineLine *regexp.Regexp = regexp.MustCompile(`^#\s/.*/[-a-zA-Z0-9_]+\.go:([0-9]+)`)
|
||||||
|
|
||||||
|
// this matches e.g. # /pa/inline-dumpxxxx.go:6
|
||||||
|
|
||||||
|
var testGoArchFlag = flag.String("arch", "", "run test for specified architecture")
|
||||||
|
|
||||||
|
func testGoArch() string {
|
||||||
|
if *testGoArchFlag == "" {
|
||||||
|
return runtime.GOARCH
|
||||||
|
}
|
||||||
|
return *testGoArchFlag
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDebugLines(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("Windows lacks $HOME which complicates workaround for 'missing $GOPATH'") // $HOME needed to work around #43938
|
||||||
|
}
|
||||||
|
// This test is potentially fragile, the goal is that debugging should step properly through "sayhi"
|
||||||
|
// If the blocks are reordered in a way that changes the statement order but execution flows correctly,
|
||||||
|
// then rearrange the expected numbers. Register abi and not-register-abi also have different sequences,
|
||||||
|
// at least for now.
|
||||||
|
|
||||||
|
switch testGoArch() {
|
||||||
|
case "arm64", "amd64": // register ABI
|
||||||
|
testDebugLines(t, "sayhi.go", "sayhi", []int{8, 9, 10, 11})
|
||||||
|
|
||||||
|
case "arm", "386": // probably not register ABI for a while
|
||||||
|
testDebugLines(t, "sayhi.go", "sayhi", []int{9, 10, 11})
|
||||||
|
|
||||||
|
default: // expect ppc64le and riscv will pick up register ABI soonish, not sure about others
|
||||||
|
t.Skip("skipped for many architectures, also changes w/ register ABI")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInlineLines(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("Windows lacks $HOME which complicates workaround for 'missing $GOPATH'") // $HOME needed to work around #43938
|
||||||
|
}
|
||||||
|
if runtime.GOARCH != "amd64" && *testGoArchFlag == "" {
|
||||||
|
// As of september 2021, works for everything except mips64, but still potentially fragile
|
||||||
|
t.Skip("only runs for amd64 unless -arch explicitly supplied")
|
||||||
|
}
|
||||||
|
|
||||||
|
want := [][]int{{3}, {4, 10}, {4, 10, 16}, {4, 10}, {4, 11, 16}, {4, 11}, {4}, {5, 10}, {5, 10, 16}, {5, 10}, {5, 11, 16}, {5, 11}, {5}}
|
||||||
|
testInlineStack(t, "inline-dump.go", "f", want)
|
||||||
|
}
|
||||||
|
|
||||||
|
func compileAndDump(t *testing.T, file, function, moreGCFlags string) []byte {
|
||||||
|
testenv.MustHaveGoBuild(t)
|
||||||
|
|
||||||
|
tmpdir, err := ioutil.TempDir("", "debug_lines_test")
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Problem creating TempDir, error %v", err))
|
||||||
|
}
|
||||||
|
if testing.Verbose() {
|
||||||
|
fmt.Printf("Preserving temporary directory %s\n", tmpdir)
|
||||||
|
} else {
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
}
|
||||||
|
|
||||||
|
source, err := filepath.Abs(filepath.Join("testdata", file))
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Could not get abspath of testdata directory and file, %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", "foo.o", "-gcflags=-d=ssa/genssa/dump="+function+" "+moreGCFlags, source)
|
||||||
|
cmd.Dir = tmpdir
|
||||||
|
cmd.Env = replaceEnv(cmd.Env, "GOSSADIR", tmpdir)
|
||||||
|
cmd.Env = replaceEnv(cmd.Env, "HOME", os.Getenv("HOME")) // workaround for #43938
|
||||||
|
testGoos := "linux" // default to linux
|
||||||
|
if testGoArch() == "wasm" {
|
||||||
|
testGoos = "js"
|
||||||
|
}
|
||||||
|
cmd.Env = replaceEnv(cmd.Env, "GOOS", testGoos)
|
||||||
|
cmd.Env = replaceEnv(cmd.Env, "GOARCH", testGoArch())
|
||||||
|
|
||||||
|
if testing.Verbose() {
|
||||||
|
fmt.Printf("About to run %s\n", asCommandLine("", cmd))
|
||||||
|
}
|
||||||
|
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
cmd.Stdout = &stdout
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
t.Fatalf("error running cmd %s: %v\nstdout:\n%sstderr:\n%s\n", asCommandLine("", cmd), err, stdout.String(), stderr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if s := stderr.String(); s != "" {
|
||||||
|
t.Fatalf("Wanted empty stderr, instead got:\n%s\n", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
dumpFile := filepath.Join(tmpdir, function+"_01__genssa.dump")
|
||||||
|
dumpBytes, err := os.ReadFile(dumpFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not read dump file %s, err=%v", dumpFile, err)
|
||||||
|
}
|
||||||
|
return dumpBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortInlineStacks(x [][]int) {
|
||||||
|
sort.Slice(x, func(i, j int) bool {
|
||||||
|
if len(x[i]) != len(x[j]) {
|
||||||
|
return len(x[i]) < len(x[j])
|
||||||
|
}
|
||||||
|
for k := range x[i] {
|
||||||
|
if x[i][k] != x[j][k] {
|
||||||
|
return x[i][k] < x[j][k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// testInlineStack ensures that inlining is described properly in the comments in the dump file
|
||||||
|
func testInlineStack(t *testing.T, file, function string, wantStacks [][]int) {
|
||||||
|
// this is an inlining reporting test, not an optimization test. -N makes it less fragile
|
||||||
|
dumpBytes := compileAndDump(t, file, function, "-N")
|
||||||
|
dump := bufio.NewScanner(bytes.NewReader(dumpBytes))
|
||||||
|
dumpLineNum := 0
|
||||||
|
var gotStmts []int
|
||||||
|
var gotStacks [][]int
|
||||||
|
for dump.Scan() {
|
||||||
|
line := dump.Text()
|
||||||
|
dumpLineNum++
|
||||||
|
matches := inlineLine.FindStringSubmatch(line)
|
||||||
|
if len(matches) == 2 {
|
||||||
|
stmt, err := strconv.ParseInt(matches[1], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected to parse a line number but saw %s instead on dump line #%d, error %v", matches[1], dumpLineNum, err)
|
||||||
|
}
|
||||||
|
if testing.Verbose() {
|
||||||
|
fmt.Printf("Saw stmt# %d for submatch '%s' on dump line #%d = '%s'\n", stmt, matches[1], dumpLineNum, line)
|
||||||
|
}
|
||||||
|
gotStmts = append(gotStmts, int(stmt))
|
||||||
|
} else if len(gotStmts) > 0 {
|
||||||
|
gotStacks = append(gotStacks, gotStmts)
|
||||||
|
gotStmts = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(gotStmts) > 0 {
|
||||||
|
gotStacks = append(gotStacks, gotStmts)
|
||||||
|
gotStmts = nil
|
||||||
|
}
|
||||||
|
sortInlineStacks(gotStacks)
|
||||||
|
sortInlineStacks(wantStacks)
|
||||||
|
if !reflect.DeepEqual(wantStacks, gotStacks) {
|
||||||
|
t.Errorf("wanted inlines %+v but got %+v", wantStacks, gotStacks)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// testDebugLines compiles testdata/<file> with flags -N -l and -d=ssa/genssa/dump=<function>
|
||||||
|
// then verifies that the statement-marked lines in that file are the same as those in wantStmts
|
||||||
|
// These files must all be short because this is super-fragile.
|
||||||
|
// "go build" is run in a temporary directory that is normally deleted, unless -test.v
|
||||||
|
func testDebugLines(t *testing.T, file, function string, wantStmts []int) {
|
||||||
|
dumpBytes := compileAndDump(t, file, function, "-N -l")
|
||||||
|
dump := bufio.NewScanner(bytes.NewReader(dumpBytes))
|
||||||
|
var gotStmts []int
|
||||||
|
dumpLineNum := 0
|
||||||
|
for dump.Scan() {
|
||||||
|
line := dump.Text()
|
||||||
|
dumpLineNum++
|
||||||
|
matches := asmLine.FindStringSubmatch(line)
|
||||||
|
if len(matches) == 2 {
|
||||||
|
stmt, err := strconv.ParseInt(matches[1], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected to parse a line number but saw %s instead on dump line #%d, error %v", matches[1], dumpLineNum, err)
|
||||||
|
}
|
||||||
|
if testing.Verbose() {
|
||||||
|
fmt.Printf("Saw stmt# %d for submatch '%s' on dump line #%d = '%s'\n", stmt, matches[1], dumpLineNum, line)
|
||||||
|
}
|
||||||
|
gotStmts = append(gotStmts, int(stmt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(wantStmts, gotStmts) {
|
||||||
|
t.Errorf("wanted stmts %v but got %v", wantStmts, gotStmts)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -43,7 +43,7 @@ type Func struct {
|
|||||||
logfiles map[string]writeSyncer
|
logfiles map[string]writeSyncer
|
||||||
HTMLWriter *HTMLWriter // html writer, for debugging
|
HTMLWriter *HTMLWriter // html writer, for debugging
|
||||||
DebugTest bool // default true unless $GOSSAHASH != ""; as a debugging aid, make new code conditional on this and use GOSSAHASH to binary search for failing cases
|
DebugTest bool // default true unless $GOSSAHASH != ""; as a debugging aid, make new code conditional on this and use GOSSAHASH to binary search for failing cases
|
||||||
PrintOrHtmlSSA bool // true if GOSSAFUNC matches, true even if fe.Log() (spew phase results to stdout) is false.
|
PrintOrHtmlSSA bool // true if GOSSAFUNC matches, true even if fe.Log() (spew phase results to stdout) is false. There's an odd dependence on this in debug.go for method logf.
|
||||||
ruleMatches map[string]int // number of times countRule was called during compilation for any given string
|
ruleMatches map[string]int // number of times countRule was called during compilation for any given string
|
||||||
ABI0 *abi.ABIConfig // A copy, for no-sync access
|
ABI0 *abi.ABIConfig // A copy, for no-sync access
|
||||||
ABI1 *abi.ABIConfig // A copy, for no-sync access
|
ABI1 *abi.ABIConfig // A copy, for no-sync access
|
||||||
|
@ -6,6 +6,7 @@ package ssa
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"cmd/internal/src"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -83,13 +84,26 @@ func (p stringFuncPrinter) endBlock(b *Block, reachable bool) {
|
|||||||
fmt.Fprintln(p.w, " "+b.LongString())
|
fmt.Fprintln(p.w, " "+b.LongString())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StmtString(p src.XPos) string {
|
||||||
|
linenumber := "(?) "
|
||||||
|
if p.IsKnown() {
|
||||||
|
pfx := ""
|
||||||
|
if p.IsStmt() == src.PosIsStmt {
|
||||||
|
pfx = "+"
|
||||||
|
}
|
||||||
|
if p.IsStmt() == src.PosNotStmt {
|
||||||
|
pfx = "-"
|
||||||
|
}
|
||||||
|
linenumber = fmt.Sprintf("(%s%d) ", pfx, p.Line())
|
||||||
|
}
|
||||||
|
return linenumber
|
||||||
|
}
|
||||||
|
|
||||||
func (p stringFuncPrinter) value(v *Value, live bool) {
|
func (p stringFuncPrinter) value(v *Value, live bool) {
|
||||||
if !p.printDead && !live {
|
if !p.printDead && !live {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Fprint(p.w, " ")
|
fmt.Fprintf(p.w, " %s", StmtString(v.Pos))
|
||||||
//fmt.Fprint(p.w, v.Block.Func.fe.Pos(v.Pos))
|
|
||||||
//fmt.Fprint(p.w, ": ")
|
|
||||||
fmt.Fprint(p.w, v.LongString())
|
fmt.Fprint(p.w, v.LongString())
|
||||||
if !live {
|
if !live {
|
||||||
fmt.Fprint(p.w, " DEAD")
|
fmt.Fprint(p.w, " DEAD")
|
||||||
|
17
src/cmd/compile/internal/ssa/testdata/inline-dump.go
vendored
Normal file
17
src/cmd/compile/internal/ssa/testdata/inline-dump.go
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
func f(m, n int) int {
|
||||||
|
a := g(n)
|
||||||
|
b := g(m)
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
func g(x int) int {
|
||||||
|
y := h(x + 1)
|
||||||
|
z := h(x - 1)
|
||||||
|
return y + z
|
||||||
|
}
|
||||||
|
|
||||||
|
func h(x int) int {
|
||||||
|
return x * x
|
||||||
|
}
|
12
src/cmd/compile/internal/ssa/testdata/sayhi.go
vendored
Normal file
12
src/cmd/compile/internal/ssa/testdata/sayhi.go
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sayhi(n int, wg *sync.WaitGroup) {
|
||||||
|
fmt.Println("hi", n)
|
||||||
|
fmt.Println("hi", n)
|
||||||
|
wg.Done()
|
||||||
|
}
|
@ -6703,7 +6703,8 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
|
|||||||
var progToValue map[*obj.Prog]*ssa.Value
|
var progToValue map[*obj.Prog]*ssa.Value
|
||||||
var progToBlock map[*obj.Prog]*ssa.Block
|
var progToBlock map[*obj.Prog]*ssa.Block
|
||||||
var valueToProgAfter []*obj.Prog // The first Prog following computation of a value v; v is visible at this point.
|
var valueToProgAfter []*obj.Prog // The first Prog following computation of a value v; v is visible at this point.
|
||||||
if f.PrintOrHtmlSSA {
|
gatherPrintInfo := f.PrintOrHtmlSSA || ssa.GenssaDump[f.Name]
|
||||||
|
if gatherPrintInfo {
|
||||||
progToValue = make(map[*obj.Prog]*ssa.Value, f.NumValues())
|
progToValue = make(map[*obj.Prog]*ssa.Value, f.NumValues())
|
||||||
progToBlock = make(map[*obj.Prog]*ssa.Block, f.NumBlocks())
|
progToBlock = make(map[*obj.Prog]*ssa.Block, f.NumBlocks())
|
||||||
f.Logf("genssa %s\n", f.Name)
|
f.Logf("genssa %s\n", f.Name)
|
||||||
@ -6814,7 +6815,7 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
|
|||||||
valueToProgAfter[v.ID] = s.pp.Next
|
valueToProgAfter[v.ID] = s.pp.Next
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.PrintOrHtmlSSA {
|
if gatherPrintInfo {
|
||||||
for ; x != s.pp.Next; x = x.Link {
|
for ; x != s.pp.Next; x = x.Link {
|
||||||
progToValue[x] = v
|
progToValue[x] = v
|
||||||
}
|
}
|
||||||
@ -6844,7 +6845,7 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
|
|||||||
x := s.pp.Next
|
x := s.pp.Next
|
||||||
s.SetPos(b.Pos)
|
s.SetPos(b.Pos)
|
||||||
Arch.SSAGenBlock(&s, b, next)
|
Arch.SSAGenBlock(&s, b, next)
|
||||||
if f.PrintOrHtmlSSA {
|
if gatherPrintInfo {
|
||||||
for ; x != s.pp.Next; x = x.Link {
|
for ; x != s.pp.Next; x = x.Link {
|
||||||
progToBlock[x] = b
|
progToBlock[x] = b
|
||||||
}
|
}
|
||||||
@ -7023,6 +7024,54 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
|
|||||||
buf.WriteString("</code>")
|
buf.WriteString("</code>")
|
||||||
f.HTMLWriter.WriteColumn("genssa", "genssa", "ssa-prog", buf.String())
|
f.HTMLWriter.WriteColumn("genssa", "genssa", "ssa-prog", buf.String())
|
||||||
}
|
}
|
||||||
|
if ssa.GenssaDump[f.Name] {
|
||||||
|
fi := f.DumpFileForPhase("genssa")
|
||||||
|
if fi != nil {
|
||||||
|
|
||||||
|
// inliningDiffers if any filename changes or if any line number except the innermost (index 0) changes.
|
||||||
|
inliningDiffers := func(a, b []src.Pos) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for i := range a {
|
||||||
|
if a[i].Filename() != b[i].Filename() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if i > 0 && a[i].Line() != b[i].Line() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var allPosOld []src.Pos
|
||||||
|
var allPos []src.Pos
|
||||||
|
|
||||||
|
for p := pp.Text; p != nil; p = p.Link {
|
||||||
|
if p.Pos.IsKnown() {
|
||||||
|
allPos = p.AllPos(allPos)
|
||||||
|
if inliningDiffers(allPos, allPosOld) {
|
||||||
|
for i := len(allPos) - 1; i >= 0; i-- {
|
||||||
|
pos := allPos[i]
|
||||||
|
fmt.Fprintf(fi, "# %s:%d\n", pos.Filename(), pos.Line())
|
||||||
|
}
|
||||||
|
allPos, allPosOld = allPosOld, allPos // swap, not copy, so that they do not share slice storage.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var s string
|
||||||
|
if v, ok := progToValue[p]; ok {
|
||||||
|
s = v.String()
|
||||||
|
} else if b, ok := progToBlock[p]; ok {
|
||||||
|
s = b.String()
|
||||||
|
} else {
|
||||||
|
s = " " // most value and branch strings are 2-3 characters long
|
||||||
|
}
|
||||||
|
fmt.Fprintf(fi, " %-6s\t%.5d %s\t%s\n", s, p.Pc, ssa.StmtString(p.Pos), p.InstructionString())
|
||||||
|
}
|
||||||
|
fi.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
defframe(&s, e, f)
|
defframe(&s, e, f)
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ package obj
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"cmd/internal/objabi"
|
"cmd/internal/objabi"
|
||||||
|
"cmd/internal/src"
|
||||||
"fmt"
|
"fmt"
|
||||||
"internal/buildcfg"
|
"internal/buildcfg"
|
||||||
"io"
|
"io"
|
||||||
@ -47,6 +48,10 @@ func (p *Prog) InnermostFilename() string {
|
|||||||
return pos.Filename()
|
return pos.Filename()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Prog) AllPos(result []src.Pos) []src.Pos {
|
||||||
|
return p.Ctxt.AllPos(p.Pos, result)
|
||||||
|
}
|
||||||
|
|
||||||
var armCondCode = []string{
|
var armCondCode = []string{
|
||||||
".EQ",
|
".EQ",
|
||||||
".NE",
|
".NE",
|
||||||
|
Loading…
Reference in New Issue
Block a user