mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 14:57:18 +00:00
Enhacnementsand toolchain
This commit is contained in:
@@ -0,0 +1,523 @@
|
||||
// vm.v — the VuurRaaf runtime: a small stack-based virtual machine.
|
||||
//
|
||||
// Stack values are 64-bit tagged integers: odd values are string handles into
|
||||
// the runtime string heap (handle = value >> 1); even values are encoded
|
||||
// numbers (value = raw << 1). Encoding numbers as even values means no
|
||||
// integer ever collides with a string handle.
|
||||
//
|
||||
// Call convention: CALL pushes a frame (retaddr, old bp, argc) and copies the
|
||||
// arguments into the callee's local slots; the callee reserves extra locals
|
||||
// with `enter n` and cleans up with `ret`/`retv`.
|
||||
module vm
|
||||
|
||||
import obj
|
||||
|
||||
// opcodes — keep in sync with the compiler, assembler, and this interpreter
|
||||
const op_halt = u8(0)
|
||||
const op_push_i = u8(1)
|
||||
const op_push_s = u8(2)
|
||||
const op_load = u8(3)
|
||||
const op_store = u8(4)
|
||||
const op_pop = u8(5)
|
||||
const op_dup = u8(6)
|
||||
const op_add = u8(7)
|
||||
const op_sub = u8(8)
|
||||
const op_mul = u8(9)
|
||||
const op_div = u8(10)
|
||||
const op_mod = u8(11)
|
||||
const op_neg = u8(12)
|
||||
const op_eq = u8(13)
|
||||
const op_ne = u8(14)
|
||||
const op_lt = u8(15)
|
||||
const op_le = u8(16)
|
||||
const op_gt = u8(17)
|
||||
const op_ge = u8(18)
|
||||
const op_and = u8(19)
|
||||
const op_or = u8(20)
|
||||
const op_not = u8(21)
|
||||
const op_jmp = u8(22)
|
||||
const op_jz = u8(23)
|
||||
const op_jnz = u8(24)
|
||||
const op_call = u8(25)
|
||||
const op_ret = u8(26)
|
||||
const op_retv = u8(27)
|
||||
const op_print = u8(28)
|
||||
const op_println = u8(29)
|
||||
const op_assert = u8(30)
|
||||
const op_enter = u8(31)
|
||||
|
||||
const stack_cap = 65536
|
||||
|
||||
struct Vm {
|
||||
mut:
|
||||
code []u8
|
||||
strings []string
|
||||
stack []i64
|
||||
sp int
|
||||
bp int
|
||||
ip int
|
||||
trace bool
|
||||
halted bool
|
||||
}
|
||||
|
||||
// run executes the function named `entry` from the executable `bin` and
|
||||
// returns its return value (0 if it never returns one).
|
||||
pub fn run(bin obj.Bin, entry string, trace bool) !i64 {
|
||||
mut v := Vm{
|
||||
code: bin.code
|
||||
strings: bin.strings.clone()
|
||||
stack: []i64{len: stack_cap}
|
||||
trace: trace
|
||||
}
|
||||
mut entry_ip := -1
|
||||
for f in bin.fns {
|
||||
if f.name == entry {
|
||||
entry_ip = f.entry
|
||||
break
|
||||
}
|
||||
}
|
||||
if entry_ip < 0 {
|
||||
names := bin.fns.map(fn (f obj.BinFn) string {
|
||||
return f.name
|
||||
})
|
||||
return error('no function "${entry}" in program (available: ${names.join(', ')})')
|
||||
}
|
||||
// synthetic frame: retaddr = -1 (halt sentinel), old bp = 0, argc = 0
|
||||
v.stack[v.sp] = v.enc_int(-1)
|
||||
v.sp++
|
||||
v.stack[v.sp] = v.enc_int(0)
|
||||
v.sp++
|
||||
v.stack[v.sp] = v.enc_int(0)
|
||||
v.sp++
|
||||
v.bp = v.sp
|
||||
v.ip = entry_ip
|
||||
v.exec()!
|
||||
if v.sp > 0 {
|
||||
return v.dec_int(v.stack[0])
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
fn (mut v Vm) exec() ! {
|
||||
for !v.halted {
|
||||
op := v.code[v.ip]
|
||||
if v.trace {
|
||||
v.trace_op(op)
|
||||
}
|
||||
match op {
|
||||
op_halt {
|
||||
v.halted = true
|
||||
}
|
||||
op_push_i {
|
||||
v.ip++
|
||||
v.push(v.enc_int(v.read_i64()))!
|
||||
}
|
||||
op_push_s {
|
||||
v.ip++
|
||||
idx := int(v.read_i64())
|
||||
v.push(v.mkstr(idx))!
|
||||
}
|
||||
op_load {
|
||||
v.ip++
|
||||
idx := int(v.read_i64())
|
||||
v.push(v.stack[v.bp + idx])!
|
||||
}
|
||||
op_store {
|
||||
v.ip++
|
||||
idx := int(v.read_i64())
|
||||
v.stack[v.bp + idx] = v.pop()!
|
||||
}
|
||||
op_pop {
|
||||
v.ip++
|
||||
v.pop()!
|
||||
}
|
||||
op_dup {
|
||||
v.ip++
|
||||
a := v.pop()!
|
||||
v.push(a)!
|
||||
v.push(a)!
|
||||
}
|
||||
op_add {
|
||||
v.ip++
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
v.push(v.add(a, b)!)!
|
||||
}
|
||||
op_sub {
|
||||
v.ip++
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
v.push(v.arith(a, b, '-')!)!
|
||||
}
|
||||
op_mul {
|
||||
v.ip++
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
v.push(v.arith(a, b, '*')!)!
|
||||
}
|
||||
op_div {
|
||||
v.ip++
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
v.push(v.arith(a, b, '/')!)!
|
||||
}
|
||||
op_mod {
|
||||
v.ip++
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
v.push(v.arith(a, b, '%')!)!
|
||||
}
|
||||
op_neg {
|
||||
v.ip++
|
||||
a := v.pop()!
|
||||
if v.is_str(a) {
|
||||
return error('cannot negate a string')
|
||||
}
|
||||
v.push(v.enc_int(-v.dec_int(a)))!
|
||||
}
|
||||
op_eq {
|
||||
v.ip++
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
v.push(v.enc_int(v.cmp(a, b, '==')!))!
|
||||
}
|
||||
op_ne {
|
||||
v.ip++
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
v.push(v.enc_int(v.cmp(a, b, '!=')!))!
|
||||
}
|
||||
op_lt {
|
||||
v.ip++
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
v.push(v.enc_int(v.cmp(a, b, '<')!))!
|
||||
}
|
||||
op_le {
|
||||
v.ip++
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
v.push(v.enc_int(v.cmp(a, b, '<=')!))!
|
||||
}
|
||||
op_gt {
|
||||
v.ip++
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
v.push(v.enc_int(v.cmp(a, b, '>')!))!
|
||||
}
|
||||
op_ge {
|
||||
v.ip++
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
v.push(v.enc_int(v.cmp(a, b, '>=')!))!
|
||||
}
|
||||
op_and {
|
||||
v.ip++
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
v.push(v.enc_int(bool_i64(v.truthy(a) && v.truthy(b))))!
|
||||
}
|
||||
op_or {
|
||||
v.ip++
|
||||
b := v.pop()!
|
||||
a := v.pop()!
|
||||
v.push(v.enc_int(bool_i64(v.truthy(a) || v.truthy(b))))!
|
||||
}
|
||||
op_not {
|
||||
v.ip++
|
||||
a := v.pop()!
|
||||
v.push(v.enc_int(bool_i64(!v.truthy(a))))!
|
||||
}
|
||||
op_jmp {
|
||||
v.ip++
|
||||
v.ip = int(v.read_i64())
|
||||
}
|
||||
op_jz {
|
||||
v.ip++
|
||||
target := int(v.read_i64())
|
||||
if !v.truthy(v.pop()!) {
|
||||
v.ip = target
|
||||
}
|
||||
}
|
||||
op_jnz {
|
||||
v.ip++
|
||||
target := int(v.read_i64())
|
||||
if v.truthy(v.pop()!) {
|
||||
v.ip = target
|
||||
}
|
||||
}
|
||||
op_call {
|
||||
v.ip++
|
||||
target := int(v.read_i64())
|
||||
argc := int(v.read_i64())
|
||||
v.call(target, argc)
|
||||
}
|
||||
op_ret {
|
||||
v.ret(false)!
|
||||
}
|
||||
op_retv {
|
||||
v.ret(true)!
|
||||
}
|
||||
op_print {
|
||||
v.ip++
|
||||
v.print_val(v.pop()!)
|
||||
}
|
||||
op_println {
|
||||
v.ip++
|
||||
v.print_val(v.pop()!)
|
||||
println('')
|
||||
}
|
||||
op_assert {
|
||||
v.ip++
|
||||
if !v.truthy(v.pop()!) {
|
||||
return error('assertion failed (ip ${v.ip})')
|
||||
}
|
||||
}
|
||||
op_enter {
|
||||
v.ip++
|
||||
n := int(v.read_i64())
|
||||
for _ in 0..n {
|
||||
v.push(0)!
|
||||
}
|
||||
}
|
||||
else {
|
||||
return error('unknown opcode ${op} at ip ${v.ip}')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut v Vm) read_i64() i64 {
|
||||
mut val := u64(0)
|
||||
for i in 0..8 {
|
||||
val |= u64(v.code[v.ip + i]) << u32(8 * i)
|
||||
}
|
||||
v.ip += 8
|
||||
return i64(val)
|
||||
}
|
||||
|
||||
fn (mut v Vm) push(x i64) ! {
|
||||
if v.sp >= v.stack.len {
|
||||
return error('stack overflow')
|
||||
}
|
||||
v.stack[v.sp] = x
|
||||
v.sp++
|
||||
}
|
||||
|
||||
fn (mut v Vm) pop() !i64 {
|
||||
if v.sp <= 0 {
|
||||
return error('stack underflow')
|
||||
}
|
||||
v.sp--
|
||||
return v.stack[v.sp]
|
||||
}
|
||||
|
||||
fn (mut v Vm) call(target int, argc int) {
|
||||
v.stack[v.sp] = v.enc_int(i64(v.ip)) // return address (ip already past both operands)
|
||||
v.sp++
|
||||
v.stack[v.sp] = v.enc_int(i64(v.bp))
|
||||
v.sp++
|
||||
v.stack[v.sp] = v.enc_int(i64(argc))
|
||||
v.sp++
|
||||
v.bp = v.sp
|
||||
// copy the arguments below the frame into local slots 0..argc-1
|
||||
for i in 0..argc {
|
||||
v.stack[v.bp + i] = v.stack[v.bp - 3 - argc + i]
|
||||
}
|
||||
v.sp = v.bp + argc
|
||||
v.ip = target
|
||||
}
|
||||
|
||||
fn (mut v Vm) ret(with_val bool) ! {
|
||||
retval := if with_val { v.pop()! } else { v.enc_int(0) }
|
||||
v.sp = v.bp - 1
|
||||
argc := int(v.dec_int(v.stack[v.sp]))
|
||||
v.sp = v.bp - 2
|
||||
old_bp := int(v.dec_int(v.stack[v.sp]))
|
||||
v.sp = v.bp - 3
|
||||
ip := int(v.dec_int(v.stack[v.sp]))
|
||||
v.sp -= argc
|
||||
v.bp = old_bp
|
||||
if ip == -1 {
|
||||
// returned to the synthetic frame: we are done
|
||||
v.halted = true
|
||||
v.push(retval)!
|
||||
return
|
||||
}
|
||||
v.ip = ip
|
||||
v.push(retval)!
|
||||
}
|
||||
|
||||
fn (mut v Vm) is_str(x i64) bool {
|
||||
return x & 1 == 1
|
||||
}
|
||||
|
||||
fn (mut v Vm) enc_int(x i64) i64 {
|
||||
return x << 1
|
||||
}
|
||||
|
||||
fn (mut v Vm) dec_int(x i64) i64 {
|
||||
return x >> 1
|
||||
}
|
||||
|
||||
fn (mut v Vm) hand(x i64) int {
|
||||
return int(x >> 1)
|
||||
}
|
||||
|
||||
fn (mut v Vm) mkstr(idx int) i64 {
|
||||
return (i64(idx) << 1) | 1
|
||||
}
|
||||
|
||||
fn (mut v Vm) truthy(x i64) bool {
|
||||
return x != 0
|
||||
}
|
||||
|
||||
fn bool_i64(b bool) i64 {
|
||||
return if b { i64(1) } else { i64(0) }
|
||||
}
|
||||
|
||||
fn (mut v Vm) add(a i64, b i64) !i64 {
|
||||
if v.is_str(a) && v.is_str(b) {
|
||||
return v.alloc_str(v.strings[v.hand(a)] + v.strings[v.hand(b)])
|
||||
}
|
||||
if v.is_str(a) {
|
||||
return v.alloc_str(v.strings[v.hand(a)] + v.num_str(b))
|
||||
}
|
||||
if v.is_str(b) {
|
||||
return v.alloc_str(v.num_str(a) + v.strings[v.hand(b)])
|
||||
}
|
||||
return v.enc_int(v.dec_int(a) + v.dec_int(b))
|
||||
}
|
||||
|
||||
fn (mut v Vm) alloc_str(s string) i64 {
|
||||
v.strings << s
|
||||
return v.mkstr(v.strings.len - 1)
|
||||
}
|
||||
|
||||
fn (mut v Vm) num_str(x i64) string {
|
||||
return v.dec_int(x).str()
|
||||
}
|
||||
|
||||
fn (mut v Vm) arith(a i64, b i64, op string) !i64 {
|
||||
if v.is_str(a) || v.is_str(b) {
|
||||
return error('cannot use strings with "${op}"')
|
||||
}
|
||||
x := v.dec_int(a)
|
||||
y := v.dec_int(b)
|
||||
match op {
|
||||
'-' {
|
||||
return v.enc_int(x - y)
|
||||
}
|
||||
'*' {
|
||||
return v.enc_int(x * y)
|
||||
}
|
||||
'/' {
|
||||
if y == 0 {
|
||||
return error('division by zero')
|
||||
}
|
||||
return v.enc_int(x / y)
|
||||
}
|
||||
'%' {
|
||||
if y == 0 {
|
||||
return error('division by zero')
|
||||
}
|
||||
return v.enc_int(x % y)
|
||||
}
|
||||
else {
|
||||
return error('internal: bad arith op "${op}"')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut v Vm) cmp(a i64, b i64, op string) !i64 {
|
||||
if v.is_str(a) && v.is_str(b) {
|
||||
sa := v.strings[v.hand(a)]
|
||||
sb := v.strings[v.hand(b)]
|
||||
return bool_i64(match op {
|
||||
'==' { sa == sb }
|
||||
'!=' { sa != sb }
|
||||
'<' { sa < sb }
|
||||
'<=' { sa <= sb }
|
||||
'>' { sa > sb }
|
||||
'>=' { sa >= sb }
|
||||
else { return error('internal: bad cmp op "${op}"') }
|
||||
})
|
||||
}
|
||||
if v.is_str(a) || v.is_str(b) {
|
||||
return error('cannot compare a string and a number')
|
||||
}
|
||||
x := v.dec_int(a)
|
||||
y := v.dec_int(b)
|
||||
return bool_i64(match op {
|
||||
'==' { x == y }
|
||||
'!=' { x != y }
|
||||
'<' { x < y }
|
||||
'<=' { x <= y }
|
||||
'>' { x > y }
|
||||
'>=' { x >= y }
|
||||
else { return error('internal: bad cmp op "${op}"') }
|
||||
})
|
||||
}
|
||||
|
||||
fn (mut v Vm) print_val(x i64) {
|
||||
if v.is_str(x) && v.valid_handle(x) {
|
||||
print(v.strings[v.hand(x)])
|
||||
} else {
|
||||
print(v.dec_int(x))
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut v Vm) valid_handle(x i64) bool {
|
||||
h := v.hand(x)
|
||||
return h >= 0 && h < v.strings.len
|
||||
}
|
||||
|
||||
fn (mut v Vm) trace_op(op u8) {
|
||||
name := match op {
|
||||
op_halt { 'halt' }
|
||||
op_push_i { 'push_int' }
|
||||
op_push_s { 'push_str' }
|
||||
op_load { 'load' }
|
||||
op_store { 'store' }
|
||||
op_pop { 'pop' }
|
||||
op_dup { 'dup' }
|
||||
op_add { 'add' }
|
||||
op_sub { 'sub' }
|
||||
op_mul { 'mul' }
|
||||
op_div { 'div' }
|
||||
op_mod { 'mod' }
|
||||
op_neg { 'neg' }
|
||||
op_eq { 'eq' }
|
||||
op_ne { 'ne' }
|
||||
op_lt { 'lt' }
|
||||
op_le { 'le' }
|
||||
op_gt { 'gt' }
|
||||
op_ge { 'ge' }
|
||||
op_and { 'and' }
|
||||
op_or { 'or' }
|
||||
op_not { 'not' }
|
||||
op_jmp { 'jmp' }
|
||||
op_jz { 'jz' }
|
||||
op_jnz { 'jnz' }
|
||||
op_call { 'call' }
|
||||
op_ret { 'ret' }
|
||||
op_retv { 'retv' }
|
||||
op_print { 'print' }
|
||||
op_println { 'println' }
|
||||
op_assert { 'assert' }
|
||||
op_enter { 'enter' }
|
||||
else { '??' }
|
||||
}
|
||||
mut s := ''
|
||||
for i in 0..v.sp {
|
||||
if i > 0 {
|
||||
s += ' '
|
||||
}
|
||||
if v.is_str(v.stack[i]) && v.valid_handle(v.stack[i]) {
|
||||
s += '"${v.strings[v.hand(v.stack[i])]}"'
|
||||
} else {
|
||||
s += '${v.dec_int(v.stack[i])}'
|
||||
}
|
||||
}
|
||||
println(' [ip=${v.ip:4}] ${name:-9} stack: [${s}]')
|
||||
}
|
||||
Reference in New Issue
Block a user