cmd/internal/ssa: utility functions to make Funcs
Adds a more convenient way to define Funcs for testing.
For instance,
b1:
v1 = Arg <mem> [.mem]
Plain -> b2
b2:
Exit v1
b3:
v2 = Const <bool> [true]
If v2 -> b3 b2
can be defined as
fun :=Fun("entry",
Bloc("entry",
Valu("mem", OpArg, TypeMem, ".mem"),
Goto("exit")),
Bloc("exit",
Exit("mem")),
Bloc("deadblock",
Valu("deadval", OpConst, TypeBool, true),
If("deadval", "deadblock", "exit")))
Also add an Equiv function to test two Funcs for equivalence.
Change-Id: If1633865aeefb8e765e772b6dad19250d93a413a
Reviewed-on: https://go-review.googlesource.com/9992
Reviewed-by: Keith Randall <khr@golang.org>
diff --git a/src/cmd/internal/ssa/deadcode_test.go b/src/cmd/internal/ssa/deadcode_test.go
index 1b7c81c..ced46e5 100644
--- a/src/cmd/internal/ssa/deadcode_test.go
+++ b/src/cmd/internal/ssa/deadcode_test.go
@@ -2,44 +2,35 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// TODO: these tests are pretty verbose. Is there a way to simplify
-// building a small Func for testing?
-
-package ssa_test
+package ssa
import (
- . "cmd/internal/ssa"
"testing"
)
func TestDeadLoop(t *testing.T) {
- f := new(Func)
- entry := f.NewBlock(BlockPlain)
- exit := f.NewBlock(BlockExit)
- f.Entry = entry
- addEdge(entry, exit)
- mem := entry.NewValue(OpArg, TypeMem, ".mem")
- exit.Control = mem
+ fun := Fun("entry",
+ Bloc("entry",
+ Valu("mem", OpArg, TypeMem, ".mem"),
+ Goto("exit")),
+ Bloc("exit",
+ Exit("mem")),
+ // dead loop
+ Bloc("deadblock",
+ // dead value in dead block
+ Valu("deadval", OpConst, TypeBool, true),
+ If("deadval", "deadblock", "exit")))
- // dead loop
- deadblock := f.NewBlock(BlockIf)
- addEdge(deadblock, deadblock)
- addEdge(deadblock, exit)
+ CheckFunc(fun.f)
+ Deadcode(fun.f)
+ CheckFunc(fun.f)
- // dead value in dead block
- deadval := deadblock.NewValue(OpConst, TypeBool, true)
- deadblock.Control = deadval
-
- CheckFunc(f)
- Deadcode(f)
- CheckFunc(f)
-
- for _, b := range f.Blocks {
- if b == deadblock {
+ for _, b := range fun.f.Blocks {
+ if b == fun.blocks["deadblock"] {
t.Errorf("dead block not removed")
}
for _, v := range b.Values {
- if v == deadval {
+ if v == fun.values["deadval"] {
t.Errorf("control value of dead block not removed")
}
}
@@ -47,23 +38,21 @@
}
func TestDeadValue(t *testing.T) {
- f := new(Func)
- entry := f.NewBlock(BlockPlain)
- exit := f.NewBlock(BlockExit)
- f.Entry = entry
- addEdge(entry, exit)
- mem := entry.NewValue(OpArg, TypeMem, ".mem")
- exit.Control = mem
+ fun := Fun("entry",
+ Bloc("entry",
+ Valu("mem", OpArg, TypeMem, ".mem"),
+ Valu("deadval", OpConst, TypeInt64, int64(37)),
+ Goto("exit")),
+ Bloc("exit",
+ Exit("mem")))
- deadval := entry.NewValue(OpConst, TypeInt64, int64(37))
+ CheckFunc(fun.f)
+ Deadcode(fun.f)
+ CheckFunc(fun.f)
- CheckFunc(f)
- Deadcode(f)
- CheckFunc(f)
-
- for _, b := range f.Blocks {
+ for _, b := range fun.f.Blocks {
for _, v := range b.Values {
- if v == deadval {
+ if v == fun.values["deadval"] {
t.Errorf("dead value not removed")
}
}
@@ -71,42 +60,34 @@
}
func TestNeverTaken(t *testing.T) {
- f := new(Func)
- entry := f.NewBlock(BlockIf)
- exit := f.NewBlock(BlockExit)
- then := f.NewBlock(BlockPlain)
- else_ := f.NewBlock(BlockPlain)
- f.Entry = entry
- addEdge(entry, then)
- addEdge(entry, else_)
- addEdge(then, exit)
- addEdge(else_, exit)
- mem := entry.NewValue(OpArg, TypeMem, ".mem")
- exit.Control = mem
+ fun := Fun("entry",
+ Bloc("entry",
+ Valu("cond", OpConst, TypeBool, false),
+ Valu("mem", OpArg, TypeMem, ".mem"),
+ If("cond", "then", "else")),
+ Bloc("then",
+ Goto("exit")),
+ Bloc("else",
+ Goto("exit")),
+ Bloc("exit",
+ Exit("mem")))
- cond := entry.NewValue(OpConst, TypeBool, false)
- entry.Control = cond
+ CheckFunc(fun.f)
+ Deadcode(fun.f)
+ CheckFunc(fun.f)
- CheckFunc(f)
- Deadcode(f)
- CheckFunc(f)
-
- if entry.Kind != BlockPlain {
+ if fun.blocks["entry"].Kind != BlockPlain {
t.Errorf("if(false) not simplified")
}
- for _, b := range f.Blocks {
- if b == then {
+ for _, b := range fun.f.Blocks {
+ if b == fun.blocks["then"] {
t.Errorf("then block still present")
}
for _, v := range b.Values {
- if v == cond {
+ if v == fun.values["cond"] {
t.Errorf("constant condition still present")
}
}
}
-}
-func addEdge(b, c *Block) {
- b.Succs = append(b.Succs, c)
- c.Preds = append(c.Preds, b)
}
diff --git a/src/cmd/internal/ssa/func_test.go b/src/cmd/internal/ssa/func_test.go
new file mode 100644
index 0000000..e7619ca
--- /dev/null
+++ b/src/cmd/internal/ssa/func_test.go
@@ -0,0 +1,401 @@
+// This file contains some utility functions to help define Funcs for testing.
+// As an example, the following func
+//
+// b1:
+// v1 = Arg <mem> [.mem]
+// Plain -> b2
+// b2:
+// Exit v1
+// b3:
+// v2 = Const <bool> [true]
+// If v2 -> b3 b2
+//
+// can be defined as
+//
+// fun := Fun("entry",
+// Bloc("entry",
+// Valu("mem", OpArg, TypeMem, ".mem"),
+// Goto("exit")),
+// Bloc("exit",
+// Exit("mem")),
+// Bloc("deadblock",
+// Valu("deadval", OpConst, TypeBool, true),
+// If("deadval", "deadblock", "exit")))
+//
+// and the Blocks or Values used in the Func can be accessed
+// like this:
+// fun.blocks["entry"] or fun.values["deadval"]
+
+package ssa
+
+// TODO(matloob): Choose better names for Fun, Bloc, Goto, etc.
+// TODO(matloob): Write a parser for the Func disassembly. Maybe
+// the parser can be used instead of Fun.
+
+import (
+ "log"
+ "reflect"
+ "testing"
+)
+
+// Compare two Funcs for equivalence. Their CFGs must be isomorphic,
+// and their values must correspond.
+// Requires that values and predecessors are in the same order, even
+// though Funcs could be equivalent when they are not.
+// TODO(matloob): Allow values and predecessors to be in different
+// orders if the CFG are otherwise equivalent.
+func Equiv(f, g *Func) bool {
+ valcor := make(map[*Value]*Value)
+ var checkVal func(fv, gv *Value) bool
+ checkVal = func(fv, gv *Value) bool {
+ if fv == nil && gv == nil {
+ return true
+ }
+ if valcor[fv] == nil && valcor[gv] == nil {
+ valcor[fv] = gv
+ valcor[gv] = fv
+ // Ignore ids. Ops and Types are compared for equality.
+ // TODO(matloob): Make sure types are canonical and can
+ // be compared for equality.
+ if fv.Op != gv.Op || fv.Type != gv.Type {
+ return false
+ }
+ if !reflect.DeepEqual(fv.Aux, gv.Aux) {
+ // This makes the assumption that aux values can be compared
+ // using DeepEqual.
+ // TODO(matloob): Aux values may be *gc.Sym pointers in the near
+ // future. Make sure they are canonical.
+ return false
+ }
+ if len(fv.Args) != len(gv.Args) {
+ return false
+ }
+ for i := range fv.Args {
+ if !checkVal(fv.Args[i], gv.Args[i]) {
+ return false
+ }
+ }
+ }
+ return valcor[fv] == gv && valcor[gv] == fv
+ }
+ blkcor := make(map[*Block]*Block)
+ var checkBlk func(fb, gb *Block) bool
+ checkBlk = func(fb, gb *Block) bool {
+ if blkcor[fb] == nil && blkcor[gb] == nil {
+ blkcor[fb] = gb
+ blkcor[gb] = fb
+ // ignore ids
+ if fb.Kind != gb.Kind {
+ return false
+ }
+ if len(fb.Values) != len(gb.Values) {
+ return false
+ }
+ for i := range fb.Values {
+ if !checkVal(fb.Values[i], gb.Values[i]) {
+ return false
+ }
+ }
+ if len(fb.Succs) != len(gb.Succs) {
+ return false
+ }
+ for i := range fb.Succs {
+ if !checkBlk(fb.Succs[i], gb.Succs[i]) {
+ return false
+ }
+ }
+ if len(fb.Preds) != len(gb.Preds) {
+ return false
+ }
+ for i := range fb.Preds {
+ if !checkBlk(fb.Preds[i], gb.Preds[i]) {
+ return false
+ }
+ }
+ return true
+
+ }
+ return blkcor[fb] == gb && blkcor[gb] == fb
+ }
+
+ return checkBlk(f.Entry, g.Entry)
+}
+
+// fun is the return type of Fun. It contains the created func
+// itself as well as indexes from block and value names into the
+// corresponding Blocks and Values.
+type fun struct {
+ f *Func
+ blocks map[string]*Block
+ values map[string]*Value
+}
+
+// Fun takes the name of an entry bloc and a series of Bloc calls, and
+// returns a fun containing the composed Func. entry must be a name
+// supplied to one of the Bloc functions. Each of the bloc names and
+// valu names should be unique across the Fun.
+func Fun(entry string, blocs ...bloc) fun {
+ f := new(Func)
+ blocks := make(map[string]*Block)
+ values := make(map[string]*Value)
+ // Create all the blocks and values.
+ for _, bloc := range blocs {
+ b := f.NewBlock(bloc.control.kind)
+ blocks[bloc.name] = b
+ for _, valu := range bloc.valus {
+ // args are filled in the second pass.
+ values[valu.name] = b.NewValue(valu.op, valu.t, valu.aux)
+ }
+ }
+ // Connect the blocks together and specify control values.
+ f.Entry = blocks[entry]
+ for _, bloc := range blocs {
+ b := blocks[bloc.name]
+ c := bloc.control
+ // Specify control values.
+ if c.control != "" {
+ cval, ok := values[c.control]
+ if !ok {
+ log.Panicf("control value for block %s missing", bloc.name)
+ }
+ b.Control = cval
+ }
+ // Fill in args.
+ for _, valu := range bloc.valus {
+ v := values[valu.name]
+ for _, arg := range valu.args {
+ a, ok := values[arg]
+ if !ok {
+ log.Panicf("arg %s missing for value %s in block %s",
+ arg, valu.name, bloc.name)
+ }
+ v.AddArg(a)
+ }
+ }
+ // Connect to successors.
+ for _, succ := range c.succs {
+ addEdge(b, blocks[succ])
+ }
+ }
+ return fun{f, blocks, values}
+}
+
+// Bloc defines a block for Fun. The bloc name should be unique
+// across the containing Fun. entries should consist of calls to valu,
+// as well as one call to Goto, If, or Exit to specify the block kind.
+func Bloc(name string, entries ...interface{}) bloc {
+ b := bloc{}
+ b.name = name
+ seenCtrl := false
+ for _, e := range entries {
+ switch v := e.(type) {
+ case ctrl:
+ // there should be exactly one Ctrl entry.
+ if seenCtrl {
+ log.Panicf("already seen control for block %s", name)
+ }
+ b.control = v
+ seenCtrl = true
+ case valu:
+ b.valus = append(b.valus, v)
+ }
+ }
+ if !seenCtrl {
+ log.Panicf("block %s doesn't have control", b.name)
+ }
+ return b
+}
+
+// Valu defines a value in a block.
+func Valu(name string, op Op, t Type, aux interface{}, args ...string) valu {
+ return valu{name, op, t, aux, args}
+}
+
+// Goto specifies that this is a BlockPlain and names the single successor.
+// TODO(matloob): choose a better name.
+func Goto(succ string) ctrl {
+ return ctrl{BlockPlain, "", []string{succ}}
+}
+
+// If specifies a BlockIf.
+func If(cond, sub, alt string) ctrl {
+ return ctrl{BlockIf, cond, []string{sub, alt}}
+}
+
+// Exit specifies a BlockExit.
+func Exit(arg string) ctrl {
+ return ctrl{BlockExit, arg, []string{}}
+}
+
+// bloc, ctrl, and valu are internal structures used by Bloc, Valu, Goto,
+// If, and Exit to help define blocks.
+
+type bloc struct {
+ name string
+ control ctrl
+ valus []valu
+}
+
+type ctrl struct {
+ kind BlockKind
+ control string
+ succs []string
+}
+
+type valu struct {
+ name string
+ op Op
+ t Type
+ aux interface{}
+ args []string
+}
+
+func addEdge(b, c *Block) {
+ b.Succs = append(b.Succs, c)
+ c.Preds = append(c.Preds, b)
+}
+
+func TestArgs(t *testing.T) {
+ fun := Fun("entry",
+ Bloc("entry",
+ Valu("a", OpConst, TypeInt64, 14),
+ Valu("b", OpConst, TypeInt64, 26),
+ Valu("sum", OpAdd, TypeInt64, nil, "a", "b"),
+ Valu("mem", OpArg, TypeMem, ".mem"),
+ Goto("exit")),
+ Bloc("exit",
+ Exit("mem")))
+ sum := fun.values["sum"]
+ for i, name := range []string{"a", "b"} {
+ if sum.Args[i] != fun.values[name] {
+ t.Errorf("arg %d for sum is incorrect: want %s, got %s",
+ i, sum.Args[i], fun.values[name])
+ }
+ }
+}
+
+func TestEquiv(t *testing.T) {
+ equivalentCases := []struct{ f, g fun }{
+ // simple case
+ {
+ Fun("entry",
+ Bloc("entry",
+ Valu("a", OpConst, TypeInt64, 14),
+ Valu("b", OpConst, TypeInt64, 26),
+ Valu("sum", OpAdd, TypeInt64, nil, "a", "b"),
+ Valu("mem", OpArg, TypeMem, ".mem"),
+ Goto("exit")),
+ Bloc("exit",
+ Exit("mem"))),
+ Fun("entry",
+ Bloc("entry",
+ Valu("a", OpConst, TypeInt64, 14),
+ Valu("b", OpConst, TypeInt64, 26),
+ Valu("sum", OpAdd, TypeInt64, nil, "a", "b"),
+ Valu("mem", OpArg, TypeMem, ".mem"),
+ Goto("exit")),
+ Bloc("exit",
+ Exit("mem"))),
+ },
+ // block order changed
+ {
+ Fun("entry",
+ Bloc("entry",
+ Valu("a", OpConst, TypeInt64, 14),
+ Valu("b", OpConst, TypeInt64, 26),
+ Valu("sum", OpAdd, TypeInt64, nil, "a", "b"),
+ Valu("mem", OpArg, TypeMem, ".mem"),
+ Goto("exit")),
+ Bloc("exit",
+ Exit("mem"))),
+ Fun("entry",
+ Bloc("exit",
+ Exit("mem")),
+ Bloc("entry",
+ Valu("a", OpConst, TypeInt64, 14),
+ Valu("b", OpConst, TypeInt64, 26),
+ Valu("sum", OpAdd, TypeInt64, nil, "a", "b"),
+ Valu("mem", OpArg, TypeMem, ".mem"),
+ Goto("exit"))),
+ },
+ }
+ for _, c := range equivalentCases {
+ if !Equiv(c.f.f, c.g.f) {
+ t.Errorf("expected equivalence. Func definitions:")
+ // TODO(matloob): Rewrite PrintFunc to output to a string or writer,
+ // so the functions can be written to the error log.
+ PrintFunc(c.f.f)
+ PrintFunc(c.g.f)
+ }
+ }
+
+ differentCases := []struct{ f, g fun }{
+ // different shape
+ {
+ Fun("entry",
+ Bloc("entry",
+ Valu("mem", OpArg, TypeMem, ".mem"),
+ Goto("exit")),
+ Bloc("exit",
+ Exit("mem"))),
+ Fun("entry",
+ Bloc("entry",
+ Valu("mem", OpArg, TypeMem, ".mem"),
+ Exit("mem"))),
+ },
+ // value order changed
+ {
+ Fun("entry",
+ Bloc("entry",
+ Valu("mem", OpArg, TypeMem, ".mem"),
+ Valu("b", OpConst, TypeInt64, 26),
+ Valu("a", OpConst, TypeInt64, 14),
+ Exit("mem"))),
+ Fun("entry",
+ Bloc("entry",
+ Valu("mem", OpArg, TypeMem, ".mem"),
+ Valu("a", OpConst, TypeInt64, 14),
+ Valu("b", OpConst, TypeInt64, 26),
+ Exit("mem"))),
+ },
+ // value aux different
+ {
+ Fun("entry",
+ Bloc("entry",
+ Valu("mem", OpArg, TypeMem, ".mem"),
+ Valu("a", OpConst, TypeInt64, 14),
+ Exit("mem"))),
+ Fun("entry",
+ Bloc("entry",
+ Valu("mem", OpArg, TypeMem, ".mem"),
+ Valu("a", OpConst, TypeInt64, 26),
+ Exit("mem"))),
+ },
+ // value args different
+ {
+ Fun("entry",
+ Bloc("entry",
+ Valu("mem", OpArg, TypeMem, ".mem"),
+ Valu("a", OpConst, TypeInt64, 14),
+ Valu("b", OpConst, TypeInt64, 26),
+ Valu("sum", OpAdd, TypeInt64, nil, "a", "b"),
+ Exit("mem"))),
+ Fun("entry",
+ Bloc("entry",
+ Valu("mem", OpArg, TypeMem, ".mem"),
+ Valu("a", OpConst, TypeInt64, 0),
+ Valu("b", OpConst, TypeInt64, 14),
+ Valu("sum", OpAdd, TypeInt64, nil, "b", "a"),
+ Exit("mem"))),
+ },
+ }
+ for _, c := range differentCases {
+ if Equiv(c.f.f, c.g.f) {
+ t.Errorf("expected difference. Func definitions:")
+ // TODO(matloob): Rewrite PrintFunc to output to a string or writer,
+ // so the functions can be written to the error log.
+ PrintFunc(c.f.f)
+ PrintFunc(c.g.f)
+ }
+ }
+}