go-vise

Constrained Size Output Virtual Machine
Info | Log | Files | Refs | README | LICENSE

commit aec0564ceae11caee70e02393a093489099f7c0f
parent 8bd0c44a615ac3bc42d1d06db1aa0edb6e79f9de
Author: lash <dev@holbrook.no>
Date:   Sun,  2 Apr 2023 13:59:40 +0100

Factor out instruction parse from runner in vm

Diffstat:
Mgo/vm/runner.go | 206+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mgo/vm/runner_test.go | 43++++++++++++++++++++++++-------------------
Mgo/vm/vm.go | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mgo/vm/vm_test.go | 60+++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
4 files changed, 259 insertions(+), 158 deletions(-)

diff --git a/go/vm/runner.go b/go/vm/runner.go @@ -1,7 +1,6 @@ package vm import ( - "encoding/binary" "context" "fmt" "log" @@ -17,176 +16,183 @@ import ( // Each step may update the state. // // On error, the remaining instructions will be returned. State will not be rolled back. -func Run(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { - var err error - for len(instruction) > 0 { - log.Printf("instruction is now 0x%x", instruction) - op := binary.BigEndian.Uint16(instruction[:2]) - if op > _MAX { - return instruction, fmt.Errorf("opcode value %v out of range (%v)", op, _MAX) +func Run(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { + running := true + for running { + log.Printf("code before 0x%x", b) + op, bb, err := opSplit(b) + if err != nil { + return b, err } + b = bb + log.Printf("code after 0x%x", b) switch op { case CATCH: - instruction, err = RunCatch(instruction[2:], st, rs, ctx) + b, err = RunCatch(b, st, rs, ctx) case CROAK: - instruction, err = RunCroak(instruction[2:], st, rs, ctx) + b, err = RunCroak(b, st, rs, ctx) case LOAD: - instruction, err = RunLoad(instruction[2:], st, rs, ctx) + b, err = RunLoad(b, st, rs, ctx) case RELOAD: - instruction, err = RunReload(instruction[2:], st, rs, ctx) + b, err = RunReload(b, st, rs, ctx) case MAP: - instruction, err = RunMap(instruction[2:], st, rs, ctx) + b, err = RunMap(b, st, rs, ctx) case MOVE: - instruction, err = RunMove(instruction[2:], st, rs, ctx) + b, err = RunMove(b, st, rs, ctx) case INCMP: - instruction, err = RunIncmp(instruction[2:], st, rs, ctx) + b, err = RunInCmp(b, st, rs, ctx) + log.Printf("bb %v", b) case HALT: - return RunHalt(instruction[2:], st, rs, ctx) + b, err = RunHalt(b, st, rs, ctx) + return b, err default: err = fmt.Errorf("Unhandled state: %v", op) } if err != nil { - return instruction, err + return b, err + } + log.Printf("aa %v", b) + if len(b) == 0 { + return []byte{}, nil } } - return instruction, nil + return b, nil } // RunMap executes the MAP opcode -func RunMap(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { - head, tail, err := instructionSplit(instruction) - if err != nil { - return instruction, err - } - err = st.Map(head) - return tail, err +func RunMap(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { + sym, b, err := ParseMap(b) + err = st.Map(sym) + return b, err } // RunMap executes the CATCH opcode -func RunCatch(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { - head, tail, err := instructionSplit(instruction) +func RunCatch(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { + sym, sig, mode, b, err := ParseCatch(b) if err != nil { - return instruction, err - } - bitFieldSize := tail[0] - bitField := tail[1:1+bitFieldSize] - tail = tail[1+bitFieldSize:] - matchMode := tail[0] // matchmode 1 is match NOT set bit - tail = tail[1:] - match := false - if matchMode > 0 { - if !st.GetIndex(bitField) { - match = true - } - } else if st.GetIndex(bitField) { - match = true + return b, err } - - if match { - log.Printf("catch at flag %v, moving to %v", bitField, head) - st.Down(head) - tail = []byte{} + r, err := matchFlag(st, sig, mode) + if err != nil { + return b, err + } + if r { + log.Printf("catch at flag %v, moving to %v", sig, sym) //bitField, d) + st.Down(sym) + b = []byte{} } - return tail, nil + return b, nil } // RunMap executes the CROAK opcode -func RunCroak(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { - head, tail, err := instructionSplit(instruction) +func RunCroak(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { + sig, mode, b, err := ParseCroak(b) + if err != nil { + return b, err + } + r, err := matchFlag(st, sig, mode) if err != nil { - return instruction, err + return b, err + } + if r { + log.Printf("croak at flag %v, purging and moving to top", sig) + st.Reset() + b = []byte{} } - _ = head - _ = tail - st.Reset() return []byte{}, nil } // RunLoad executes the LOAD opcode -func RunLoad(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { - head, tail, err := instructionSplit(instruction) +func RunLoad(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { +// head, tail, err := instructionSplit(b) +// if err != nil { +// return b, err +// } +// if !st.Check(head) { +// return b, fmt.Errorf("key %v already loaded", head) +// } +// sz := uint16(tail[0]) +// tail = tail[1:] + sym, sz, b, err := ParseLoad(b) if err != nil { - return instruction, err - } - if !st.Check(head) { - return instruction, fmt.Errorf("key %v already loaded", head) + return b, err } - sz := uint16(tail[0]) - tail = tail[1:] - r, err := refresh(head, rs, ctx) + r, err := refresh(sym, rs, ctx) if err != nil { - return tail, err + return b, err } - err = st.Add(head, r, sz) - return tail, err + err = st.Add(sym, r, uint16(sz)) + return b, err } // RunLoad executes the RELOAD opcode -func RunReload(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { - head, tail, err := instructionSplit(instruction) +func RunReload(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { +// head, tail, err := instructionSplit(b) +// if err != nil { +// return b, err +// } + sym, b, err := ParseReload(b) if err != nil { - return instruction, err + return b, err } - r, err := refresh(head, rs, ctx) + + r, err := refresh(sym, rs, ctx) if err != nil { - return tail, err + return b, err } - st.Update(head, r) - return tail, nil + st.Update(sym, r) + return b, nil } // RunLoad executes the MOVE opcode -func RunMove(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { - head, tail, err := instructionSplit(instruction) +func RunMove(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { + sym, b, err := ParseMove(b) +// head, tail, err := instructionSplit(b) if err != nil { - return instruction, err + return b, err } - st.Down(head) - return tail, nil -} - -// RunLoad executes the BACK opcode -func RunBack(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { - st.Up() - return instruction, nil + st.Down(sym) + return b, nil } // RunIncmp executes the INCMP opcode -func RunIncmp(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { - head, tail, err := instructionSplit(instruction) +func RunInCmp(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { + //head, tail, err := instructionSplit(b) + sym, target, b, err := ParseInCmp(b) if err != nil { - return instruction, err - } - sym, tail, err := instructionSplit(tail) - if err != nil { - return instruction, err + return b, err } v, err := st.GetFlag(state.FLAG_INMATCH) if err != nil { - return tail, err + return b, err } if v { - return tail, nil + return b, nil } input, err := st.GetInput() if err != nil { - return tail, err + return b, err } - log.Printf("checking input %v %v", input, head) - if head == string(input) { + if sym == string(input) { log.Printf("input match for '%s'", input) _, err = st.SetFlag(state.FLAG_INMATCH) - st.Down(sym) + st.Down(target) } - return tail, err + log.Printf("b last %v", b) + return b, err } // RunHalt executes the HALT opcode -func RunHalt(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { +func RunHalt(b []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { + var err error + b, err = ParseHalt(b) + if err != nil { + return b, err + } log.Printf("found HALT, stopping") - _, err := st.ResetFlag(state.FLAG_INMATCH) - return instruction, err + _, err = st.ResetFlag(state.FLAG_INMATCH) + return b, err } diff --git a/go/vm/runner_test.go b/go/vm/runner_test.go @@ -80,11 +80,13 @@ func(r *TestResource) GetCode(sym string) ([]byte, error) { func TestRun(t *testing.T) { st := state.NewState(5) rs := TestResource{} - b := []byte{0x00, MOVE, 0x03} - b = append(b, []byte("foo")...) + + b := NewLine(nil, MOVE, []string{"foo"}, nil, nil) + //b := []byte{0x00, MOVE, 0x03} + //b = append(b, []byte("foo")...) _, err := Run(b, &st, &rs, context.TODO()) if err != nil { - t.Errorf("error on valid opcode: %v", err) + t.Errorf("run error: %v", err) } b = []byte{0x01, 0x02} @@ -98,11 +100,10 @@ func TestRunLoadRender(t *testing.T) { st := state.NewState(5) st.Down("barbarbar") rs := TestResource{} - sym := "one" - ins := append([]byte{uint8(len(sym))}, []byte(sym)...) - ins = append(ins, 0x0a) + var err error - _, err = RunLoad(ins, &st, &rs, context.TODO()) + b := NewLine(nil, LOAD, []string{"one"}, []byte{0x0a}, nil) + b, err = Run(b, &st, &rs, context.TODO()) if err != nil { t.Error(err) } @@ -124,10 +125,13 @@ func TestRunLoadRender(t *testing.T) { t.Errorf("expected error for render of bar: %v" ,err) } - sym = "two" - ins = append([]byte{uint8(len(sym))}, []byte(sym)...) - ins = append(ins, 0) - _, err = RunLoad(ins, &st, &rs, context.TODO()) + b = NewLine(nil, LOAD, []string{"two"}, []byte{0x0a}, nil) + b, err = Run(b, &st, &rs, context.TODO()) + if err != nil { + t.Error(err) + } + b = NewLine(nil, MAP, []string{"one"}, nil, nil) + _, err = Run(b, &st, &rs, context.TODO()) if err != nil { t.Error(err) } @@ -148,20 +152,21 @@ func TestRunLoadRender(t *testing.T) { func TestRunMultiple(t *testing.T) { st := state.NewState(5) rs := TestResource{} - b := []byte{} - b = NewLine(b, LOAD, []string{"one"}, nil, []uint8{0}) - b = NewLine(b, LOAD, []string{"two"}, nil, []uint8{42}) - _, err := Run(b, &st, &rs, context.TODO()) + b := NewLine(nil, LOAD, []string{"one"}, []byte{0x00}, nil) + b = NewLine(b, LOAD, []string{"two"}, []byte{42}, nil) + b, err := Run(b, &st, &rs, context.TODO()) if err != nil { t.Error(err) } + if len(b) > 0 { + t.Errorf("expected empty code") + } } func TestRunReload(t *testing.T) { st := state.NewState(5) rs := TestResource{} - b := []byte{} - b = NewLine(b, LOAD, []string{"dyn"}, nil, []uint8{0}) + b := NewLine(nil, LOAD, []string{"dyn"}, nil, []uint8{0}) b = NewLine(b, MAP, []string{"dyn"}, nil, nil) _, err := Run(b, &st, &rs, context.TODO()) if err != nil { @@ -242,8 +247,8 @@ func TestRunInputHandler(t *testing.T) { bi := NewLine([]byte{}, INCMP, []string{"bar", "aiee"}, nil, nil) bi = NewLine(bi, INCMP, []string{"baz", "foo"}, nil, nil) - bi = NewLine(bi, LOAD, []string{"one"}, nil, []uint8{0}) - bi = NewLine(bi, LOAD, []string{"two"}, nil, []uint8{3}) + bi = NewLine(bi, LOAD, []string{"one"}, []byte{0x00}, nil) + bi = NewLine(bi, LOAD, []string{"two"}, []byte{0x03}, nil) bi = NewLine(bi, MAP, []string{"one"}, nil, nil) bi = NewLine(bi, MAP, []string{"two"}, nil, nil) diff --git a/go/vm/vm.go b/go/vm/vm.go @@ -3,50 +3,55 @@ package vm import ( "encoding/binary" "fmt" + + "git.defalsify.org/festive/state" ) +func Parse(b []byte) (Opcode, []byte, error) { + op, b, err := opSplit(b) + if err != nil { + return NOOP, b, err + } + return op, b, nil +} func ParseLoad(b []byte) (string, uint32, []byte, error) { - return parseSymLen(b, LOAD) + return parseSymLen(b) } func ParseReload(b []byte) (string, []byte, error) { - return parseSym(b, RELOAD) + return parseSym(b) } func ParseMap(b []byte) (string, []byte, error) { - return parseSym(b, MAP) + return parseSym(b) } func ParseMove(b []byte) (string, []byte, error) { - return parseSym(b, MOVE) + return parseSym(b) } func ParseHalt(b []byte) ([]byte, error) { - return parseNoArg(b, HALT) + return parseNoArg(b) } -func ParseCatch(b []byte) (string, uint8, []byte, error) { - return parseSymSig(b, CATCH) +func ParseCatch(b []byte) (string, uint32, bool, []byte, error) { + return parseSymSig(b) } -func ParseCroak(b []byte) (string, uint8, []byte, error) { - return parseSymSig(b, CROAK) +func ParseCroak(b []byte) (uint32, bool, []byte, error) { + return parseSig(b) } func ParseInCmp(b []byte) (string, string, []byte, error) { - return parseTwoSym(b, INCMP) + return parseTwoSym(b) } -func parseNoArg(b []byte, op Opcode) ([]byte, error) { - return opCheck(b, op) +func parseNoArg(b []byte) ([]byte, error) { + return b, nil } -func parseSym(b []byte, op Opcode) (string, []byte, error) { - b, err := opCheck(b, op) - if err != nil { - return "", b, err - } +func parseSym(b []byte) (string, []byte, error) { sym, tail, err := instructionSplit(b) if err != nil { return "", b, err @@ -54,11 +59,7 @@ func parseSym(b []byte, op Opcode) (string, []byte, error) { return sym, tail, nil } -func parseTwoSym(b []byte, op Opcode) (string, string, []byte, error) { - b, err := opCheck(b, op) - if err != nil { - return "", "", b, err - } +func parseTwoSym(b []byte) (string, string, []byte, error) { symOne, tail, err := instructionSplit(b) if err != nil { return "", "", b, err @@ -70,11 +71,7 @@ func parseTwoSym(b []byte, op Opcode) (string, string, []byte, error) { return symOne, symTwo, tail, nil } -func parseSymLen(b []byte, op Opcode) (string, uint32, []byte, error) { - b, err := opCheck(b, op) - if err != nil { - return "", 0, b, err - } +func parseSymLen(b []byte) (string, uint32, []byte, error) { sym, tail, err := instructionSplit(b) if err != nil { return "", 0, b, err @@ -86,21 +83,51 @@ func parseSymLen(b []byte, op Opcode) (string, uint32, []byte, error) { return sym, sz, tail, nil } -func parseSymSig(b []byte, op Opcode) (string, uint8, []byte, error) { - b, err := opCheck(b, op) +func parseSymSig(b []byte) (string, uint32, bool, []byte, error) { + sym, tail, err := instructionSplit(b) if err != nil { - return "", 0, b, err + return "", 0, false, b, err } - sym, tail, err := instructionSplit(b) + sig, tail, err := intSplit(tail) if err != nil { - return "", 0, b, err + return "", 0, false, b, err } if len(tail) == 0 { - return "", 0, b, fmt.Errorf("instruction too short") + return "", 0, false, b, fmt.Errorf("instruction too short") } - n := tail[0] + matchmode := tail[0] > 0 tail = tail[1:] - return sym, n, tail, nil + + return sym, sig, matchmode, tail, nil +} + +func parseSig(b []byte) (uint32, bool, []byte, error) { + sig, b, err := intSplit(b) + if err != nil { + return 0, false, b, err + } + if len(b) == 0 { + return 0, false, b, fmt.Errorf("instruction too short") + } + matchmode := b[0] > 0 + b = b[1:] + + return sig, matchmode, b, nil +} + +func matchFlag(st *state.State, sig uint32, invertMatch bool) (bool, error) { + r, err := st.GetFlag(sig) + if err != nil { + return false, err + } + if invertMatch { + if !r { + return true, nil + } + } else if r { + return true, nil + } + return false, nil } // NewLine creates a new instruction line for the VM. @@ -124,6 +151,13 @@ func NewLine(instructionList []byte, instruction uint16, strargs []string, bytea return append(instructionList, b...) } +func byteSplit(b []byte) ([]byte, []byte, error) { + bitFieldSize := b[0] + bitField := b[1:1+bitFieldSize] + b = b[1+bitFieldSize:] + return bitField, b, nil +} + func intSplit(b []byte) (uint32, []byte, error) { l := uint8(b[0]) sz := uint32(l) @@ -163,10 +197,12 @@ func instructionSplit(b []byte) (string, []byte, error) { } func opCheck(b []byte, opIn Opcode) ([]byte, error) { - op, b, err := opSplit(b) + var bb []byte + op, bb, err := opSplit(b) if err != nil { return b, err } + b = bb if op != opIn { return b, fmt.Errorf("not a %v instruction", op) } diff --git a/go/vm/vm_test.go b/go/vm/vm_test.go @@ -6,14 +6,19 @@ import ( func TestParseNoArg(t *testing.T) { b := NewLine(nil, HALT, nil, nil, nil) + _, b, _ = opSplit(b) b, err := ParseHalt(b) if err != nil { t.Fatal(err) } + if len(b) > 0 { + t.Fatalf("expected empty code") + } } func TestParseSym(t *testing.T) { b := NewLine(nil, MAP, []string{"baz"}, nil, nil) + _, b, _ = opSplit(b) sym, b, err := ParseMap(b) if err != nil { t.Fatal(err) @@ -21,8 +26,12 @@ func TestParseSym(t *testing.T) { if sym != "baz" { t.Fatalf("expected sym baz, got %v", sym) } + if len(b) > 0 { + t.Fatalf("expected empty code") + } b = NewLine(nil, RELOAD, []string{"xyzzy"}, nil, nil) + _, b, _ = opSplit(b) sym, b, err = ParseReload(b) if err != nil { t.Fatal(err) @@ -30,8 +39,12 @@ func TestParseSym(t *testing.T) { if sym != "xyzzy" { t.Fatalf("expected sym xyzzy, got %v", sym) } + if len(b) > 0 { + t.Fatalf("expected empty code") + } b = NewLine(nil, MOVE, []string{"plugh"}, nil, nil) + _, b, _ = opSplit(b) sym, b, err = ParseMove(b) if err != nil { t.Fatal(err) @@ -39,10 +52,14 @@ func TestParseSym(t *testing.T) { if sym != "plugh" { t.Fatalf("expected sym plugh, got %v", sym) } + if len(b) > 0 { + t.Fatalf("expected empty code") + } } func TestParseTwoSym(t *testing.T) { b := NewLine(nil, INCMP, []string{"foo", "bar"}, nil, nil) + _, b, _ = opSplit(b) one, two, b, err := ParseInCmp(b) if err != nil { t.Fatal(err) @@ -53,24 +70,53 @@ func TestParseTwoSym(t *testing.T) { if two != "bar" { t.Fatalf("expected symtwo bar, got %v", two) } + if len(b) > 0 { + t.Fatalf("expected empty code") + } +} + +func TestParseSig(t *testing.T) { + b := NewLine(nil, CROAK, nil, []byte{0x0b, 0x13}, []uint8{0x04}) + _, b, _ = opSplit(b) + n, m, b, err := ParseCroak(b) + if err != nil { + t.Fatal(err) + } + if n != 2835 { + t.Fatalf("expected n 13, got %v", n) + } + if !m { + t.Fatalf("expected m true") + } + if len(b) > 0 { + t.Fatalf("expected empty code") + } } func TestParseSymSig(t *testing.T) { - b := NewLine(nil, CATCH, []string{"baz"}, nil, []uint8{0x0d}) - sym, n, b, err := ParseCatch(b) + b := NewLine(nil, CATCH, []string{"baz"}, []byte{0x0a, 0x13}, []uint8{0x01}) + _, b, _ = opSplit(b) + sym, n, m, b, err := ParseCatch(b) if err != nil { t.Fatal(err) } if sym != "baz" { t.Fatalf("expected sym baz, got %v", sym) } - if n != 13 { + if n != 2579 { t.Fatalf("expected n 13, got %v", n) } + if !m { + t.Fatalf("expected m true") + } + if len(b) > 0 { + t.Fatalf("expected empty code") + } } func TestParseSymAndLen(t *testing.T) { b := NewLine(nil, LOAD, []string{"foo"}, []byte{0x2a}, nil) + _, b, _ = opSplit(b) sym, n, b, err := ParseLoad(b) if err != nil { t.Fatal(err) @@ -83,6 +129,7 @@ func TestParseSymAndLen(t *testing.T) { } b = NewLine(nil, LOAD, []string{"bar"}, []byte{0x02, 0x9a}, nil) + _, b, _ = opSplit(b) sym, n, b, err = ParseLoad(b) if err != nil { t.Fatal(err) @@ -93,8 +140,12 @@ func TestParseSymAndLen(t *testing.T) { if n != 666 { t.Fatalf("expected n 666, got %v", n) } + if len(b) > 0 { + t.Fatalf("expected empty code") + } b = NewLine(nil, LOAD, []string{"baz"}, []byte{0x0}, nil) + _, b, _ = opSplit(b) sym, n, b, err = ParseLoad(b) if err != nil { t.Fatal(err) @@ -105,4 +156,7 @@ func TestParseSymAndLen(t *testing.T) { if n != 0 { t.Fatalf("expected n 666, got %v", n) } + if len(b) > 0 { + t.Fatalf("expected empty code") + } }