go/src/cmd/compile/internal/ssa/func_test.go
Josh Bleecher Snyder a68e5d94fa cmd/compile: clean up SSA test API
I noted in CL 38327 that the SSA test API felt a bit
clunky after the ssa.Func/ssa.Cache/ssa.Config refactoring,
and promised to clean it up once the dust settled.
The dust has settled.

Along the way, this CL fixes a potential latent bug,
in which the amd64 test context was used for all dummy Syslook calls.
The lone SSA test using the s390x context did not depend on the
Syslook context being correct, so the bug did not arise in practice.

Change-Id: If964251d1807976073ad7f47da0b1f1f77c58413
Reviewed-on: https://go-review.googlesource.com/38346
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
2017-03-19 05:37:39 +00:00

476 lines
12 KiB
Go

// Copyright 2015 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.
// This file contains some utility functions to help define Funcs for testing.
// As an example, the following func
//
// b1:
// v1 = InitMem <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", OpInitMem, TypeMem, 0, nil),
// Goto("exit")),
// Bloc("exit",
// Exit("mem")),
// Bloc("deadblock",
// Valu("deadval", OpConstBool, TypeBool, 0, 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 (
"cmd/internal/src"
"fmt"
"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 || fv.AuxInt != gv.AuxInt {
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].b, gb.Succs[i].b) {
return false
}
}
if len(fb.Preds) != len(gb.Preds) {
return false
}
for i := range fb.Preds {
if !checkBlk(fb.Preds[i].b, gb.Preds[i].b) {
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
}
var emptyPass pass = pass{
name: "empty pass",
}
// 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 (c *Conf) Fun(entry string, blocs ...bloc) fun {
f := NewFunc(c.Frontend())
f.Config = c.config
// TODO: Either mark some SSA tests as t.Parallel,
// or set up a shared Cache and Reset it between tests.
// But not both.
f.Cache = new(Cache)
f.pass = &emptyPass
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.NewValue0IA(src.NoXPos, valu.op, valu.t, valu.auxint, 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 {
f.Fatalf("control value for block %s missing", bloc.name)
}
b.SetControl(cval)
}
// Fill in args.
for _, valu := range bloc.valus {
v := values[valu.name]
for _, arg := range valu.args {
a, ok := values[arg]
if !ok {
b.Fatalf("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 {
b.AddEdgeTo(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 {
panic(fmt.Sprintf("already seen control for block %s", name))
}
b.control = v
seenCtrl = true
case valu:
b.valus = append(b.valus, v)
}
}
if !seenCtrl {
panic(fmt.Sprintf("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, auxint int64, aux interface{}, args ...string) valu {
return valu{name, op, t, auxint, 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{}}
}
// Eq specifies a BlockAMD64EQ.
func Eq(cond, sub, alt string) ctrl {
return ctrl{BlockAMD64EQ, cond, []string{sub, alt}}
}
// 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
auxint int64
aux interface{}
args []string
}
func TestArgs(t *testing.T) {
c := testConfig(t)
fun := c.Fun("entry",
Bloc("entry",
Valu("a", OpConst64, TypeInt64, 14, nil),
Valu("b", OpConst64, TypeInt64, 26, nil),
Valu("sum", OpAdd64, TypeInt64, 0, nil, "a", "b"),
Valu("mem", OpInitMem, TypeMem, 0, nil),
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) {
cfg := testConfig(t)
equivalentCases := []struct{ f, g fun }{
// simple case
{
cfg.Fun("entry",
Bloc("entry",
Valu("a", OpConst64, TypeInt64, 14, nil),
Valu("b", OpConst64, TypeInt64, 26, nil),
Valu("sum", OpAdd64, TypeInt64, 0, nil, "a", "b"),
Valu("mem", OpInitMem, TypeMem, 0, nil),
Goto("exit")),
Bloc("exit",
Exit("mem"))),
cfg.Fun("entry",
Bloc("entry",
Valu("a", OpConst64, TypeInt64, 14, nil),
Valu("b", OpConst64, TypeInt64, 26, nil),
Valu("sum", OpAdd64, TypeInt64, 0, nil, "a", "b"),
Valu("mem", OpInitMem, TypeMem, 0, nil),
Goto("exit")),
Bloc("exit",
Exit("mem"))),
},
// block order changed
{
cfg.Fun("entry",
Bloc("entry",
Valu("a", OpConst64, TypeInt64, 14, nil),
Valu("b", OpConst64, TypeInt64, 26, nil),
Valu("sum", OpAdd64, TypeInt64, 0, nil, "a", "b"),
Valu("mem", OpInitMem, TypeMem, 0, nil),
Goto("exit")),
Bloc("exit",
Exit("mem"))),
cfg.Fun("entry",
Bloc("exit",
Exit("mem")),
Bloc("entry",
Valu("a", OpConst64, TypeInt64, 14, nil),
Valu("b", OpConst64, TypeInt64, 26, nil),
Valu("sum", OpAdd64, TypeInt64, 0, nil, "a", "b"),
Valu("mem", OpInitMem, TypeMem, 0, nil),
Goto("exit"))),
},
}
for _, c := range equivalentCases {
if !Equiv(c.f.f, c.g.f) {
t.Error("expected equivalence. Func definitions:")
t.Error(c.f.f)
t.Error(c.g.f)
}
}
differentCases := []struct{ f, g fun }{
// different shape
{
cfg.Fun("entry",
Bloc("entry",
Valu("mem", OpInitMem, TypeMem, 0, nil),
Goto("exit")),
Bloc("exit",
Exit("mem"))),
cfg.Fun("entry",
Bloc("entry",
Valu("mem", OpInitMem, TypeMem, 0, nil),
Exit("mem"))),
},
// value order changed
{
cfg.Fun("entry",
Bloc("entry",
Valu("mem", OpInitMem, TypeMem, 0, nil),
Valu("b", OpConst64, TypeInt64, 26, nil),
Valu("a", OpConst64, TypeInt64, 14, nil),
Exit("mem"))),
cfg.Fun("entry",
Bloc("entry",
Valu("mem", OpInitMem, TypeMem, 0, nil),
Valu("a", OpConst64, TypeInt64, 14, nil),
Valu("b", OpConst64, TypeInt64, 26, nil),
Exit("mem"))),
},
// value auxint different
{
cfg.Fun("entry",
Bloc("entry",
Valu("mem", OpInitMem, TypeMem, 0, nil),
Valu("a", OpConst64, TypeInt64, 14, nil),
Exit("mem"))),
cfg.Fun("entry",
Bloc("entry",
Valu("mem", OpInitMem, TypeMem, 0, nil),
Valu("a", OpConst64, TypeInt64, 26, nil),
Exit("mem"))),
},
// value aux different
{
cfg.Fun("entry",
Bloc("entry",
Valu("mem", OpInitMem, TypeMem, 0, nil),
Valu("a", OpConst64, TypeInt64, 0, 14),
Exit("mem"))),
cfg.Fun("entry",
Bloc("entry",
Valu("mem", OpInitMem, TypeMem, 0, nil),
Valu("a", OpConst64, TypeInt64, 0, 26),
Exit("mem"))),
},
// value args different
{
cfg.Fun("entry",
Bloc("entry",
Valu("mem", OpInitMem, TypeMem, 0, nil),
Valu("a", OpConst64, TypeInt64, 14, nil),
Valu("b", OpConst64, TypeInt64, 26, nil),
Valu("sum", OpAdd64, TypeInt64, 0, nil, "a", "b"),
Exit("mem"))),
cfg.Fun("entry",
Bloc("entry",
Valu("mem", OpInitMem, TypeMem, 0, nil),
Valu("a", OpConst64, TypeInt64, 0, nil),
Valu("b", OpConst64, TypeInt64, 14, nil),
Valu("sum", OpAdd64, TypeInt64, 0, nil, "b", "a"),
Exit("mem"))),
},
}
for _, c := range differentCases {
if Equiv(c.f.f, c.g.f) {
t.Error("expected difference. Func definitions:")
t.Error(c.f.f)
t.Error(c.g.f)
}
}
}
// TestConstCache ensures that the cache will not return
// reused free'd values with a non-matching AuxInt
func TestConstCache(t *testing.T) {
c := testConfig(t)
f := c.Fun("entry",
Bloc("entry",
Valu("mem", OpInitMem, TypeMem, 0, nil),
Exit("mem")))
v1 := f.f.ConstBool(src.NoXPos, TypeBool, false)
v2 := f.f.ConstBool(src.NoXPos, TypeBool, true)
f.f.freeValue(v1)
f.f.freeValue(v2)
v3 := f.f.ConstBool(src.NoXPos, TypeBool, false)
v4 := f.f.ConstBool(src.NoXPos, TypeBool, true)
if v3.AuxInt != 0 {
t.Errorf("expected %s to have auxint of 0\n", v3.LongString())
}
if v4.AuxInt != 1 {
t.Errorf("expected %s to have auxint of 1\n", v4.LongString())
}
}
// opcodeMap returns a map from opcode to the number of times that opcode
// appears in the function.
func opcodeMap(f *Func) map[Op]int {
m := map[Op]int{}
for _, b := range f.Blocks {
for _, v := range b.Values {
m[v.Op]++
}
}
return m
}
// opcodeCounts checks that the number of opcodes listed in m agree with the
// number of opcodes that appear in the function.
func checkOpcodeCounts(t *testing.T, f *Func, m map[Op]int) {
n := opcodeMap(f)
for op, cnt := range m {
if n[op] != cnt {
t.Errorf("%s appears %d times, want %d times", op, n[op], cnt)
}
}
}