summaryrefslogtreecommitdiff
path: root/vm
diff options
context:
space:
mode:
authorMarc Vertes <mvertes@free.fr>2024-03-08 19:29:34 +0100
committerMarc Vertes <mvertes@free.fr>2024-03-08 19:29:34 +0100
commit8c4fa9d85cd274439dbd7d0a5c699fe1cea557dc (patch)
treea8c910cf13aa06e61ee2db40b5dd2f1f63a25b9d /vm
parent828cfd1c8da5e243fd0f34530fb1a7faab49784e (diff)
feat: add type representation in vm package
Type and Value types in vm package are now used in place of reflect.Type and reflect.Value. It allows to remove the dependency on reflect for parser and compiler packages. The main purpose of Type is to provide a solution to implement recursive structs, named types, interfaces and methods, despite the limitations of Go reflect. The goal is to provide the thinnest layer around reflect.
Diffstat (limited to 'vm')
-rw-r--r--vm/type.go74
-rw-r--r--vm/vm.go88
-rw-r--r--vm/vm_test.go12
3 files changed, 123 insertions, 51 deletions
diff --git a/vm/type.go b/vm/type.go
new file mode 100644
index 0000000..d29de48
--- /dev/null
+++ b/vm/type.go
@@ -0,0 +1,74 @@
+package vm
+
+import "reflect"
+
+// Runtime type and value representations (based on reflect).
+
+// Type is the representation of a runtime type.
+type Type struct {
+ Name string
+ Rtype reflect.Type
+}
+
+func (t *Type) Elem() *Type {
+ return &Type{Rtype: t.Rtype.Elem()}
+}
+
+func (t *Type) Out(i int) *Type {
+ return &Type{Rtype: t.Rtype.Out(i)}
+}
+
+// Value is the representation of a runtime value.
+type Value struct {
+ Type *Type
+ Data reflect.Value
+}
+
+// NewValue returns an addressable zero value for the specified type.
+func NewValue(typ *Type) Value {
+ return Value{Type: typ, Data: reflect.New(typ.Rtype).Elem()}
+}
+
+// TypeOf returns the runtime type of v.
+func TypeOf(v any) *Type {
+ t := reflect.TypeOf(v)
+ return &Type{Name: t.Name(), Rtype: t}
+}
+
+// ValueOf returns the runtime value of v.
+func ValueOf(v any) Value {
+ return Value{Data: reflect.ValueOf(v)}
+}
+
+func PointerTo(t *Type) *Type {
+ return &Type{Rtype: reflect.PointerTo(t.Rtype)}
+}
+
+func ArrayOf(size int, t *Type) *Type {
+ return &Type{Rtype: reflect.ArrayOf(size, t.Rtype)}
+}
+
+func SliceOf(t *Type) *Type {
+ return &Type{Rtype: reflect.SliceOf(t.Rtype)}
+}
+
+func FuncOf(arg, ret []*Type, variadic bool) *Type {
+ a := make([]reflect.Type, len(arg))
+ for i, e := range arg {
+ a[i] = e.Rtype
+ }
+ r := make([]reflect.Type, len(ret))
+ for i, e := range ret {
+ r[i] = e.Rtype
+ }
+ return &Type{Rtype: reflect.FuncOf(a, r, variadic)}
+}
+
+func StructOf(fields []*Type) *Type {
+ rf := make([]reflect.StructField, len(fields))
+ for i, f := range fields {
+ rf[i].Name = "X" + f.Name
+ rf[i].Type = f.Rtype
+ }
+ return &Type{Rtype: reflect.StructOf(rf)}
+}
diff --git a/vm/vm.go b/vm/vm.go
index f282367..a8c1f28 100644
--- a/vm/vm.go
+++ b/vm/vm.go
@@ -89,10 +89,10 @@ type Code [][]int64
// Machine represents a virtual machine.
type Machine struct {
- code Code // code to execute
- mem []reflect.Value // memory, as a stack
- ip, fp int // instruction and frame pointer
- ic uint64 // instruction counter, incremented at each instruction executed
+ code Code // code to execute
+ mem []Value // memory, as a stack
+ ip, fp int // instruction and frame pointer
+ ic uint64 // instruction counter, incremented at each instruction executed
// flags uint // to set options such as restrict CallX, etc...
}
@@ -123,27 +123,27 @@ func (m *Machine) Run() (err error) {
ic++
switch op := code[ip]; op[1] {
case Add:
- mem[sp-2] = reflect.ValueOf(int(mem[sp-2].Int() + mem[sp-1].Int()))
+ mem[sp-2] = ValueOf(int(mem[sp-2].Data.Int() + mem[sp-1].Data.Int()))
mem = mem[:sp-1]
case Mul:
- mem[sp-2] = reflect.ValueOf(int(mem[sp-2].Int() * mem[sp-1].Int()))
+ mem[sp-2] = ValueOf(int(mem[sp-2].Data.Int() * mem[sp-1].Data.Int()))
mem = mem[:sp-1]
case Addr:
- mem[sp-1] = mem[sp-1].Addr()
+ mem[sp-1].Data = mem[sp-1].Data.Addr()
case Assign:
- mem[op[2]].Set(mem[sp-1])
+ mem[op[2]].Data.Set(mem[sp-1].Data)
mem = mem[:sp-1]
case Fassign:
- mem[fp+int(op[2])-1].Set(mem[sp-1])
+ mem[fp+int(op[2])-1].Data.Set(mem[sp-1].Data)
mem = mem[:sp-1]
case Call:
- nip := int(mem[sp-1].Int())
- mem = append(mem[:sp-1], reflect.ValueOf(ip+1), reflect.ValueOf(fp))
+ nip := int(mem[sp-1].Data.Int())
+ mem = append(mem[:sp-1], ValueOf(ip+1), ValueOf(fp))
ip = nip
fp = sp + 1
continue
case Calli:
- mem = append(mem, reflect.ValueOf(ip+1), reflect.ValueOf(fp))
+ mem = append(mem, ValueOf(ip+1), ValueOf(fp))
fp = sp + 2
ip += int(op[2])
continue
@@ -151,57 +151,57 @@ func (m *Machine) Run() (err error) {
l := int(op[2])
in := make([]reflect.Value, l)
for i := range in {
- in[i] = mem[sp-2-i]
+ in[i] = mem[sp-2-i].Data
}
- f := mem[sp-1]
+ f := mem[sp-1].Data
mem = mem[:sp-l-1]
for _, v := range f.Call(in) {
- mem = append(mem, v)
+ mem = append(mem, Value{Data: v})
}
case Deref:
- mem[sp-1] = mem[sp-1].Elem()
+ mem[sp-1].Data = mem[sp-1].Data.Elem()
case Dup:
mem = append(mem, mem[int(op[2])])
case New:
- mem[int(op[2])+fp-1] = reflect.New(mem[int(op[3])].Type()).Elem()
+ mem[int(op[2])+fp-1] = NewValue(mem[int(op[3])].Type)
case Equal:
- mem[sp-2] = reflect.ValueOf(mem[sp-2].Equal(mem[sp-1]))
+ mem[sp-2] = ValueOf(mem[sp-2].Data.Equal(mem[sp-1].Data))
mem = mem[:sp-1]
case EqualSet:
- if mem[sp-2].Equal(mem[sp-1]) {
+ if mem[sp-2].Data.Equal(mem[sp-1].Data) {
// If equal then lhs and rhs are popped, replaced by test result, as in Equal.
- mem[sp-2] = reflect.ValueOf(true)
+ mem[sp-2] = ValueOf(true)
mem = mem[:sp-1]
} else {
// If not equal then the lhs is let on stack for further processing.
// This is used to simplify bytecode in case clauses of switch statments.
- mem[sp-1] = reflect.ValueOf(false)
+ mem[sp-1] = ValueOf(false)
}
case Exit:
return err
case Fdup:
mem = append(mem, mem[int(op[2])+fp-1])
case Field:
- mem[sp-1] = mem[sp-1].FieldByIndex(slint(op[2:]))
+ mem[sp-1].Data = mem[sp-1].Data.FieldByIndex(slint(op[2:]))
case Jump:
ip += int(op[2])
continue
case JumpTrue:
- cond := mem[sp-1].Bool()
+ cond := mem[sp-1].Data.Bool()
mem = mem[:sp-1]
if cond {
ip += int(op[2])
continue
}
case JumpFalse:
- cond := mem[sp-1].Bool()
+ cond := mem[sp-1].Data.Bool()
mem = mem[:sp-1]
if !cond {
ip += int(op[2])
continue
}
case JumpSetTrue:
- cond := mem[sp-1].Bool()
+ cond := mem[sp-1].Data.Bool()
if cond {
ip += int(op[2])
// Note that stack is not modified if cond is true
@@ -209,7 +209,7 @@ func (m *Machine) Run() (err error) {
}
mem = mem[:sp-1]
case JumpSetFalse:
- cond := mem[sp-1].Bool()
+ cond := mem[sp-1].Data.Bool()
if !cond {
ip += int(op[2])
// Note that stack is not modified if cond is false
@@ -217,39 +217,39 @@ func (m *Machine) Run() (err error) {
}
mem = mem[:sp-1]
case Greater:
- mem[sp-2] = reflect.ValueOf(mem[sp-1].Int() > mem[sp-2].Int())
+ mem[sp-2] = ValueOf(mem[sp-1].Data.Int() > mem[sp-2].Data.Int())
mem = mem[:sp-1]
case Lower:
- mem[sp-2] = reflect.ValueOf(mem[sp-1].Int() < mem[sp-2].Int())
+ mem[sp-2] = ValueOf(mem[sp-1].Data.Int() < mem[sp-2].Data.Int())
mem = mem[:sp-1]
case Loweri:
- mem[sp-1] = reflect.ValueOf(mem[sp-1].Int() < op[2])
+ mem[sp-1] = ValueOf(mem[sp-1].Data.Int() < op[2])
case Not:
- mem[sp-1] = reflect.ValueOf(!mem[sp-1].Bool())
+ mem[sp-1] = ValueOf(!mem[sp-1].Data.Bool())
case Pop:
mem = mem[:sp-int(op[2])]
case Push:
//mem = append(mem, reflect.ValueOf(int(op[2])))
- mem = append(mem, reflect.New(reflect.TypeOf(0)).Elem())
- mem[sp].SetInt(op[2])
+ mem = append(mem, NewValue(TypeOf(0)))
+ mem[sp].Data.SetInt(op[2])
case Grow:
- mem = append(mem, make([]reflect.Value, op[2])...)
+ mem = append(mem, make([]Value, op[2])...)
case Return:
- ip = int(mem[fp-2].Int())
+ ip = int(mem[fp-2].Data.Int())
ofp := fp
- fp = int(mem[fp-1].Int())
+ fp = int(mem[fp-1].Data.Int())
mem = append(mem[:ofp-int(op[2])-int(op[3])-1], mem[sp-int(op[2]):]...)
continue
case Sub:
- mem[sp-2] = reflect.ValueOf(int(mem[sp-1].Int() - mem[sp-2].Int()))
+ mem[sp-2] = ValueOf(int(mem[sp-1].Data.Int() - mem[sp-2].Data.Int()))
mem = mem[:sp-1]
case Subi:
- mem[sp-1] = reflect.ValueOf(int(mem[sp-1].Int() - op[2]))
+ mem[sp-1] = ValueOf(int(mem[sp-1].Data.Int() - op[2]))
case Index:
- mem[sp-2] = mem[sp-1].Index(int(mem[sp-2].Int()))
+ mem[sp-2].Data = mem[sp-1].Data.Index(int(mem[sp-2].Data.Int()))
mem = mem[:sp-1]
case Vassign:
- mem[sp-1].Set(mem[sp-2])
+ mem[sp-1].Data.Set(mem[sp-2].Data)
mem = mem[:sp-2]
}
ip++
@@ -263,18 +263,18 @@ func (m *Machine) PushCode(code ...[]int64) (p int) {
}
func (m *Machine) SetIP(ip int) { m.ip = ip }
-func (m *Machine) Push(v ...reflect.Value) (l int) {
+func (m *Machine) Push(v ...Value) (l int) {
l = len(m.mem)
m.mem = append(m.mem, v...)
return l
}
-func (m *Machine) Pop() (v reflect.Value) {
+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) Top() (v reflect.Value) {
+func (m *Machine) Top() (v Value) {
if l := len(m.mem); l > 0 {
v = m.mem[l-1]
}
@@ -315,13 +315,13 @@ func slint(a []int64) []int {
return r
}
-func Vstring(lv []reflect.Value) string {
+func Vstring(lv []Value) string {
s := "["
for _, v := range lv {
if s != "[" {
s += " "
}
- s += fmt.Sprintf("%v", v)
+ s += fmt.Sprintf("%v", v.Data)
}
return s + "]"
}
diff --git a/vm/vm_test.go b/vm/vm_test.go
index 07c063e..fb71176 100644
--- a/vm/vm_test.go
+++ b/vm/vm_test.go
@@ -3,7 +3,6 @@ package vm
import (
"fmt"
"log"
- "reflect"
"testing"
)
@@ -50,11 +49,10 @@ func BenchmarkVM(b *testing.B) {
}
var tests = []struct {
- //sym []any // initial memory values
- sym []reflect.Value // initial memory values
- code [][]int64 // bytecode to execute
- start, end int //
- mem string // expected memory content
+ sym []Value // initial memory values
+ code [][]int64 // bytecode to execute
+ start, end int //
+ mem string // expected memory content
}{{ // #00 -- A simple addition.
code: [][]int64{
{0, Push, 1},
@@ -64,7 +62,7 @@ var tests = []struct {
},
start: 0, end: 1, mem: "[3]",
}, { // #01 -- Calling a function defined outside the VM.
- sym: []reflect.Value{reflect.ValueOf(fmt.Println), reflect.ValueOf("Hello")},
+ sym: []Value{ValueOf(fmt.Println), ValueOf("Hello")},
code: [][]int64{
{0, Dup, 0},
{0, CallX, 1},