summaryrefslogtreecommitdiff
path: root/vm/vm.go
diff options
context:
space:
mode:
authorMarc Vertes <mvertes@free.fr>2025-11-14 13:47:51 +0100
committerMarc Vertes <mvertes@free.fr>2025-11-14 13:47:51 +0100
commit1a9c22d1219eb0239417db16c9fe96eea3ced2fe (patch)
treea4fa82cfbbc924d281a94e307656a7af14caadcd /vm/vm.go
parenta4d7730581e8e65faa6a9285d107a041accf60b1 (diff)
fix: refactor VM instruction type
Use custom types for VM instructions. More idiomatic code for tracing.
Diffstat (limited to 'vm/vm.go')
-rw-r--r--vm/vm.go243
1 files changed, 92 insertions, 151 deletions
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 := "["