go-vise

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

commit ee55f3e5fe7e37a18bef715e962495dd0d69884d
parent 6dbc0041385cb5e4800833477999ea2da5689923
Author: lash <dev@holbrook.no>
Date:   Fri, 31 Mar 2023 15:03:54 +0100

Add run loop

Diffstat:
Mgo/state/state.go | 37++++++++++++++++++++++++++++++++-----
Mgo/state/state_test.go | 38+++++++++++++++++++-------------------
Mgo/vm/vm.go | 154+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mgo/vm/vm_test.go | 15+++++++++------
4 files changed, 148 insertions(+), 96 deletions(-)

diff --git a/go/state/state.go b/go/state/state.go @@ -12,6 +12,7 @@ type State struct { Cache []map[string]string CacheMap map[string]string ExecPath []string + Idx uint16 } func NewState(bitSize uint64) State { @@ -35,13 +36,13 @@ func(st State) WithCacheSize(cacheSize uint32) State { return st } -func(st *State) Enter(input string) { +func(st *State) Down(input string) { m := make(map[string]string) st.Cache = append(st.Cache, m) st.CacheMap = make(map[string]string) } -func(st *State) Add(key string, value string) error { +func(st *State) Add(key string, value string, sizeHint uint32) error { checkFrame := st.frameOf(key) if checkFrame > -1 { return fmt.Errorf("key %v already defined in frame %v", key, checkFrame) @@ -53,6 +54,26 @@ func(st *State) Add(key string, value string) error { log.Printf("add key %s value size %v", key, sz) st.Cache[len(st.Cache)-1][key] = value st.CacheUseSize += sz + _ = sizeHint + return nil +} + +func(st *State) Update(key string, value string) error { + checkFrame := st.frameOf(key) + if checkFrame == -1 { + return fmt.Errorf("key %v not defined", key) + } + r := st.Cache[checkFrame][key] + l := uint32(len(r)) + st.Cache[checkFrame][key] = "" + st.CacheUseSize -= l + sz := st.checkCapacity(value) + if sz == 0 { + baseUseSize := st.CacheUseSize + st.Cache[checkFrame][key] = r + st.CacheUseSize += l + return fmt.Errorf("Cache capacity exceeded %v of %v", baseUseSize + sz, st.CacheSize) + } return nil } @@ -76,7 +97,7 @@ func(st *State) Get() (map[string]string, error) { return st.Cache[len(st.Cache)-1], nil } -func(st *State) Exit() error { +func(st *State) Up() error { l := len(st.Cache) if l == 0 { return fmt.Errorf("exit called beyond top frame") @@ -92,16 +113,20 @@ func(st *State) Exit() error { return nil } -func(st *State) Reset() error { +func(st *State) Reset() { + if len(st.Cache) == 0 { + return + } st.Cache = st.Cache[:1] st.CacheUseSize = 0 - return nil + return } func(st *State) Check(key string) bool { return st.frameOf(key) == -1 } +// return 0-indexed frame number where key is defined. -1 if not defined func(st *State) frameOf(key string) int { log.Printf("--- %s", key) for i, m := range st.Cache { @@ -114,6 +139,8 @@ func(st *State) frameOf(key string) int { return -1 } +// bytes that will be added to cache use size for string +// returns 0 if capacity would be exceeded func(st *State) checkCapacity(v string) uint32 { sz := uint32(len(v)) if st.CacheSize == 0 { diff --git a/go/state/state_test.go b/go/state/state_test.go @@ -35,40 +35,40 @@ func TestNewStateCache(t *testing.T) { func TestStateCacheUse(t *testing.T) { st := NewState(17) st = st.WithCacheSize(10) - st.Enter("foo") - err := st.Add("bar", "baz") + st.Down("foo") + err := st.Add("bar", "baz", 0) if err != nil { t.Error(err) } - err = st.Add("inky", "pinky") + err = st.Add("inky", "pinky", 0) if err != nil { t.Error(err) } - err = st.Add("blinky", "clyde") + err = st.Add("blinky", "clyde", 0) if err == nil { t.Errorf("expected capacity error") } } -func TestStateEnterExit(t *testing.T) { +func TestStateDownUp(t *testing.T) { st := NewState(17) - st.Enter("one") - err := st.Add("foo", "bar") + st.Down("one") + err := st.Add("foo", "bar", 0) if err != nil { t.Error(err) } - err = st.Add("baz", "xyzzy") + err = st.Add("baz", "xyzzy", 0) if err != nil { t.Error(err) } if st.CacheUseSize != 8 { t.Errorf("expected cache use size 8 got %v", st.CacheUseSize) } - err = st.Exit() + err = st.Up() if err != nil { t.Error(err) } - err = st.Exit() + err = st.Up() if err == nil { t.Errorf("expected out of top frame error") } @@ -76,17 +76,17 @@ func TestStateEnterExit(t *testing.T) { func TestStateReset(t *testing.T) { st := NewState(17) - st.Enter("one") - err := st.Add("foo", "bar") + st.Down("one") + err := st.Add("foo", "bar", 0) if err != nil { t.Error(err) } - err = st.Add("baz", "xyzzy") + err = st.Add("baz", "xyzzy", 0) if err != nil { t.Error(err) } - st.Enter("two") - st.Enter("three") + st.Down("two") + st.Down("three") st.Reset() if st.CacheUseSize != 0 { t.Errorf("expected cache use size 0, got %v", st.CacheUseSize) @@ -98,13 +98,13 @@ func TestStateReset(t *testing.T) { func TestStateLoadDup(t *testing.T) { st := NewState(17) - st.Enter("one") - err := st.Add("foo", "bar") + st.Down("one") + err := st.Add("foo", "bar", 0) if err != nil { t.Error(err) } - st.Enter("two") - err = st.Add("foo", "baz") + st.Down("two") + err = st.Add("foo", "baz", 0) if err == nil { t.Errorf("expected fail on duplicate load") } diff --git a/go/vm/vm.go b/go/vm/vm.go @@ -9,108 +9,130 @@ import ( "git.defalsify.org/festive/resource" ) -type Runner func(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, error) +type Runner func(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) -func Run(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, error) { - op := binary.BigEndian.Uint16(instruction[:2]) - if op > _MAX { - return st, fmt.Errorf("opcode value %v out of range (%v)", op, _MAX) +func Run(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) { + var err error + for len(instruction) > 0 { + op := binary.BigEndian.Uint16(instruction[:2]) + if op > _MAX { + return st, instruction, fmt.Errorf("opcode value %v out of range (%v)", op, _MAX) + } + switch op { + case CATCH: + st, instruction, err = RunCatch(instruction[2:], st, rs, ctx) + case CROAK: + st, instruction, err = RunCroak(instruction[2:], st, rs, ctx) + case LOAD: + st, instruction, err = RunLoad(instruction[2:], st, rs, ctx) + case RELOAD: + st, instruction, err = RunReload(instruction[2:], st, rs, ctx) + case MAP: + st, instruction, err = RunMap(instruction[2:], st, rs, ctx) + case SINK: + st, instruction, err = RunSink(instruction[2:], st, rs, ctx) + default: + err = fmt.Errorf("Unhandled state: %v", op) + } + if err != nil { + return st, instruction, err + } } - switch op { - case CATCH: - RunCatch(instruction[2:], st, rs, ctx) - case CROAK: - RunCroak(instruction[2:], st, rs, ctx) - case LOAD: - RunLoad(instruction[2:], st, rs, ctx) - case RELOAD: - RunReload(instruction[2:], st, rs, ctx) - case MAP: - RunMap(instruction[2:], st, rs, ctx) - case SINK: - RunSink(instruction[2:], st, rs, ctx) - default: - err := fmt.Errorf("Unhandled state: %v", op) - return st, err - } - return st, nil + return st, instruction, nil } -func instructionSplit(b []byte) (string, []byte, error) { - if len(b) == 0 { - return "", nil, fmt.Errorf("argument is empty") - } - sz := uint8(b[0]) - if sz == 0 { - return "", nil, fmt.Errorf("zero-length argument") - } - tailSz := uint8(len(b)) - if tailSz - 1 < sz { - return "", nil, fmt.Errorf("corrupt instruction, len %v less than symbol length: %v", tailSz, sz) - } - r := string(b[1:1+sz]) - return r, b[1+sz:], nil -} - -func RunMap(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, error) { +func RunMap(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) { head, tail, err := instructionSplit(instruction) if err != nil { - return st, err + return st, instruction, err } - _ = tail st.Map(head) - return st, nil + return st, tail, nil } -func RunSink(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, error) { - return st, nil +func RunSink(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) { + return st, nil, nil } -func RunCatch(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, error) { +func RunCatch(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) { head, tail, err := instructionSplit(instruction) if err != nil { - return st, err + return st, instruction, err } r, err := rs.Get(head) if err != nil { - return st, err + return st, instruction, err } - _ = tail - st.Add(head, r) - return st, nil + st.Add(head, r, uint32(len(r))) + return st, tail, nil } -func RunCroak(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, error) { +func RunCroak(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) { head, tail, err := instructionSplit(instruction) if err != nil { - return st, err + return st, instruction, err } _ = head - _ = tail st.Reset() - return st, nil + return st, tail, nil } -func RunLoad(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, error) { +func RunLoad(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) { head, tail, err := instructionSplit(instruction) if err != nil { - return st, err + return st, instruction, err } if !st.Check(head) { - return st, fmt.Errorf("key %v already loaded", head) + return st, instruction, fmt.Errorf("key %v already loaded", head) } - fn, err := rs.FuncFor(head) + sz := uint32(tail[0]) + tail = tail[1:] + + r, err := refresh(head, tail, rs, ctx) if err != nil { - return st, err + return st, tail, err } - r, err := fn(tail, ctx) + st.Add(head, r, sz) + return st, tail, nil +} + +func RunReload(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) { + head, tail, err := instructionSplit(instruction) if err != nil { - return st, err + return st, instruction, err } - st.Add(head, r) - return st, nil + r, err := refresh(head, tail, rs, ctx) + if err != nil { + return st, tail, err + } + st.Add(head, r, uint32(len(r))) + return st, tail, nil +} + +func RunMove(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, []byte, error) { + return st, nil, nil +} + +func refresh(key string, sym []byte, rs resource.Fetcher, ctx context.Context) (string, error) { + fn, err := rs.FuncFor(key) + if err != nil { + return "", err + } + return fn(sym, ctx) } -func RunReload(instruction []byte, st state.State, rs resource.Fetcher, ctx context.Context) (state.State, error) { - return st, nil +func instructionSplit(b []byte) (string, []byte, error) { + if len(b) == 0 { + return "", nil, fmt.Errorf("argument is empty") + } + sz := uint8(b[0]) + if sz == 0 { + return "", nil, fmt.Errorf("zero-length argument") + } + tailSz := uint8(len(b)) + if tailSz < sz { + return "", nil, fmt.Errorf("corrupt instruction, len %v less than symbol length: %v", tailSz, sz) + } + r := string(b[1:1+sz]) + return r, b[1+sz:], nil } diff --git a/go/vm/vm_test.go b/go/vm/vm_test.go @@ -63,14 +63,15 @@ func (r *TestResource) FuncFor(sym string) (resource.EntryFunc, error) { func TestRun(t *testing.T) { st := state.NewState(5) rs := TestResource{} - b := []byte{0x00, 0x01} - r, err := Run(b, st, &rs, context.TODO()) + b := []byte{0x00, 0x01, 0x03} + b = append(b, []byte("foo")...) + r, _, err := Run(b, st, &rs, context.TODO()) if err != nil { t.Errorf("error on valid opcode: %v", err) } b = []byte{0x01, 0x02} - r, err = Run(b, st, &rs, context.TODO()) + r, _, err = Run(b, st, &rs, context.TODO()) if err == nil { t.Errorf("no error on invalid opcode") } @@ -79,12 +80,13 @@ func TestRun(t *testing.T) { func TestRunLoad(t *testing.T) { st := state.NewState(5) - st.Enter("barbarbar") + st.Down("barbarbar") rs := TestResource{} sym := "one" ins := append([]byte{uint8(len(sym))}, []byte(sym)...) + ins = append(ins, 0x0a) var err error - st, err = RunLoad(ins, st, &rs, context.TODO()) + st, _, err = RunLoad(ins, st, &rs, context.TODO()) if err != nil { t.Error(err) } @@ -108,7 +110,8 @@ func TestRunLoad(t *testing.T) { sym = "two" ins = append([]byte{uint8(len(sym))}, []byte(sym)...) - st, err = RunLoad(ins, st, &rs, context.TODO()) + ins = append(ins, 0) + st, _, err = RunLoad(ins, st, &rs, context.TODO()) if err != nil { t.Error(err) }