nondescript/vm.nim

253 lines
6.9 KiB
Nim

import strformat
import tables
import value
import chunk
import config
import pointerutils
import types/stack
import types/ndstring
when profileInstructions:
import times
import std/monotimes
# 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]
var runcounts: array[OpCode, float64]
type
Frame = object
stackBottom: int # the absolute index of where 0 inside the frame is
returnIp: ptr uint8
InterpretResult* = enum
irOK, irRuntimeError
proc run*(chunk: Chunk): InterpretResult =
var
ip: ptr uint8 = chunk.code[0].unsafeAddr
stack: Stack[NdValue] = newStack[NdValue](256)
hadError: bool
globals: Table[string, NdValue]
frames: Stack[Frame] = newStack[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
frames.add(Frame(stackBottom: 0))
#template peek(stack: seq[NdValue]): NdValue =
# stack[stack.high]
#proc popn(stack: var seq[NdValue], amt: int) {.inline.} =
# for i in countup(1, amt):
# discard stack.pop()
template popn(stack: var Stack[NdValue], amt: int) =
stack.deleteTopN(amt)
proc readUI8(): int =
result = ip[].int
ip = ip.padd(1)
proc readDU8(): int =
result = ip.DU8ptrToInt
ip = ip.padd(argSize)
proc readConstant(): NdValue =
let index = readDU8()
chunk.constants[index]
#template frameBottom: int = frames.peek().stackBottom
template frameBottom: int = frames[frames.high].stackBottom
while true:
{.computedgoto.} # See https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma
let ins = ip[].OpCode
ip = ip.padd(1)
when debugVM:
let ii = ip.pdiff(chunk.code[0].unsafeAddr)
let opname = ($ins)
var msg = &"[{ii:4}] {opname}"
msg &= " Stack: [ "
for i in 0 .. stack.high():
let e = stack[i]
if i == frameBottom:
msg &= &"<{e}> "
else:
msg &= &"{e} "
msg &= "]"
echo msg
when profileInstructions:
let startTime = getMonoTime()
runtimes[ins].inc
case ins:
of opPop:
discard stack.pop()
of opPopA:
let amt = readDU8()
stack.popn(amt)
of opPopSA:
let amt = readUI8()
stack.popn(amt)
of opConstant:
let val: NdValue = readConstant()
stack.add(val)
of opNegate:
let res = stack.peek().negate()
if not res.ok:
runtimeError(res.msg)
break
of opAdd:
let right = stack.pop()
let res = stack.peek().add(right)
if not res.ok:
runtimeError(res.msg)
break
of opSubtract:
let right = stack.pop()
let res = stack.peek().subtract(right)
if not res.ok:
runtimeError(res.msg)
break
of opMultiply:
let right = stack.pop()
let res = stack.peek().multiply(right)
if not res.ok:
runtimeError(res.msg)
break
of opDivide:
let right = stack.pop()
let res = stack.peek().divide(right)
if not res.ok:
runtimeError(res.msg)
break
of opReturn:
if frames.len() == 1:
break
else:
ip = frames.pop().returnIp # remove frame that's over
of opTrue:
stack.add(toNdValue(true))
of opFalse:
stack.add(toNdValue(false))
of opNil:
stack.add(toNdValue())
of opNot:
stack.add(toNdValue(stack.pop().isFalsey))
of opEqual:
stack.add(toNdValue(stack.pop().equal(stack.pop())))
of opLess:
let right = stack.pop()
let res = stack.peek().less(right)
if not res.ok:
runtimeError(res.msg)
break
of opGreater:
let right = stack.pop()
let res = stack.peek().greater(right)
if not res.ok:
runtimeError(res.msg)
break
of opPrint:
echo $stack.peek()
of opDefineGlobal:
let name = $readConstant().stringValue
if not globals.hasKey(name):
globals[name] = stack.pop()
else:
runtimeError("Attempt to redefine an existing global variable.")
break
of opGetGlobal:
let name = $readConstant().stringValue
if globals.hasKey(name):
stack.add(globals[name])
else:
runtimeError(&"Undefined global variable {name}.")
break
of opSetGlobal:
let name = $readConstant().stringValue
if globals.hasKey(name):
globals[name] = stack.peek()
else:
runtimeError(&"Attempt to set undefined global variable {name}.")
break
of opGetLocal:
let slot = readDU8()
stack.add(stack[slot + frameBottom])
of opSetLocal:
let slot = readDU8()
stack[slot + frameBottom] = stack.peek()
of opJumpIfFalse:
let offset = readDU8()
if stack.peek().isFalsey:
ip = ip.padd(offset)
of opJumpIfFalsePop:
let offset = readDU8()
if stack.pop().isFalsey:
ip = ip.padd(offset)
of opJump:
let offset = readDU8()
ip = ip.padd(offset)
of opLoop:
let offset = readDU8()
ip = ip.psub(offset)
of opCheckArity:
let arity = readUI8()
let argcount = stack.high() - frameBottom
if arity != argcount:
runtimeError(&"Wrong number of arguments, expected {arity}, got {argcount}.")
break
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 = readUI8()
let funct = stack.getIndexNeg(argcount)
if funct.ndType != ntFunct:
runtimeError("Attempt to call a non-funct (a defunct?).") # here is a bad defunct joke
break
stack.setIndexNeg(argcount, toNdValue()) # replace the function with nil: this is the return value slot
# create new frame
frames.add(Frame(stackBottom: stack.high - argcount, returnIp: ip))
ip = chunk.code[0].unsafeAddr.padd(funct.entryII) # jump to the entry point
when profileInstructions:
durations[ins] += getMonoTime() - startTime
when assertionsVM:
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
let times = runcounts[op]
echo &"OpCode: {op} total duration {dur} ms {times} times"
stack.free()
frames.free()
if hadError:
irRuntimeError
else:
irOK