start at some basic passes

This commit is contained in:
Michael McLoughlin
2018-12-02 12:28:33 -08:00
parent 0ceb1c55a4
commit 43575d8b61
14 changed files with 7209 additions and 3904 deletions

83
pass/cfg.go Normal file
View File

@@ -0,0 +1,83 @@
package pass
import (
"errors"
"fmt"
"github.com/mmcloughlin/avo"
)
// LabelTarget populates the LabelTarget of the given function. This maps from
// label name to the following instruction.
func LabelTarget(fn *avo.Function) error {
target := map[avo.Label]*avo.Instruction{}
for idx := 0; idx < len(fn.Nodes); idx++ {
// Is this a label?
lbl, ok := fn.Nodes[idx].(avo.Label)
if !ok {
continue
}
// Check for a duplicate label.
if _, found := target[lbl]; found {
return fmt.Errorf("duplicate label \"%s\"", lbl)
}
// Advance to next node.
if idx == len(fn.Nodes)-1 {
return errors.New("function ends with label")
}
idx++
// Should be an instruction.
i, ok := fn.Nodes[idx].(*avo.Instruction)
if !ok {
return errors.New("instruction should follow a label")
}
target[lbl] = i
}
fn.LabelTarget = target
return nil
}
// CFG constructs the call-flow-graph of each function.
func CFG(fn *avo.Function) error {
is := fn.Instructions()
n := len(is)
// Populate successors.
for i := 0; i < n; i++ {
cur := is[i]
var nxt *avo.Instruction
if i+1 < n {
nxt = is[i+1]
}
// If it's a branch, locate the target.
if cur.IsBranch {
lbl := cur.TargetLabel()
if lbl == nil {
return errors.New("no label for branch instruction")
}
target, found := fn.LabelTarget[*lbl]
if !found {
return errors.New("unknown label")
}
cur.Succ = append(cur.Succ, target)
}
// Otherwise, could continue to the following instruction.
switch {
case cur.IsTerminal:
case cur.IsBranch && !cur.IsConditional:
default:
cur.Succ = append(cur.Succ, nxt)
}
}
// Populate predecessors.
for _, i := range is {
for _, s := range i.Succ {
s.Pred = append(s.Pred, i)
}
}
return nil
}

77
pass/cfg_test.go Normal file
View File

@@ -0,0 +1,77 @@
package pass
import (
"reflect"
"testing"
"github.com/mmcloughlin/avo"
)
func TestLabelTarget(t *testing.T) {
expect := map[avo.Label]*avo.Instruction{
"lblA": &avo.Instruction{Opcode: "A"},
"lblB": &avo.Instruction{Opcode: "B"},
}
f := avo.NewFunction("happypath")
for lbl, i := range expect {
f.AddLabel(lbl)
f.AddInstruction(i)
f.AddInstruction(&avo.Instruction{Opcode: "IDK"})
}
if err := LabelTarget(f); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(expect, f.LabelTarget) {
t.Fatalf("incorrect LabelTarget value\ngot=%#v\nexpext=%#v\n", f.LabelTarget, expect)
}
}
func TestLabelTargetDuplicate(t *testing.T) {
f := avo.NewFunction("dupelabel")
f.AddLabel(avo.Label("lblA"))
f.AddInstruction(&avo.Instruction{Opcode: "A"})
f.AddLabel(avo.Label("lblA"))
f.AddInstruction(&avo.Instruction{Opcode: "A"})
err := LabelTarget(f)
if err == nil || err.Error() != "duplicate label \"lblA\"" {
t.Fatalf("expected error on duplcate label; got %v", err)
}
}
func TestLabelTargetEndsWithLabel(t *testing.T) {
f := avo.NewFunction("endswithlabel")
f.AddInstruction(&avo.Instruction{Opcode: "A"})
f.AddLabel(avo.Label("theend"))
err := LabelTarget(f)
if err == nil || err.Error() != "function ends with label" {
t.Fatalf("expected error when function ends with label; got %v", err)
}
}
func TestLabelTargetInstructionFollowLabel(t *testing.T) {
f := avo.NewFunction("expectinstafterlabel")
f.AddLabel(avo.Label("lblA"))
f.AddLabel(avo.Label("lblB"))
f.AddInstruction(&avo.Instruction{Opcode: "A"})
err := LabelTarget(f)
if err == nil || err.Error() != "instruction should follow a label" {
t.Fatalf("expected error when label is not followed by instruction; got %v", err)
}
}
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
}

17
pass/pass.go Normal file
View File

@@ -0,0 +1,17 @@
package pass
import "github.com/mmcloughlin/avo"
// TODO(mbm): pass types
// FunctionPass builds a full pass that operates on all functions independently.
func FunctionPass(p func(*avo.Function) error) func(*avo.File) error {
return func(f *avo.File) error {
for _, fn := range f.Functions {
if err := p(fn); err != nil {
return err
}
}
return nil
}
}