From cf01e6f212cd2cae8c3595057ff739d942d18479 Mon Sep 17 00:00:00 2001 From: David Chase Date: Tue, 18 Oct 2016 12:30:54 -0400 Subject: [PATCH] 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 TryBot-Result: Gobot Gobot Reviewed-by: Keith Randall --- src/cmd/compile/internal/gc/ssa_test.go | 52 +++ .../gc/testdata/flowgraph_generator1.go | 315 ++++++++++++++++++ 2 files changed, 367 insertions(+) create mode 100644 src/cmd/compile/internal/gc/testdata/flowgraph_generator1.go diff --git a/src/cmd/compile/internal/gc/ssa_test.go b/src/cmd/compile/internal/gc/ssa_test.go index bb315b97e83..cbeb8ad0ce1 100644 --- a/src/cmd/compile/internal/gc/ssa_test.go +++ b/src/cmd/compile/internal/gc/ssa_test.go @@ -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") } diff --git a/src/cmd/compile/internal/gc/testdata/flowgraph_generator1.go b/src/cmd/compile/internal/gc/testdata/flowgraph_generator1.go new file mode 100644 index 00000000000..48b05f74918 --- /dev/null +++ b/src/cmd/compile/internal/gc/testdata/flowgraph_generator1.go @@ -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<>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) +} +`) +}