From 1a9c22d1219eb0239417db16c9fe96eea3ced2fe Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Fri, 14 Nov 2025 13:47:51 +0100 Subject: fix: refactor VM instruction type Use custom types for VM instructions. More idiomatic code for tracing. --- vm/vm.go | 243 ++++++++++++++++++++++++--------------------------------------- 1 file changed, 92 insertions(+), 151 deletions(-) (limited to 'vm/vm.go') diff --git a/vm/vm.go b/vm/vm.go index 3cb9358..0aedaef 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -5,126 +5,98 @@ import ( "fmt" // for tracing only "log" // for tracing only "reflect" // for optional CallX only - "strconv" // for tracing only "unsafe" // to allow setting unexported struct fields ) const debug = true +type ( + Op int // Op is a VM opcode (bytecode instruction). + Pos int // Pos is the source code position of instruction +) + +//go:generate stringer -type=Op + // Byte-code instruction set. const ( // Instruction effect on stack: values consumed -- values produced. - Nop = iota // -- - Add // n1 n2 -- sum ; sum = n1+n2 - Addr // a -- &a ; - Assign // val -- ; mem[$1] = val - Fassign // val -- ; mem[$1] = val - Vassign // val dest -- ; dest.Set(val) - Call // f [a1 .. ai] -- [r1 .. rj] ; r1, ... = prog[f](a1, ...) - Calli // f [a1 .. ai] -- [r1 .. rj] ; r1, ... = prog[f](a1, ...) - CallX // f [a1 .. ai] -- [r1 .. rj] ; r1, ... = mem[f](a1, ...) - Deref // x -- *x ; - Dup // addr -- value ; value = mem[addr] - Fdup // addr -- value ; value = mem[addr] - Equal // n1 n2 -- cond ; cond = n1 == n2 - EqualSet // n1 n2 -- n1 cond ; cond = n1 == n2 - Exit // -- ; - Field // s -- f ; f = s.FieldIndex($1, ...) - Greater // n1 n2 -- cond; cond = n1 > n2 - Grow // -- ; sp += $1 - Index // a i -- a[i] ; - Jump // -- ; ip += $1 - JumpTrue // cond -- ; if cond { ip += $1 } - JumpFalse // cond -- ; if cond { ip += $1 } - JumpSetTrue // - JumpSetFalse // - Lower // n1 n2 -- cond ; cond = n1 < n2 - Loweri // n1 -- cond ; cond = n1 < $1 - Mul // n1 n2 -- prod ; prod = n1*n2 - New // -- x; mem[fp+$1] = new mem[$2] - Not // c -- r ; r = !c - Pop // v -- - Push // -- v - Return // [r1 .. ri] -- ; exit frame: sp = fp, fp = pop - Sub // n1 n2 -- diff ; diff = n1 - n2 - Subi // n1 -- diff ; diff = n1 - $1 + Nop Op = iota // -- + Add // n1 n2 -- sum ; sum = n1+n2 + Addr // a -- &a ; + Assign // val -- ; mem[$1] = val + Fassign // val -- ; mem[$1] = val + Vassign // val dest -- ; dest.Set(val) + Call // f [a1 .. ai] -- [r1 .. rj] ; r1, ... = prog[f](a1, ...) + Calli // f [a1 .. ai] -- [r1 .. rj] ; r1, ... = prog[f](a1, ...) + CallX // f [a1 .. ai] -- [r1 .. rj] ; r1, ... = mem[f](a1, ...) + Deref // x -- *x ; + Dup // addr -- value ; value = mem[addr] + Fdup // addr -- value ; value = mem[addr] + Equal // n1 n2 -- cond ; cond = n1 == n2 + EqualSet // n1 n2 -- n1 cond ; cond = n1 == n2 + Exit // -- ; + Field // s -- f ; f = s.FieldIndex($1, ...) + Greater // n1 n2 -- cond; cond = n1 > n2 + Grow // -- ; sp += $1 + Index // a i -- a[i] ; + Jump // -- ; ip += $1 + JumpTrue // cond -- ; if cond { ip += $1 } + JumpFalse // cond -- ; if cond { ip += $1 } + JumpSetTrue // + JumpSetFalse // + Lower // n1 n2 -- cond ; cond = n1 < n2 + Loweri // n1 -- cond ; cond = n1 < $1 + Mul // n1 n2 -- prod ; prod = n1*n2 + New // -- x; mem[fp+$1] = new mem[$2] + Not // c -- r ; r = !c + Pop // v -- + Push // -- v + Return // [r1 .. ri] -- ; exit frame: sp = fp, fp = pop + Sub // n1 n2 -- diff ; diff = n1 - n2 + Subi // n1 -- diff ; diff = n1 - $1 ) -var strop = [...]string{ // for VM tracing. - Nop: "Nop", - Add: "Add", - Addr: "Addr", - Assign: "Assign", - Call: "Call", - Calli: "Calli", - CallX: "CallX", - Deref: "Deref", - Dup: "Dup", - Equal: "Equal", - EqualSet: "EqualSet", - Exit: "Exit", - Fassign: "Fassign", - Fdup: "Fdup", - Field: "Field", - Greater: "Greater", - Grow: "Grow", - Index: "Index", - Jump: "Jump", - JumpTrue: "JumpTrue", - JumpFalse: "JumpFalse", - JumpSetTrue: "JumpSetTrue", - JumpSetFalse: "JumpSetFalse", - Lower: "Lower", - Loweri: "Loweri", - Mul: "Mul", - New: "New", - Not: "Not", - Pop: "Pop", - Push: "Push", - Return: "Return", - Sub: "Sub", - Subi: "Subi", - Vassign: "Vassign", +// Instruction represents a virtual machine bytecode instruction. +type Instruction struct { + Pos // position in source + Op // opcode + Arg []int // arguments +} + +func (i Instruction) String() (s string) { + s = i.Op.String() + for _, a := range i.Arg { + s += fmt.Sprintf(" %v", a) + } + return s } // Code represents the virtual machine byte code. -type Code [][]int64 +type Code []Instruction // Machine represents a virtual machine. type Machine struct { code Code // code to execute mem []Value // memory, as a stack - ip, fp int // instruction and frame pointer + ip, fp int // instruction pointer and frame pointer ic uint64 // instruction counter, incremented at each instruction executed // flags uint // to set options such as restrict CallX, etc... } // Run runs a program. func (m *Machine) Run() (err error) { - code, mem, ip, fp, sp, ic := m.code, m.mem, m.ip, m.fp, 0, m.ic + mem, ip, fp, sp, ic := m.mem, m.ip, m.fp, 0, m.ic defer func() { m.mem, m.ip, m.fp, m.ic = mem, ip, fp, ic }() - trace := func() { - if !debug { - return - } - var op2, op3 string - c := code[ip] - if l := len(c); l > 2 { - op2 = strconv.Itoa(int(c[2])) - if l > 3 { - op3 = strconv.Itoa(int(c[3])) - } - } - log.Printf("ip:%-4d sp:%-4d fp:%-4d op:[%-12s %-4s %-4s] mem:%v\n", ip, sp, fp, strop[c[1]], op2, op3, Vstring(mem)) - } - for { sp = len(mem) // stack pointer - trace() + c := m.code[ip] + if debug { + log.Printf("ip:%-4d sp:%-4d fp:%-4d op:[%-14v] mem:%v\n", ip, sp, fp, c, Vstring(mem)) + } ic++ - switch op := code[ip]; op[1] { + switch c.Op { case Add: mem[sp-2] = ValueOf(int(mem[sp-2].Data.Int() + mem[sp-1].Data.Int())) mem = mem[:sp-1] @@ -134,10 +106,10 @@ func (m *Machine) Run() (err error) { case Addr: mem[sp-1].Data = mem[sp-1].Data.Addr() case Assign: - mem[op[2]].Data.Set(mem[sp-1].Data) + mem[c.Arg[0]].Data.Set(mem[sp-1].Data) mem = mem[:sp-1] case Fassign: - mem[fp+int(op[2])-1].Data.Set(mem[sp-1].Data) + mem[fp+c.Arg[0]-1].Data.Set(mem[sp-1].Data) mem = mem[:sp-1] case Call: nip := int(mem[sp-1].Data.Int()) @@ -148,25 +120,24 @@ func (m *Machine) Run() (err error) { case Calli: mem = append(mem, ValueOf(ip+1), ValueOf(fp)) fp = sp + 2 - ip = int(op[2]) + ip = c.Arg[0] continue case CallX: // Should be made optional. - l := int(op[2]) - in := make([]reflect.Value, l) + in := make([]reflect.Value, c.Arg[0]) for i := range in { in[i] = mem[sp-2-i].Data } f := mem[sp-1].Data - mem = mem[:sp-l-1] + mem = mem[:sp-c.Arg[0]-1] for _, v := range f.Call(in) { mem = append(mem, Value{Data: v}) } case Deref: mem[sp-1].Data = mem[sp-1].Data.Elem() case Dup: - mem = append(mem, mem[int(op[2])]) + mem = append(mem, mem[c.Arg[0]]) case New: - mem[int(op[2])+fp-1] = NewValue(mem[int(op[3])].Type) + mem[c.Arg[0]+fp-1] = NewValue(mem[c.Arg[1]].Type) case Equal: mem[sp-2] = ValueOf(mem[sp-2].Data.Equal(mem[sp-1].Data)) mem = mem[:sp-1] @@ -183,44 +154,44 @@ func (m *Machine) Run() (err error) { case Exit: return err case Fdup: - mem = append(mem, mem[int(op[2])+fp-1]) + mem = append(mem, mem[c.Arg[0]+fp-1]) case Field: - fv := mem[sp-1].Data.FieldByIndex(slint(op[2:])) + fv := mem[sp-1].Data.FieldByIndex(c.Arg) if !fv.CanSet() { // Normally private fields can not bet set via reflect. Override this limitation. fv = reflect.NewAt(fv.Type(), unsafe.Pointer(fv.UnsafeAddr())).Elem() } mem[sp-1].Data = fv case Jump: - ip += int(op[2]) + ip += c.Arg[0] continue case JumpTrue: cond := mem[sp-1].Data.Bool() mem = mem[:sp-1] if cond { - ip += int(op[2]) + ip += c.Arg[0] continue } case JumpFalse: cond := mem[sp-1].Data.Bool() mem = mem[:sp-1] if !cond { - ip += int(op[2]) + ip += c.Arg[0] continue } case JumpSetTrue: cond := mem[sp-1].Data.Bool() if cond { - ip += int(op[2]) - // Note that stack is not modified if cond is true + ip += c.Arg[0] + // Note that the stack is not modified if cond is true. continue } mem = mem[:sp-1] case JumpSetFalse: cond := mem[sp-1].Data.Bool() if !cond { - ip += int(op[2]) - // Note that stack is not modified if cond is false + ip += c.Arg[0] + // Note that the stack is not modified if cond is false. continue } mem = mem[:sp-1] @@ -231,28 +202,27 @@ func (m *Machine) Run() (err error) { mem[sp-2] = ValueOf(mem[sp-1].Data.Int() < mem[sp-2].Data.Int()) mem = mem[:sp-1] case Loweri: - mem[sp-1] = ValueOf(mem[sp-1].Data.Int() < op[2]) + mem[sp-1] = ValueOf(mem[sp-1].Data.Int() < int64(c.Arg[0])) case Not: mem[sp-1] = ValueOf(!mem[sp-1].Data.Bool()) case Pop: - mem = mem[:sp-int(op[2])] + mem = mem[:sp-c.Arg[0]] case Push: - // mem = append(mem, reflect.ValueOf(int(op[2]))) mem = append(mem, NewValue(TypeOf(0))) - mem[sp].Data.SetInt(op[2]) + mem[sp].Data.SetInt(int64(c.Arg[0])) case Grow: - mem = append(mem, make([]Value, op[2])...) + mem = append(mem, make([]Value, c.Arg[0])...) case Return: ip = int(mem[fp-2].Data.Int()) ofp := fp fp = int(mem[fp-1].Data.Int()) - mem = append(mem[:ofp-int(op[2])-int(op[3])-1], mem[sp-int(op[2]):]...) + mem = append(mem[:ofp-c.Arg[0]-c.Arg[1]-1], mem[sp-c.Arg[0]:]...) continue case Sub: mem[sp-2] = ValueOf(int(mem[sp-1].Data.Int() - mem[sp-2].Data.Int())) mem = mem[:sp-1] case Subi: - mem[sp-1] = ValueOf(int(mem[sp-1].Data.Int() - op[2])) + mem[sp-1] = ValueOf(int(mem[sp-1].Data.Int()) - c.Arg[0]) case Index: mem[sp-2].Data = mem[sp-1].Data.Index(int(mem[sp-2].Data.Int())) mem = mem[:sp-1] @@ -265,7 +235,7 @@ func (m *Machine) Run() (err error) { } // PushCode adds instructions to the machine code. -func (m *Machine) PushCode(code ...[]int64) (p int) { +func (m *Machine) PushCode(code ...Instruction) (p int) { p = len(m.code) m.code = append(m.code, code...) return p @@ -282,12 +252,12 @@ func (m *Machine) Push(v ...Value) (l int) { } // Pop removes and returns the value on the top of machine stack. -func (m *Machine) Pop() (v Value) { - l := len(m.mem) - 1 - v = m.mem[l] - m.mem = m.mem[:l] - return v -} +// func (m *Machine) Pop() (v Value) { +// l := len(m.mem) - 1 +// v = m.mem[l] +// m.mem = m.mem[:l] +// return v +// } // Top returns (but not remove) the value on the top of machine stack. func (m *Machine) Top() (v Value) { @@ -299,40 +269,11 @@ func (m *Machine) Top() (v Value) { // PopExit removes the last machine code instruction if is Exit. func (m *Machine) PopExit() { - if l := len(m.code); l > 0 && m.code[l-1][1] == Exit { + if l := len(m.code); l > 0 && m.code[l-1].Op == Exit { m.code = m.code[:l-1] } } -// CodeString returns the string representation of a machine code instruction. -func CodeString(op []int64) string { - switch len(op) { - case 2: - return strop[op[1]] - case 3: - return fmt.Sprintf("%s %d", strop[op[1]], op[2]) - case 4: - return fmt.Sprintf("%s %d %d", strop[op[1]], op[2], op[3]) - } - return "" -} - -// Disassemble returns the code as a readable string. -func Disassemble(code [][]int64) (asm string) { - for _, op := range code { - asm += CodeString(op) + "\n" - } - return asm -} - -func slint(a []int64) []int { - r := make([]int, len(a)) - for i, v := range a { - r[i] = int(v) - } - return r -} - // Vstring returns the string repreentation of a list of values. func Vstring(lv []Value) string { s := "[" -- cgit v1.2.3