diff --git a/ast.go b/ast.go index 026c4b8..1eeb2f8 100644 --- a/ast.go +++ b/ast.go @@ -47,6 +47,9 @@ func (i Instruction) TargetLabel() *Label { if !i.IsBranch { return nil } + if len(i.Operands) == 0 { + return nil + } if ref, ok := i.Operands[0].(operand.LabelRef); ok { lbl := Label(ref) return &lbl diff --git a/pass/cfg.go b/pass/cfg.go index 120888a..501e09e 100644 --- a/pass/cfg.go +++ b/pass/cfg.go @@ -75,7 +75,9 @@ func CFG(fn *avo.Function) error { // Populate predecessors. for _, i := range is { for _, s := range i.Succ { - s.Pred = append(s.Pred, i) + if s != nil { + s.Pred = append(s.Pred, i) + } } } diff --git a/pass/cfg_test.go b/pass/cfg_test.go index eb0237c..adddc3d 100644 --- a/pass/cfg_test.go +++ b/pass/cfg_test.go @@ -2,8 +2,11 @@ package pass import ( "reflect" + "sort" "testing" + "github.com/mmcloughlin/avo/operand" + "github.com/mmcloughlin/avo" ) @@ -68,10 +71,222 @@ func TestLabelTargetInstructionFollowLabel(t *testing.T) { } } -func TestCFG(t *testing.T) { - // TODO(mbm): jump backward - // TODO(mbm): jump forward - // TODO(mbm): multiple returns - // TODO(mbm): infinite loop - // TODO(mbm): very short infinite loop +func TestCFGSingleBasicBlock(t *testing.T) { + f := avo.NewFunction("simple") + f.AddInstruction(&avo.Instruction{Opcode: "A"}) + f.AddInstruction(&avo.Instruction{Opcode: "B"}) + f.AddInstruction(Terminal("RET")) + + if err := ComputeCFG(t, f); err != nil { + t.Fatal(err) + } + + AssertSuccessors(t, f, map[string][]string{ + "A": {"B"}, + "B": {"RET"}, + "RET": {}, + }) + + AssertPredecessors(t, f, map[string][]string{ + "A": {}, + "B": {"A"}, + "RET": {"B"}, + }) +} + +func TestCFGCondBranch(t *testing.T) { + f := avo.NewFunction("condbranch") + f.AddInstruction(&avo.Instruction{Opcode: "A"}) + f.AddLabel(avo.Label("lblB")) + f.AddInstruction(&avo.Instruction{Opcode: "B"}) + f.AddInstruction(&avo.Instruction{Opcode: "C"}) + f.AddInstruction(CondBranch("J", "lblB")) + f.AddInstruction(Terminal("RET")) + + if err := ComputeCFG(t, f); err != nil { + t.Fatal(err) + } + + AssertSuccessors(t, f, map[string][]string{ + "A": {"B"}, + "B": {"C"}, + "C": {"J"}, + "J": {"B", "RET"}, + "RET": {}, + }) +} + +func TestCFGUncondBranch(t *testing.T) { + f := avo.NewFunction("uncondbranch") + f.AddInstruction(&avo.Instruction{Opcode: "A"}) + f.AddLabel(avo.Label("lblB")) + f.AddInstruction(&avo.Instruction{Opcode: "B"}) + f.AddInstruction(UncondBranch("JMP", "lblB")) + + if err := ComputeCFG(t, f); err != nil { + t.Fatal(err) + } + + AssertSuccessors(t, f, map[string][]string{ + "A": {"B"}, + "B": {"JMP"}, + "JMP": {"B"}, + }) +} + +func TestCFGJumpForward(t *testing.T) { + f := avo.NewFunction("forward") + f.AddInstruction(&avo.Instruction{Opcode: "A"}) + f.AddInstruction(CondBranch("J", "done")) + f.AddInstruction(&avo.Instruction{Opcode: "B"}) + f.AddLabel(avo.Label("done")) + f.AddInstruction(Terminal("RET")) + + if err := ComputeCFG(t, f); err != nil { + t.Fatal(err) + } + + AssertSuccessors(t, f, map[string][]string{ + "A": {"J"}, + "J": {"B", "RET"}, + "B": {"RET"}, + "RET": {}, + }) +} + +func TestCFGMultiReturn(t *testing.T) { + f := avo.NewFunction("multireturn") + f.AddInstruction(&avo.Instruction{Opcode: "A"}) + f.AddInstruction(CondBranch("J", "fork")) + f.AddInstruction(&avo.Instruction{Opcode: "B"}) + f.AddInstruction(Terminal("RET1")) + f.AddLabel(avo.Label("fork")) + f.AddInstruction(&avo.Instruction{Opcode: "C"}) + f.AddInstruction(Terminal("RET2")) + + if err := ComputeCFG(t, f); err != nil { + t.Fatal(err) + } + + AssertSuccessors(t, f, map[string][]string{ + "A": {"J"}, + "J": {"B", "C"}, + "B": {"RET1"}, + "RET1": {}, + "C": {"RET2"}, + "RET2": {}, + }) +} + +func TestCFGShortLoop(t *testing.T) { + f := avo.NewFunction("shortloop") + f.AddLabel(avo.Label("cycle")) + f.AddInstruction(UncondBranch("JMP", "cycle")) + + if err := ComputeCFG(t, f); err != nil { + t.Fatal(err) + } + + AssertSuccessors(t, f, map[string][]string{ + "JMP": {"JMP"}, + }) +} + +func TestCFGUndefinedLabel(t *testing.T) { + f := avo.NewFunction("undeflabel") + f.AddInstruction(&avo.Instruction{Opcode: "A"}) + f.AddInstruction(CondBranch("J", "undef")) + + err := ComputeCFG(t, f) + + if err == nil { + t.Fatal("expect error on undefined label") + } +} + +func TestCFGMissingLabel(t *testing.T) { + f := avo.NewFunction("missinglabel") + f.AddInstruction(&avo.Instruction{Opcode: "A"}) + f.AddInstruction(&avo.Instruction{Opcode: "J", IsBranch: true}) // no label operand + err := ComputeCFG(t, f) + if err == nil { + t.Fatal("expect error on missing label") + } +} + +// Terminal builds a terminal instruction. +func Terminal(opcode string) *avo.Instruction { + return &avo.Instruction{Opcode: opcode, IsTerminal: true} +} + +// CondBranch builds a conditional branch instruction to the given label. +func CondBranch(opcode, lbl string) *avo.Instruction { + return &avo.Instruction{ + Opcode: opcode, + Operands: []operand.Op{operand.LabelRef(lbl)}, + IsBranch: true, + IsConditional: true, + } +} + +// UncondBranch builds an unconditional branch instruction to the given label. +func UncondBranch(opcode, lbl string) *avo.Instruction { + return &avo.Instruction{ + Opcode: opcode, + Operands: []operand.Op{operand.LabelRef(lbl)}, + IsBranch: true, + IsConditional: false, + } +} + +func ComputeCFG(t *testing.T, f *avo.Function) error { + t.Helper() + if err := LabelTarget(f); err != nil { + t.Fatal(err) + } + return CFG(f) +} + +func AssertSuccessors(t *testing.T, f *avo.Function, expect map[string][]string) { + AssertEqual(t, "successors", OpcodeSuccessorGraph(f), expect) +} + +func AssertPredecessors(t *testing.T, f *avo.Function, expect map[string][]string) { + AssertEqual(t, "predecessors", OpcodePredecessorGraph(f), expect) +} + +func AssertEqual(t *testing.T, what string, got, expect interface{}) { + t.Logf("%s=%#v\n", what, got) + if reflect.DeepEqual(expect, got) { + return + } + t.Fatalf("bad %s; expected=%#v", what, expect) +} + +// OpcodeSuccessorGraph builds a map from opcode name to successor opcode names. +func OpcodeSuccessorGraph(f *avo.Function) map[string][]string { + return OpcodeGraph(f, func(i *avo.Instruction) []*avo.Instruction { return i.Succ }) +} + +// OpcodePredecessorGraph builds a map from opcode name to predecessor opcode names. +func OpcodePredecessorGraph(f *avo.Function) map[string][]string { + return OpcodeGraph(f, func(i *avo.Instruction) []*avo.Instruction { return i.Pred }) +} + +// OpcodeGraph builds a map from opcode name to neighboring opcode names. Each list of neighbors is sorted. +func OpcodeGraph(f *avo.Function, neighbors func(*avo.Instruction) []*avo.Instruction) map[string][]string { + g := map[string][]string{} + for _, i := range f.Instructions() { + opcodes := []string{} + for _, n := range neighbors(i) { + opcode := "" + if n != nil { + opcode = n.Opcode + } + opcodes = append(opcodes, opcode) + } + sort.Strings(opcodes) + g[i.Opcode] = opcodes + } + return g }