core/asm: separate legacy and eof instruction iterators

This commit is contained in:
Martin Holst Swende 2024-10-01 10:57:43 +02:00
parent edec35510e
commit ae12e05ef6
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
4 changed files with 97 additions and 36 deletions

View file

@ -32,6 +32,7 @@ type instructionIterator struct {
op vm.OpCode op vm.OpCode
error error error error
started bool started bool
eofEnabled bool
} }
// NewInstructionIterator creates a new instruction iterator. // NewInstructionIterator creates a new instruction iterator.
@ -41,6 +42,13 @@ func NewInstructionIterator(code []byte) *instructionIterator {
return it return it
} }
// NewEOFInstructionIterator creates a new instruction iterator for EOF-code.
func NewEOFInstructionIterator(code []byte) *instructionIterator {
it := NewInstructionIterator(code)
it.eofEnabled = true
return it
}
// Next returns true if there is a next instruction and moves on. // Next returns true if there is a next instruction and moves on.
func (it *instructionIterator) Next() bool { func (it *instructionIterator) Next() bool {
if it.error != nil || uint64(len(it.code)) <= it.pc { if it.error != nil || uint64(len(it.code)) <= it.pc {
@ -63,10 +71,23 @@ func (it *instructionIterator) Next() bool {
// We reached the end. // We reached the end.
return false return false
} }
it.op = vm.OpCode(it.code[it.pc]) it.op = vm.OpCode(it.code[it.pc])
var a int
if a := vm.Immediates(it.op); a > 0 { if !it.eofEnabled { // Legacy code
if it.op.IsPush() {
a = int(it.op) - int(vm.PUSH0)
}
} else { // EOF code
if it.op == vm.RJUMPV {
// RJUMPV is unique as it has a variable sized operand. The total size is
// determined by the count byte which immediately follows RJUMPV.
maxIndex := int(it.code[it.pc+1])
a = (maxIndex+1)*2 + 1
} else {
a = vm.Immediates(it.op)
}
}
if a > 0 {
u := it.pc + 1 + uint64(a) u := it.pc + 1 + uint64(a)
if uint64(len(it.code)) <= it.pc || uint64(len(it.code)) < u { if uint64(len(it.code)) <= it.pc || uint64(len(it.code)) < u {
it.error = fmt.Errorf("incomplete instruction at %v", it.pc) it.error = fmt.Errorf("incomplete instruction at %v", it.pc)
@ -105,7 +126,6 @@ func PrintDisassembled(code string) error {
if err != nil { if err != nil {
return err return err
} }
it := NewInstructionIterator(script) it := NewInstructionIterator(script)
for it.Next() { for it.Next() {
if it.Arg() != nil && 0 < len(it.Arg()) { if it.Arg() != nil && 0 < len(it.Arg()) {

View file

@ -17,44 +17,78 @@
package asm package asm
import ( import (
"testing"
"encoding/hex" "encoding/hex"
"fmt"
"strings"
"testing"
) )
// Tests disassembling instructions // Tests disassembling instructions
func TestInstructionIterator(t *testing.T) { func TestInstructionIterator(t *testing.T) {
for i, tc := range []struct { for i, tc := range []struct {
want int
code string code string
wantErr string legacyWant string
eofWant string
}{ }{
{2, "61000000", ""}, // valid code {"", "", ""}, // empty
{0, "6100", "incomplete instruction at 0"}, // invalid code {"6100", `err: incomplete instruction at 0`, `err: incomplete instruction at 0`},
{2, "5900", ""}, // push0 {"61000000", `
{0, "", ""}, // empty 00000: PUSH2 0x0000
{2, "d1aabb00", ""}, // DATALOADN(aabb),STOP 00003: STOP`, `
{0, "d1aa", "incomplete instruction at 0"}, // DATALOADN(aa) invalid 00000: PUSH2 0x0000
00003: STOP`},
{"5F00", `
00000: PUSH0
00001: STOP`, `
00000: PUSH0
00001: STOP`},
{"d1aabb00", `00000: DATALOADN
00001: opcode 0xaa not defined
00002: opcode 0xbb not defined
00003: STOP`, `
00000: DATALOADN 0xaabb
00003: STOP`}, // DATALOADN(aabb),STOP
{"d1aa", `
00000: DATALOADN
00001: opcode 0xaa not defined`, "err: incomplete instruction at 0\n"}, // DATALOADN(aa) invalid
{"e20211223344556600", `
00000: RJUMPV
00001: MUL
00002: GT
00003: opcode 0x22 not defined
00004: CALLER
00005: DIFFICULTY
00006: SSTORE
err: incomplete instruction at 7`, `
00000: RJUMPV 0x02112233445566
00008: STOP`}, // RJUMPV( 6 bytes), STOP
} { } {
var ( var (
have int
code, _ = hex.DecodeString(tc.code) code, _ = hex.DecodeString(tc.code)
it = NewInstructionIterator(code) legacy = strings.TrimSpace(disassembly(NewInstructionIterator(code)))
eof = strings.TrimSpace(disassembly(NewEOFInstructionIterator(code)))
) )
for it.Next() { if want := strings.TrimSpace(tc.legacyWant); legacy != want {
have++ t.Errorf("test %d: wrong (legacy) output. have:\n%q\nwant:\n%q\n", i, legacy, want)
} }
var haveErr = "" if want := strings.TrimSpace(tc.eofWant); eof != want {
if it.Error() != nil { t.Errorf("test %d: wrong (eof) output. have:\n%q\nwant:\n%q\n", i, eof, want)
haveErr = it.Error().Error()
}
if haveErr != tc.wantErr {
t.Errorf("test %d: encountered error: %q want %q", i, haveErr, tc.wantErr)
continue
}
if have != tc.want {
t.Errorf("wrong instruction count, have %d want %d", have, tc.want)
} }
} }
} }
func disassembly(it *instructionIterator) string {
var out = new(strings.Builder)
for it.Next() {
if it.Arg() != nil && 0 < len(it.Arg()) {
fmt.Fprintf(out, "%05x: %v %#x\n", it.PC(), it.Op(), it.Arg())
} else {
fmt.Fprintf(out, "%05x: %v\n", it.PC(), it.Op())
}
}
if err := it.Error(); err != nil {
fmt.Fprintf(out, "err: %v\n", err)
}
return out.String()
}

View file

@ -38,7 +38,7 @@ func eofCodeBitmapInternal(code, bits bitvec) bitvec {
if op == RJUMPV { if op == RJUMPV {
// RJUMPV is unique as it has a variable sized operand. // RJUMPV is unique as it has a variable sized operand.
// The total size is determined by the count byte which // The total size is determined by the count byte which
// immediate proceeds RJUMPV. Truncation will be caught // immediate follows RJUMPV. Truncation will be caught
// in other validation steps -- for now, just return a // in other validation steps -- for now, just return a
// valid bitmap for as much of the code as is // valid bitmap for as much of the code as is
// available. // available.

View file

@ -58,6 +58,13 @@ func init() {
// Immediates returns the number bytes of immediates (argument not from // Immediates returns the number bytes of immediates (argument not from
// stack but from code) a given opcode has. // stack but from code) a given opcode has.
// OBS:
// - This function assumes EOF instruction-set. It cannot be upon in
// a. pre-EOF code
// b. post-EOF but legacy code
// - RJUMPV is unique as it has a variable sized operand. The total size is
// determined by the count byte which immediately follows RJUMPV. This method
// will return '3' for RJUMPV, which is the minimum.
func Immediates(op OpCode) int { func Immediates(op OpCode) int {
return int(immediates[op]) return int(immediates[op])
} }