1
0
mirror of https://github.com/golang/go synced 2024-11-23 20:40:07 -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:
David Chase 2016-10-18 12:30:54 -04:00
parent 2ddd07138d
commit cf01e6f212
2 changed files with 367 additions and 0 deletions

View File

@ -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") }

View 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)
}
`)
}