refactor vm
This commit is contained in:
parent
5b23d27c0a
commit
a1445f4f62
3
main.nim
3
main.nim
|
@ -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:
|
||||
|
|
|
@ -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
243
vm.nim
|
@ -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()
|
||||
|
||||
|
|
Loading…
Reference in New Issue