diff --git a/pass/pass.go b/pass/pass.go index 4a3abd7..3fd5452 100644 --- a/pass/pass.go +++ b/pass/pass.go @@ -11,6 +11,7 @@ import ( // Compile pass compiles an avo file. Upon successful completion the avo file // may be printed to Go assembly. var Compile = Concat( + Verify, FunctionPass(PruneJumpToFollowingLabel), FunctionPass(PruneDanglingLabels), FunctionPass(LabelTarget), @@ -51,6 +52,22 @@ func (p FunctionPass) Execute(f *ir.File) error { return nil } +// InstructionPass is a convenience for implementing a full file pass with a +// function that operates on each Instruction independently. +type InstructionPass func(*ir.Instruction) error + +// Execute calls p on every instruction in the file. Exits on the first error. +func (p InstructionPass) Execute(f *ir.File) error { + for _, fn := range f.Functions() { + for _, i := range fn.Instructions() { + if err := p(i); err != nil { + return err + } + } + } + return nil +} + // Concat returns a pass that executes the given passes in order, stopping on the first error. func Concat(passes ...Interface) Interface { return Func(func(f *ir.File) error { diff --git a/pass/verify.go b/pass/verify.go new file mode 100644 index 0000000..1e7b368 --- /dev/null +++ b/pass/verify.go @@ -0,0 +1,32 @@ +package pass + +import ( + "errors" + + "github.com/mmcloughlin/avo/ir" + "github.com/mmcloughlin/avo/operand" +) + +// Verify pass validates an avo file. +var Verify = Concat( + InstructionPass(VerifyMemOperands), +) + +// VerifyMemOperands checks the instruction's memory operands. +func VerifyMemOperands(i *ir.Instruction) error { + for _, op := range i.Operands { + m, ok := op.(operand.Mem) + if !ok { + continue + } + + if m.Base == nil { + return errors.New("bad memory operand: missing base register") + } + + if m.Index != nil && m.Scale == 0 { + return errors.New("bad memory operand: index register with scale 0") + } + } + return nil +} diff --git a/pass/verify_test.go b/pass/verify_test.go new file mode 100644 index 0000000..97d4bc2 --- /dev/null +++ b/pass/verify_test.go @@ -0,0 +1,58 @@ +package pass + +import ( + "strings" + "testing" + + "github.com/mmcloughlin/avo/ir" + "github.com/mmcloughlin/avo/operand" + "github.com/mmcloughlin/avo/reg" +) + +func TestVerifyMemOperands(t *testing.T) { + i := &ir.Instruction{ + Operands: []operand.Op{ + reg.RAX, + operand.Mem{ + Base: reg.R10, + Disp: 42, + }, + }, + } + if err := VerifyMemOperands(i); err != nil { + t.Fatal(err) + } +} + +func TestVerifyMemOperandsErrors(t *testing.T) { + cases := []struct { + Operands []operand.Op + ErrorSubstring string + }{ + { + Operands: []operand.Op{ + reg.RAX, + operand.Mem{ + Disp: 42, + }, + }, + ErrorSubstring: "missing base", + }, + { + Operands: []operand.Op{ + operand.Mem{ + Base: reg.EBX, + Index: reg.R9L, + }, + reg.ECX, + }, + ErrorSubstring: "index register with scale 0", + }, + } + for _, c := range cases { + i := &ir.Instruction{Operands: c.Operands} + if err := VerifyMemOperands(i); err == nil || !strings.Contains(err.Error(), c.ErrorSubstring) { + t.Errorf("got error %v; expected error to contain %q", err, c.ErrorSubstring) + } + } +}