mirror of
https://github.com/golang/go
synced 2024-11-23 22:20:02 -07:00
cmd/compile: add test generator for control and data flow
From a compact specification of control flow graphs, generate complete set of possible assignment patterns to output y, and also generate an interpretable specification. Compiles (hoping for crash, or not) and then runs, where the run checks function output against interpreted output for various inputs observed to terminate in the interpreter. In ssa_test.go, added ability to generate a test and run (compile and run) the generated test, possibly with modified environment variables. The generated test is compiled including the -D=ssa/check/on flag, and if the interpreter terminates in a small number of steps, then it is also run to check the result. Change-Id: I392c828e36c543411b7733ca0799628452733276 Reviewed-on: https://go-review.googlesource.com/22751 Run-TryBot: David Chase <drchase@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
parent
2ddd07138d
commit
cf01e6f212
@ -7,8 +7,10 @@ package gc
|
||||
import (
|
||||
"bytes"
|
||||
"internal/testenv"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@ -40,6 +42,56 @@ func doTest(t *testing.T, filename string, kind string) {
|
||||
}
|
||||
}
|
||||
|
||||
// runGenTest runs a test-generator, then runs the generated test.
|
||||
// Generated test can either fail in compilation or execution.
|
||||
// The environment variable parameter(s) is passed to the run
|
||||
// of the generated test.
|
||||
func runGenTest(t *testing.T, filename, tmpname string, ev ...string) {
|
||||
testenv.MustHaveGoRun(t)
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd := exec.Command("go", "run", filepath.Join("testdata", filename))
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatalf("Failed: %v:\nOut: %s\nStderr: %s\n", err, &stdout, &stderr)
|
||||
}
|
||||
// Write stdout into a temporary file
|
||||
tmpdir, ok := ioutil.TempDir("", tmpname)
|
||||
if ok != nil {
|
||||
t.Fatalf("Failed to create temporary directory")
|
||||
}
|
||||
|
||||
rungo := filepath.Join(tmpdir, "run.go")
|
||||
ok = ioutil.WriteFile(rungo, stdout.Bytes(), 0600)
|
||||
if ok != nil {
|
||||
t.Fatalf("Failed to create temporary file " + rungo)
|
||||
}
|
||||
|
||||
stdout.Reset()
|
||||
stderr.Reset()
|
||||
cmd = exec.Command("go", "run", "-gcflags", "-d=ssa/check/on", rungo)
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
cmd.Env = append(cmd.Env, ev...)
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatalf("Failed: %v:\nOut: %s\nStderr: %s\n", err, &stdout, &stderr)
|
||||
}
|
||||
if s := stderr.String(); s != "" {
|
||||
t.Errorf("Stderr = %s\nWant empty", s)
|
||||
}
|
||||
if s := stdout.String(); s != "" {
|
||||
t.Errorf("Stdout = %s\nWant empty", s)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGenFlowGraph(t *testing.T) {
|
||||
runGenTest(t, "flowgraph_generator1.go", "ssa_fg_tmp1")
|
||||
if runtime.GOOS != "windows" {
|
||||
runGenTest(t, "flowgraph_generator1.go", "ssa_fg_tmp2", "GO_SSA_PHI_LOC_CUTOFF=0")
|
||||
}
|
||||
}
|
||||
|
||||
// TestShortCircuit tests OANDAND and OOROR expressions and short circuiting.
|
||||
func TestShortCircuit(t *testing.T) { runTest(t, "short.go") }
|
||||
|
||||
|
315
src/cmd/compile/internal/gc/testdata/flowgraph_generator1.go
vendored
Normal file
315
src/cmd/compile/internal/gc/testdata/flowgraph_generator1.go
vendored
Normal file
@ -0,0 +1,315 @@
|
||||
// Copyright 2016 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// make fake flow graph.
|
||||
|
||||
// The blocks of the flow graph are designated with letters A
|
||||
// through Z, always including A (start block) and Z (exit
|
||||
// block) The specification of a flow graph is a comma-
|
||||
// separated list of block successor words, for blocks ordered
|
||||
// A, B, C etc, where each block except Z has one or two
|
||||
// successors, and any block except A can be a target. Within
|
||||
// the generated code, each block with two successors includes
|
||||
// a conditional testing x & 1 != 0 (x is the input parameter
|
||||
// to the generated function) and also unconditionally shifts x
|
||||
// right by one, so that different inputs generate different
|
||||
// execution paths, including loops. Every block inverts a
|
||||
// global binary to ensure it is not empty. For a flow graph
|
||||
// with J words (J+1 blocks), a J-1 bit serial number specifies
|
||||
// which blocks (not including A and Z) include an increment of
|
||||
// the return variable y by increasing powers of 10, and a
|
||||
// different version of the test function is created for each
|
||||
// of the 2-to-the-(J-1) serial numbers.
|
||||
|
||||
// For each generated function a compact summary is also
|
||||
// created so that the generated funtion can be simulated
|
||||
// with a simple interpreter to sanity check the behavior of
|
||||
// the compiled code.
|
||||
|
||||
// For example:
|
||||
|
||||
// func BC_CD_BE_BZ_CZ101(x int64) int64 {
|
||||
// y := int64(0)
|
||||
// var b int64
|
||||
// _ = b
|
||||
// b = x & 1
|
||||
// x = x >> 1
|
||||
// if b != 0 {
|
||||
// goto C
|
||||
// }
|
||||
// goto B
|
||||
// B:
|
||||
// glob_ = !glob_
|
||||
// y += 1
|
||||
// b = x & 1
|
||||
// x = x >> 1
|
||||
// if b != 0 {
|
||||
// goto D
|
||||
// }
|
||||
// goto C
|
||||
// C:
|
||||
// glob_ = !glob_
|
||||
// // no y increment
|
||||
// b = x & 1
|
||||
// x = x >> 1
|
||||
// if b != 0 {
|
||||
// goto E
|
||||
// }
|
||||
// goto B
|
||||
// D:
|
||||
// glob_ = !glob_
|
||||
// y += 10
|
||||
// b = x & 1
|
||||
// x = x >> 1
|
||||
// if b != 0 {
|
||||
// goto Z
|
||||
// }
|
||||
// goto B
|
||||
// E:
|
||||
// glob_ = !glob_
|
||||
// // no y increment
|
||||
// b = x & 1
|
||||
// x = x >> 1
|
||||
// if b != 0 {
|
||||
// goto Z
|
||||
// }
|
||||
// goto C
|
||||
// Z:
|
||||
// return y
|
||||
// }
|
||||
|
||||
// {f:BC_CD_BE_BZ_CZ101,
|
||||
// maxin:32, blocks:[]blo{
|
||||
// blo{inc:0, cond:true, succs:[2]int64{1, 2}},
|
||||
// blo{inc:1, cond:true, succs:[2]int64{2, 3}},
|
||||
// blo{inc:0, cond:true, succs:[2]int64{1, 4}},
|
||||
// blo{inc:10, cond:true, succs:[2]int64{1, 25}},
|
||||
// blo{inc:0, cond:true, succs:[2]int64{2, 25}},}},
|
||||
|
||||
var labels string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
func blocks(spec string) (blocks []string, fnameBase string) {
|
||||
spec = strings.ToUpper(spec)
|
||||
blocks = strings.Split(spec, ",")
|
||||
fnameBase = strings.Replace(spec, ",", "_", -1)
|
||||
return
|
||||
}
|
||||
|
||||
func makeFunctionFromFlowGraph(blocks []blo, fname string) string {
|
||||
s := ""
|
||||
|
||||
for j := range blocks {
|
||||
// begin block
|
||||
if j == 0 {
|
||||
// block A, implicit label
|
||||
s += `
|
||||
func ` + fname + `(x int64) int64 {
|
||||
y := int64(0)
|
||||
var b int64
|
||||
_ = b`
|
||||
} else {
|
||||
// block B,C, etc, explicit label w/ conditional increment
|
||||
l := labels[j : j+1]
|
||||
yeq := `
|
||||
// no y increment`
|
||||
if blocks[j].inc != 0 {
|
||||
yeq = `
|
||||
y += ` + fmt.Sprintf("%d", blocks[j].inc)
|
||||
}
|
||||
|
||||
s += `
|
||||
` + l + `:
|
||||
glob = !glob` + yeq
|
||||
}
|
||||
|
||||
// edges to successors
|
||||
if blocks[j].cond { // conditionally branch to second successor
|
||||
s += `
|
||||
b = x & 1
|
||||
x = x >> 1
|
||||
if b != 0 {` + `
|
||||
goto ` + string(labels[blocks[j].succs[1]]) + `
|
||||
}`
|
||||
|
||||
}
|
||||
// branch to first successor
|
||||
s += `
|
||||
goto ` + string(labels[blocks[j].succs[0]])
|
||||
}
|
||||
|
||||
// end block (Z)
|
||||
s += `
|
||||
Z:
|
||||
return y
|
||||
}
|
||||
`
|
||||
return s
|
||||
}
|
||||
|
||||
var graphs []string = []string{
|
||||
"Z", "BZ,Z", "B,BZ", "BZ,BZ",
|
||||
"ZB,Z", "B,ZB", "ZB,BZ", "ZB,ZB",
|
||||
|
||||
"BC,C,Z", "BC,BC,Z", "BC,BC,BZ",
|
||||
"BC,Z,Z", "BC,ZC,Z", "BC,ZC,BZ",
|
||||
"BZ,C,Z", "BZ,BC,Z", "BZ,CZ,Z",
|
||||
"BZ,C,BZ", "BZ,BC,BZ", "BZ,CZ,BZ",
|
||||
"BZ,C,CZ", "BZ,BC,CZ", "BZ,CZ,CZ",
|
||||
|
||||
"BC,CD,BE,BZ,CZ",
|
||||
"BC,BD,CE,CZ,BZ",
|
||||
"BC,BD,CE,FZ,GZ,F,G",
|
||||
"BC,BD,CE,FZ,GZ,G,F",
|
||||
|
||||
"BC,DE,BE,FZ,FZ,Z",
|
||||
"BC,DE,BE,FZ,ZF,Z",
|
||||
"BC,DE,BE,ZF,FZ,Z",
|
||||
"BC,DE,EB,FZ,FZ,Z",
|
||||
"BC,ED,BE,FZ,FZ,Z",
|
||||
"CB,DE,BE,FZ,FZ,Z",
|
||||
|
||||
"CB,ED,BE,FZ,FZ,Z",
|
||||
"BC,ED,EB,FZ,ZF,Z",
|
||||
"CB,DE,EB,ZF,FZ,Z",
|
||||
"CB,ED,EB,FZ,FZ,Z",
|
||||
|
||||
"BZ,CD,CD,CE,BZ",
|
||||
"EC,DF,FG,ZC,GB,BE,FD",
|
||||
"BH,CF,DG,HE,BF,CG,DH,BZ",
|
||||
}
|
||||
|
||||
// blo describes a block in the generated/interpreted code
|
||||
type blo struct {
|
||||
inc int64 // increment amount
|
||||
cond bool // block ends in conditional
|
||||
succs [2]int64
|
||||
}
|
||||
|
||||
// strings2blocks converts a slice of strings specifying
|
||||
// successors into a slice of blo encoding the blocks in a
|
||||
// common form easy to execute or interpret.
|
||||
func strings2blocks(blocks []string, fname string, i int) (bs []blo, cond uint) {
|
||||
bs = make([]blo, len(blocks))
|
||||
edge := int64(1)
|
||||
cond = 0
|
||||
k := uint(0)
|
||||
for j, s := range blocks {
|
||||
if j == 0 {
|
||||
} else {
|
||||
if (i>>k)&1 != 0 {
|
||||
bs[j].inc = edge
|
||||
edge *= 10
|
||||
}
|
||||
k++
|
||||
}
|
||||
if len(s) > 1 {
|
||||
bs[j].succs[1] = int64(blocks[j][1] - 'A')
|
||||
bs[j].cond = true
|
||||
cond++
|
||||
}
|
||||
bs[j].succs[0] = int64(blocks[j][0] - 'A')
|
||||
}
|
||||
return bs, cond
|
||||
}
|
||||
|
||||
// fmtBlocks writes out the blocks for consumption in the generated test
|
||||
func fmtBlocks(bs []blo) string {
|
||||
s := "[]blo{"
|
||||
for _, b := range bs {
|
||||
s += fmt.Sprintf("blo{inc:%d, cond:%v, succs:[2]int64{%d, %d}},", b.inc, b.cond, b.succs[0], b.succs[1])
|
||||
}
|
||||
s += "}"
|
||||
return s
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Printf(`// This is a machine-generated test file from flowgraph_generator1.go.
|
||||
package main
|
||||
import "fmt"
|
||||
var glob bool
|
||||
`)
|
||||
s := "var funs []fun = []fun{"
|
||||
for _, g := range graphs {
|
||||
split, fnameBase := blocks(g)
|
||||
nconfigs := 1 << uint(len(split)-1)
|
||||
|
||||
for i := 0; i < nconfigs; i++ {
|
||||
fname := fnameBase + fmt.Sprintf("%b", i)
|
||||
bs, k := strings2blocks(split, fname, i)
|
||||
fmt.Printf("%s", makeFunctionFromFlowGraph(bs, fname))
|
||||
s += `
|
||||
{f:` + fname + `, maxin:` + fmt.Sprintf("%d", 1<<k) + `, blocks:` + fmtBlocks(bs) + `},`
|
||||
}
|
||||
|
||||
}
|
||||
s += `}
|
||||
`
|
||||
// write types for name+array tables.
|
||||
fmt.Printf("%s",
|
||||
`
|
||||
type blo struct {
|
||||
inc int64
|
||||
cond bool
|
||||
succs [2]int64
|
||||
}
|
||||
type fun struct {
|
||||
f func(int64) int64
|
||||
maxin int64
|
||||
blocks []blo
|
||||
}
|
||||
`)
|
||||
// write table of function names and blo arrays.
|
||||
fmt.Printf("%s", s)
|
||||
|
||||
// write interpreter and main/test
|
||||
fmt.Printf("%s", `
|
||||
func interpret(blocks []blo, x int64) (int64, bool) {
|
||||
y := int64(0)
|
||||
last := int64(25) // 'Z'-'A'
|
||||
j := int64(0)
|
||||
for i := 0; i < 4*len(blocks); i++ {
|
||||
b := blocks[j]
|
||||
y += b.inc
|
||||
next := b.succs[0]
|
||||
if b.cond {
|
||||
c := x&1 != 0
|
||||
x = x>>1
|
||||
if c {
|
||||
next = b.succs[1]
|
||||
}
|
||||
}
|
||||
if next == last {
|
||||
return y, true
|
||||
}
|
||||
j = next
|
||||
}
|
||||
return -1, false
|
||||
}
|
||||
|
||||
func main() {
|
||||
sum := int64(0)
|
||||
for i, f := range funs {
|
||||
for x := int64(0); x < 16*f.maxin; x++ {
|
||||
y, ok := interpret(f.blocks, x)
|
||||
if ok {
|
||||
yy := f.f(x)
|
||||
if y != yy {
|
||||
fmt.Printf("y(%d) != yy(%d), x=%b, i=%d, blocks=%v\n", y, yy, x, i, f.blocks)
|
||||
return
|
||||
}
|
||||
sum += y
|
||||
}
|
||||
}
|
||||
}
|
||||
// fmt.Printf("Sum of all returns over all terminating inputs is %d\n", sum)
|
||||
}
|
||||
`)
|
||||
}
|
Loading…
Reference in New Issue
Block a user