pass: VerifyMemOperands (#127)
Introduces a pass to verify memory operands. This is added to a new Verify pass, that in future could do much more. Updates #125
This commit is contained in:
committed by
GitHub
parent
ff7a160610
commit
126469f13d
17
pass/pass.go
17
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 {
|
||||
|
||||
32
pass/verify.go
Normal file
32
pass/verify.go
Normal file
@@ -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
|
||||
}
|
||||
58
pass/verify_test.go
Normal file
58
pass/verify_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user