refactor vm

This commit is contained in:
prod2 2022-01-27 01:36:43 +01:00
parent 5b23d27c0a
commit a1445f4f62
3 changed files with 118 additions and 129 deletions

View File

@ -11,8 +11,7 @@ proc interpret(name: string, source: string): Result =
compiler.compile()
if compiler.hadError:
return rsCompileError
let vm = newVM(compiler.chunk)
case vm.run():
case compiler.chunk.run():
of irOK:
rsOK
of irRuntimeError:

View File

@ -20,6 +20,7 @@ type
of ktString:
stringValue*: string
of ktFunct:
module*: string
entryII*: int # entry instruction index
arity*: int # number of arguments
of errorTypes:

243
vm.nim
View File

@ -5,6 +5,7 @@ import value
import chunk
import config
import pointerutils
when profileInstructions:
import times
import std/monotimes
@ -14,93 +15,84 @@ type
stackBottom: int # the absolute index of where 0 inside the frame is
returnIp: ptr uint8
VM* = ref object
chunk: Chunk
ip: ptr uint8
stack: seq[KonValue]
hadError: bool
globals: Table[string, KonValue]
frames: seq[Frame]
InterpretResult* = enum
irOK, irRuntimeError
proc newVM*(ch: Chunk): VM =
result = VM(chunk: ch, stack: newSeqOfCap[KonValue](256), frames: newSeqOfCap[Frame](4))
result.ip = ch.code[0].unsafeAddr
proc push(vm: VM, val: KonValue) =
vm.stack.add(val)
proc pop(vm: VM): KonValue =
vm.stack.pop()
proc peek(vm: VM): KonValue =
vm.stack[vm.stack.high]
proc runtimeError(vm: VM, msg: string) =
let ii = vm.ip.pdiff(vm.chunk.code[0].unsafeAddr)
let line = vm.chunk.lines[ii]
write stderr, &"[line: {line}] {msg}\n"
vm.hadError = true
proc pushSafe(vm: VM, val: KonValue): bool =
## returns if the value is not a runtime error
## prints the error if it is a runtime error
## pushes it to the stack if it is not an error
if val.isError():
vm.runtimeError($val)
return false
else:
vm.push(val)
return true
proc advance(vm: VM): uint8 =
result = vm.ip[]
vm.ip = vm.ip.padd(1)
proc readDU8(vm: VM): int =
result = vm.ip.DU8ptrToInt
vm.ip = vm.ip.padd(argSize)
proc readConstant(vm: VM): KonValue =
let index = vm.readDU8()
vm.chunk.constants[index]
proc binary(op: OpCode, left: KonValue, right: KonValue): KonValue =
case op:
of opAdd:
return left.add(right)
of opSubtract:
return left.subtract(right)
of opMultiply:
return left.multiply(right)
of opDivide:
return left.divide(right)
else:
discard #unreachable
when profileInstructions:
# not a perfect profiling approach, doing random stacktrace dumps
# x amount of times per second to get the current source line
# would be better
var durations: array[OpCode, Duration]
proc run*(vm: VM): InterpretResult =
proc run*(chunk: Chunk): InterpretResult =
template frameBottom: int = vm.frames[vm.frames.high].stackBottom
var
ip: ptr uint8 = chunk.code[0].unsafeAddr
stack: seq[KonValue] = newSeqOfCap[KonValue](256)
hadError: bool
globals: Table[string, KonValue]
frames: seq[Frame] = newSeqOfCap[Frame](4)
proc runtimeError(msg: string) =
let ii = ip.pdiff(chunk.code[0].unsafeAddr)
let line = chunk.lines[ii]
write stderr, &"[line: {line}] {msg}\n"
hadError = true
proc peek(stack: seq[KonValue]): KonValue {.inline.} =
stack[stack.high]
proc pushSafe(stack: var seq[KonValue], val: KonValue): bool =
## returns if the value is not a runtime error
## prints the error if it is a runtime error
## pushes it to the stack if it is not an error
if val.isError():
runtimeError($val)
false
else:
stack.add(val)
true
proc advance(): uint8 =
result = ip[]
ip = ip.padd(1)
proc readDU8(): int =
result = ip.DU8ptrToInt
ip = ip.padd(argSize)
proc readConstant(): KonValue =
let index = readDU8()
chunk.constants[index]
proc binary(op: OpCode, left: KonValue, right: KonValue): KonValue =
case op:
of opAdd:
return left.add(right)
of opSubtract:
return left.subtract(right)
of opMultiply:
return left.multiply(right)
of opDivide:
return left.divide(right)
else:
discard #unreachable
template frameBottom: int = frames[frames.high].stackBottom
while true:
{.computedgoto.} # See https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma
let ins = vm.advance.OpCode
#let ins = advance().OpCode
let ins = ip[].OpCode
ip = ip.padd(1)
when debugVM:
let ii = ip.pdiff(chunk.code[0].unsafeAddr)
let opname = ($ins)
var msg = &"[{vm.ii:4}] {opname}"
var msg = &"[{ii:4}] {opname}"
msg &= " Stack: [ "
for i in 0 .. vm.stack.high:
let e = vm.stack[i]
for i in 0 .. stack.high:
let e = stack[i]
if i == frameBottom:
msg &= &"<{e}> "
else:
@ -113,124 +105,121 @@ proc run*(vm: VM): InterpretResult =
case ins:
of opPop:
discard vm.pop()
discard stack.pop()
of opConstant:
let val: KonValue = vm.readConstant()
vm.push(val)
let val: KonValue = readConstant()
stack.add(val)
of opNegate:
let val = vm.pop.negate
if not vm.pushSafe(val):
let val = stack.pop().negate
if not stack.pushSafe(val):
break
of opAdd, opSubtract, opMultiply, opDivide:
let right = vm.pop()
let left = vm.pop()
if not vm.pushSafe(binary(ins, left, right)):
let right = stack.pop()
let left = stack.pop()
if not stack.pushSafe(binary(ins, left, right)):
break
of opReturn:
if vm.frames.len == 0:
if frames.len == 0:
break
else:
vm.ip = vm.frames.pop().returnIp # remove frame that's over
ip = frames.pop().returnIp # remove frame that's over
of opTrue:
vm.push(toKonValue(true))
stack.add(toKonValue(true))
of opFalse:
vm.push(toKonValue(false))
stack.add(toKonValue(false))
of opNil:
vm.push(toKonValue())
stack.add(toKonValue())
of opNot:
vm.push(toKonValue(vm.pop.isFalsey))
stack.add(toKonValue(stack.pop().isFalsey))
of opEqual:
vm.push(toKonValue(vm.pop.equal(vm.pop())))
stack.add(toKonValue(stack.pop().equal(stack.pop())))
of opLess:
if not vm.pushSafe(vm.pop() > vm.pop()):
if not stack.pushSafe(stack.pop() > stack.pop()):
break
of opGreater:
if not vm.pushSafe(vm.pop() < vm.pop()):
if not stack.pushSafe(stack.pop() < stack.pop()):
break
of opPrint:
echo $vm.peek()
echo $stack.peek()
of opDefineGlobal:
let name = vm.readConstant().stringValue
if not vm.globals.hasKey(name):
vm.globals[name] = vm.pop()
let name = readConstant().stringValue
if not globals.hasKey(name):
globals[name] = stack.pop()
else:
vm.runtimeError("Attempt to redefine an existing global variable.")
runtimeError("Attempt to redefine an existing global variable.")
break
of opGetGlobal:
let name = vm.readConstant().stringValue
if vm.globals.hasKey(name):
vm.push(vm.globals[name])
let name = readConstant().stringValue
if globals.hasKey(name):
stack.add(globals[name])
else:
vm.runtimeError(&"Undefined global variable {name}.")
runtimeError(&"Undefined global variable {name}.")
break
of opSetGlobal:
let name = vm.readConstant().stringValue
if vm.globals.hasKey(name):
vm.globals[name] = vm.peek()
let name = readConstant().stringValue
if globals.hasKey(name):
globals[name] = stack.peek()
else:
vm.runtimeError(&"Attempt to set undefined global variable {name}.")
runtimeError(&"Attempt to set undefined global variable {name}.")
break
of opGetLocal:
let slot = vm.readDU8()
vm.push(vm.stack[slot + frameBottom])
let slot = readDU8()
stack.add(stack[slot + frameBottom])
of opSetLocal:
let slot = vm.readDU8()
vm.stack[slot + frameBottom] = vm.peek()
let slot = readDU8()
stack[slot + frameBottom] = stack.peek()
of opJumpIfFalse:
let offset = vm.readDU8()
if vm.peek.isFalsey:
vm.ip = vm.ip.padd(offset)
let offset = readDU8()
if stack.peek().isFalsey:
ip = ip.padd(offset)
of opJumpIfFalsePop:
let offset = vm.readDU8()
if vm.pop.isFalsey:
vm.ip = vm.ip.padd(offset)
let offset = readDU8()
if stack.pop().isFalsey:
ip = ip.padd(offset)
of opJump:
let offset = vm.readDU8()
vm.ip = vm.ip.padd(offset)
let offset = readDU8()
ip = ip.padd(offset)
of opLoop:
let offset = vm.readDU8()
vm.ip = vm.ip.psub(offset)
let offset = readDU8()
ip = ip.psub(offset)
of opCall:
# create the call env
# current stack before opCall:
# ... <funct obj> <arg1> <arg2> <arg3>
# opCall converts it to this
# ... <ret val> <arg1> <arg2> <arg3>
let argcount = vm.readDU8()
let funct = vm.stack[vm.stack.high - argcount]
let argcount = readDU8()
let funct = stack[stack.high - argcount]
if funct.konType != ktFunct:
vm.runtimeError("Attempt to call a non-funct (a defunct?).") # here is a bad defunct joke
runtimeError("Attempt to call a non-funct (a defunct?).") # here is a bad defunct joke
break
if funct.arity != argcount:
vm.runtimeError(&"Wrong number of arguments, expected {funct.arity}, got {argcount}.")
runtimeError(&"Wrong number of arguments, expected {funct.arity}, got {argcount}.")
break
# if funct.module ... TODO
vm.stack[vm.stack.high - argcount] = toKonValue() # replace the function with nil: this is the return value slot
stack[stack.high - argcount] = toKonValue() # replace the function with nil: this is the return value slot
# create new frame
vm.frames.add(Frame(stackBottom: vm.stack.high - argcount, returnIp: vm.ip))
frames.add(Frame(stackBottom: stack.high - argcount, returnIp: ip))
vm.ip = vm.chunk.code[0].unsafeAddr.padd(funct.entryII) # jump to the entry point
ip = chunk.code[0].unsafeAddr.padd(funct.entryII) # jump to the entry point
when profileInstructions:
durations[ins] += getMonoTime() - startTime
when assertionsVM:
if not vm.hadError and vm.stack.len > 0:
vm.runtimeError(&"VM Assertion failed: stack is of non-zero length {vm.stack.len} after execution has finished.")
if not hadError and stack.len > 0:
runtimeError(&"VM Assertion failed: stack is of non-zero length {stack.len} after execution has finished.")
when profileInstructions:
for op in OpCode:
let dur = durations[op].inMilliseconds
echo &"OpCode: {op} total duration {dur} ms"
if vm.hadError:
if hadError:
irRuntimeError
else:
irOK
proc interpretChunk*(ch: Chunk): InterpretResult =
let vm = newVM(ch)
return vm.run()