// 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, types.TypeMem, 0, nil),
|
// Goto("exit")),
|
// Bloc("exit",
|
// Exit("mem")),
|
// Bloc("deadblock",
|
// Valu("deadval", OpConstBool, c.config.Types.Bool, 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/compile/internal/types"
|
"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
|
f.cachedLineStarts = newBiasedSparseMap(0, 100)
|
|
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 *types.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 *types.Type
|
auxint int64
|
aux interface{}
|
args []string
|
}
|
|
func TestArgs(t *testing.T) {
|
c := testConfig(t)
|
fun := c.Fun("entry",
|
Bloc("entry",
|
Valu("a", OpConst64, c.config.Types.Int64, 14, nil),
|
Valu("b", OpConst64, c.config.Types.Int64, 26, nil),
|
Valu("sum", OpAdd64, c.config.Types.Int64, 0, nil, "a", "b"),
|
Valu("mem", OpInitMem, types.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, cfg.config.Types.Int64, 14, nil),
|
Valu("b", OpConst64, cfg.config.Types.Int64, 26, nil),
|
Valu("sum", OpAdd64, cfg.config.Types.Int64, 0, nil, "a", "b"),
|
Valu("mem", OpInitMem, types.TypeMem, 0, nil),
|
Goto("exit")),
|
Bloc("exit",
|
Exit("mem"))),
|
cfg.Fun("entry",
|
Bloc("entry",
|
Valu("a", OpConst64, cfg.config.Types.Int64, 14, nil),
|
Valu("b", OpConst64, cfg.config.Types.Int64, 26, nil),
|
Valu("sum", OpAdd64, cfg.config.Types.Int64, 0, nil, "a", "b"),
|
Valu("mem", OpInitMem, types.TypeMem, 0, nil),
|
Goto("exit")),
|
Bloc("exit",
|
Exit("mem"))),
|
},
|
// block order changed
|
{
|
cfg.Fun("entry",
|
Bloc("entry",
|
Valu("a", OpConst64, cfg.config.Types.Int64, 14, nil),
|
Valu("b", OpConst64, cfg.config.Types.Int64, 26, nil),
|
Valu("sum", OpAdd64, cfg.config.Types.Int64, 0, nil, "a", "b"),
|
Valu("mem", OpInitMem, types.TypeMem, 0, nil),
|
Goto("exit")),
|
Bloc("exit",
|
Exit("mem"))),
|
cfg.Fun("entry",
|
Bloc("exit",
|
Exit("mem")),
|
Bloc("entry",
|
Valu("a", OpConst64, cfg.config.Types.Int64, 14, nil),
|
Valu("b", OpConst64, cfg.config.Types.Int64, 26, nil),
|
Valu("sum", OpAdd64, cfg.config.Types.Int64, 0, nil, "a", "b"),
|
Valu("mem", OpInitMem, types.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, types.TypeMem, 0, nil),
|
Goto("exit")),
|
Bloc("exit",
|
Exit("mem"))),
|
cfg.Fun("entry",
|
Bloc("entry",
|
Valu("mem", OpInitMem, types.TypeMem, 0, nil),
|
Exit("mem"))),
|
},
|
// value order changed
|
{
|
cfg.Fun("entry",
|
Bloc("entry",
|
Valu("mem", OpInitMem, types.TypeMem, 0, nil),
|
Valu("b", OpConst64, cfg.config.Types.Int64, 26, nil),
|
Valu("a", OpConst64, cfg.config.Types.Int64, 14, nil),
|
Exit("mem"))),
|
cfg.Fun("entry",
|
Bloc("entry",
|
Valu("mem", OpInitMem, types.TypeMem, 0, nil),
|
Valu("a", OpConst64, cfg.config.Types.Int64, 14, nil),
|
Valu("b", OpConst64, cfg.config.Types.Int64, 26, nil),
|
Exit("mem"))),
|
},
|
// value auxint different
|
{
|
cfg.Fun("entry",
|
Bloc("entry",
|
Valu("mem", OpInitMem, types.TypeMem, 0, nil),
|
Valu("a", OpConst64, cfg.config.Types.Int64, 14, nil),
|
Exit("mem"))),
|
cfg.Fun("entry",
|
Bloc("entry",
|
Valu("mem", OpInitMem, types.TypeMem, 0, nil),
|
Valu("a", OpConst64, cfg.config.Types.Int64, 26, nil),
|
Exit("mem"))),
|
},
|
// value aux different
|
{
|
cfg.Fun("entry",
|
Bloc("entry",
|
Valu("mem", OpInitMem, types.TypeMem, 0, nil),
|
Valu("a", OpConst64, cfg.config.Types.Int64, 0, 14),
|
Exit("mem"))),
|
cfg.Fun("entry",
|
Bloc("entry",
|
Valu("mem", OpInitMem, types.TypeMem, 0, nil),
|
Valu("a", OpConst64, cfg.config.Types.Int64, 0, 26),
|
Exit("mem"))),
|
},
|
// value args different
|
{
|
cfg.Fun("entry",
|
Bloc("entry",
|
Valu("mem", OpInitMem, types.TypeMem, 0, nil),
|
Valu("a", OpConst64, cfg.config.Types.Int64, 14, nil),
|
Valu("b", OpConst64, cfg.config.Types.Int64, 26, nil),
|
Valu("sum", OpAdd64, cfg.config.Types.Int64, 0, nil, "a", "b"),
|
Exit("mem"))),
|
cfg.Fun("entry",
|
Bloc("entry",
|
Valu("mem", OpInitMem, types.TypeMem, 0, nil),
|
Valu("a", OpConst64, cfg.config.Types.Int64, 0, nil),
|
Valu("b", OpConst64, cfg.config.Types.Int64, 14, nil),
|
Valu("sum", OpAdd64, cfg.config.Types.Int64, 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, types.TypeMem, 0, nil),
|
Exit("mem")))
|
v1 := f.f.ConstBool(c.config.Types.Bool, false)
|
v2 := f.f.ConstBool(c.config.Types.Bool, true)
|
f.f.freeValue(v1)
|
f.f.freeValue(v2)
|
v3 := f.f.ConstBool(c.config.Types.Bool, false)
|
v4 := f.f.ConstBool(c.config.Types.Bool, 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)
|
}
|
}
|
}
|