2022-01-20 21:54:11 +01:00
|
|
|
import strformat
|
|
|
|
import tables
|
|
|
|
|
|
|
|
import value
|
|
|
|
import chunk
|
|
|
|
import config
|
2022-01-22 04:46:53 +01:00
|
|
|
import pointerutils
|
2022-01-27 01:36:43 +01:00
|
|
|
|
2022-01-27 18:54:02 +01:00
|
|
|
import types/stack
|
|
|
|
|
2022-01-22 17:28:53 +01:00
|
|
|
when profileInstructions:
|
|
|
|
import times
|
|
|
|
import std/monotimes
|
2022-01-20 21:54:11 +01:00
|
|
|
|
|
|
|
type
|
2022-01-21 01:51:55 +01:00
|
|
|
Frame = object
|
|
|
|
stackBottom: int # the absolute index of where 0 inside the frame is
|
2022-01-22 04:46:53 +01:00
|
|
|
returnIp: ptr uint8
|
2022-01-21 01:51:55 +01:00
|
|
|
|
2022-01-20 21:54:11 +01:00
|
|
|
InterpretResult* = enum
|
|
|
|
irOK, irRuntimeError
|
|
|
|
|
2022-01-22 17:28:53 +01:00
|
|
|
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]
|
|
|
|
|
2022-01-27 01:36:43 +01:00
|
|
|
proc run*(chunk: Chunk): InterpretResult =
|
2022-01-20 21:54:11 +01:00
|
|
|
|
2022-01-27 01:36:43 +01:00
|
|
|
var
|
|
|
|
ip: ptr uint8 = chunk.code[0].unsafeAddr
|
2022-01-27 18:54:02 +01:00
|
|
|
#stack: seq[NdValue] = newSeqOfCap[NdValue](256)
|
|
|
|
stack: Stack[NdValue] = newStack[NdValue](256)
|
2022-01-27 01:36:43 +01:00
|
|
|
hadError: bool
|
2022-01-27 05:37:10 +01:00
|
|
|
globals: Table[string, NdValue]
|
2022-01-27 01:36:43 +01:00
|
|
|
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
|
|
|
|
|
2022-01-27 18:54:02 +01:00
|
|
|
#template peek(stack: seq[NdValue]): NdValue =
|
|
|
|
# stack[stack.high]
|
2022-01-27 01:36:43 +01:00
|
|
|
|
2022-01-27 18:54:02 +01:00
|
|
|
#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)
|
2022-01-27 03:32:42 +01:00
|
|
|
|
|
|
|
proc readUI8(): int =
|
|
|
|
result = ip[].int
|
|
|
|
ip = ip.padd(1)
|
2022-01-27 04:41:32 +01:00
|
|
|
|
2022-01-27 01:36:43 +01:00
|
|
|
proc readDU8(): int =
|
|
|
|
result = ip.DU8ptrToInt
|
|
|
|
ip = ip.padd(argSize)
|
|
|
|
|
2022-01-27 05:37:10 +01:00
|
|
|
proc readConstant(): NdValue =
|
2022-01-27 01:36:43 +01:00
|
|
|
let index = readDU8()
|
|
|
|
chunk.constants[index]
|
2022-01-27 03:32:42 +01:00
|
|
|
|
2022-01-27 01:36:43 +01:00
|
|
|
template frameBottom: int = frames[frames.high].stackBottom
|
2022-01-27 06:09:04 +01:00
|
|
|
|
2022-01-20 21:54:11 +01:00
|
|
|
while true:
|
2022-01-22 04:46:53 +01:00
|
|
|
{.computedgoto.} # See https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma
|
|
|
|
|
2022-01-27 01:36:43 +01:00
|
|
|
let ins = ip[].OpCode
|
|
|
|
ip = ip.padd(1)
|
2022-01-21 01:51:55 +01:00
|
|
|
|
2022-01-20 21:54:11 +01:00
|
|
|
when debugVM:
|
2022-01-27 01:36:43 +01:00
|
|
|
let ii = ip.pdiff(chunk.code[0].unsafeAddr)
|
2022-01-21 01:51:55 +01:00
|
|
|
let opname = ($ins)
|
2022-01-27 01:36:43 +01:00
|
|
|
var msg = &"[{ii:4}] {opname}"
|
2022-01-20 21:54:11 +01:00
|
|
|
msg &= " Stack: [ "
|
2022-01-27 18:54:02 +01:00
|
|
|
for i in 0 .. stack.high():
|
2022-01-27 01:36:43 +01:00
|
|
|
let e = stack[i]
|
2022-01-21 01:51:55 +01:00
|
|
|
if i == frameBottom:
|
|
|
|
msg &= &"<{e}> "
|
|
|
|
else:
|
|
|
|
msg &= &"{e} "
|
2022-01-20 21:54:11 +01:00
|
|
|
msg &= "]"
|
|
|
|
echo msg
|
2022-01-22 17:28:53 +01:00
|
|
|
|
|
|
|
when profileInstructions:
|
|
|
|
let startTime = getMonoTime()
|
|
|
|
|
2022-01-20 21:54:11 +01:00
|
|
|
case ins:
|
2022-01-22 04:46:53 +01:00
|
|
|
of opPop:
|
2022-01-27 01:36:43 +01:00
|
|
|
discard stack.pop()
|
2022-01-27 03:32:42 +01:00
|
|
|
of opPopA:
|
|
|
|
let amt = readDU8()
|
|
|
|
stack.popn(amt)
|
|
|
|
of opPopSA:
|
|
|
|
let amt = readUI8()
|
|
|
|
stack.popn(amt)
|
2022-01-20 21:54:11 +01:00
|
|
|
of opConstant:
|
2022-01-27 05:37:10 +01:00
|
|
|
let val: NdValue = readConstant()
|
2022-01-27 01:36:43 +01:00
|
|
|
stack.add(val)
|
2022-01-20 21:54:11 +01:00
|
|
|
of opNegate:
|
2022-01-27 06:09:04 +01:00
|
|
|
let res = stack.peek().negate()
|
2022-01-27 02:03:23 +01:00
|
|
|
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)
|
2022-01-20 21:54:11 +01:00
|
|
|
break
|
2022-01-27 02:03:23 +01:00
|
|
|
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:
|
2022-01-27 01:36:43 +01:00
|
|
|
let right = stack.pop()
|
2022-01-27 02:03:23 +01:00
|
|
|
let res = stack.peek().divide(right)
|
|
|
|
if not res.ok:
|
|
|
|
runtimeError(res.msg)
|
2022-01-20 21:54:11 +01:00
|
|
|
break
|
|
|
|
of opReturn:
|
2022-01-27 01:36:43 +01:00
|
|
|
if frames.len == 0:
|
2022-01-21 01:51:55 +01:00
|
|
|
break
|
|
|
|
else:
|
2022-01-27 01:36:43 +01:00
|
|
|
ip = frames.pop().returnIp # remove frame that's over
|
2022-01-20 21:54:11 +01:00
|
|
|
of opTrue:
|
2022-01-27 05:37:10 +01:00
|
|
|
stack.add(toNdValue(true))
|
2022-01-20 21:54:11 +01:00
|
|
|
of opFalse:
|
2022-01-27 05:37:10 +01:00
|
|
|
stack.add(toNdValue(false))
|
2022-01-20 21:54:11 +01:00
|
|
|
of opNil:
|
2022-01-27 05:37:10 +01:00
|
|
|
stack.add(toNdValue())
|
2022-01-20 21:54:11 +01:00
|
|
|
of opNot:
|
2022-01-27 05:37:10 +01:00
|
|
|
stack.add(toNdValue(stack.pop().isFalsey))
|
2022-01-20 21:54:11 +01:00
|
|
|
of opEqual:
|
2022-01-27 05:37:10 +01:00
|
|
|
stack.add(toNdValue(stack.pop().equal(stack.pop())))
|
2022-01-20 21:54:11 +01:00
|
|
|
of opLess:
|
2022-01-27 06:09:04 +01:00
|
|
|
let right = stack.pop()
|
|
|
|
let res = stack.peek().less(right)
|
|
|
|
if not res.ok:
|
|
|
|
runtimeError(res.msg)
|
2022-01-20 21:54:11 +01:00
|
|
|
break
|
|
|
|
of opGreater:
|
2022-01-27 06:09:04 +01:00
|
|
|
let right = stack.pop()
|
|
|
|
let res = stack.peek().greater(right)
|
|
|
|
if not res.ok:
|
|
|
|
runtimeError(res.msg)
|
2022-01-20 21:54:11 +01:00
|
|
|
break
|
|
|
|
of opPrint:
|
2022-01-27 01:36:43 +01:00
|
|
|
echo $stack.peek()
|
2022-01-20 21:54:11 +01:00
|
|
|
of opDefineGlobal:
|
2022-01-27 01:36:43 +01:00
|
|
|
let name = readConstant().stringValue
|
|
|
|
if not globals.hasKey(name):
|
|
|
|
globals[name] = stack.pop()
|
2022-01-20 21:54:11 +01:00
|
|
|
else:
|
2022-01-27 01:36:43 +01:00
|
|
|
runtimeError("Attempt to redefine an existing global variable.")
|
2022-01-20 21:54:11 +01:00
|
|
|
break
|
|
|
|
of opGetGlobal:
|
2022-01-27 01:36:43 +01:00
|
|
|
let name = readConstant().stringValue
|
|
|
|
if globals.hasKey(name):
|
|
|
|
stack.add(globals[name])
|
2022-01-20 21:54:11 +01:00
|
|
|
else:
|
2022-01-27 01:36:43 +01:00
|
|
|
runtimeError(&"Undefined global variable {name}.")
|
2022-01-20 21:54:11 +01:00
|
|
|
break
|
|
|
|
of opSetGlobal:
|
2022-01-27 01:36:43 +01:00
|
|
|
let name = readConstant().stringValue
|
|
|
|
if globals.hasKey(name):
|
|
|
|
globals[name] = stack.peek()
|
2022-01-20 21:54:11 +01:00
|
|
|
else:
|
2022-01-27 01:36:43 +01:00
|
|
|
runtimeError(&"Attempt to set undefined global variable {name}.")
|
2022-01-20 21:54:11 +01:00
|
|
|
break
|
|
|
|
of opGetLocal:
|
2022-01-27 01:36:43 +01:00
|
|
|
let slot = readDU8()
|
|
|
|
stack.add(stack[slot + frameBottom])
|
2022-01-20 21:54:11 +01:00
|
|
|
of opSetLocal:
|
2022-01-27 01:36:43 +01:00
|
|
|
let slot = readDU8()
|
|
|
|
stack[slot + frameBottom] = stack.peek()
|
2022-01-20 21:54:11 +01:00
|
|
|
of opJumpIfFalse:
|
2022-01-27 01:36:43 +01:00
|
|
|
let offset = readDU8()
|
|
|
|
if stack.peek().isFalsey:
|
|
|
|
ip = ip.padd(offset)
|
2022-01-20 21:54:11 +01:00
|
|
|
of opJumpIfFalsePop:
|
2022-01-27 01:36:43 +01:00
|
|
|
let offset = readDU8()
|
|
|
|
if stack.pop().isFalsey:
|
|
|
|
ip = ip.padd(offset)
|
2022-01-20 21:54:11 +01:00
|
|
|
of opJump:
|
2022-01-27 01:36:43 +01:00
|
|
|
let offset = readDU8()
|
|
|
|
ip = ip.padd(offset)
|
2022-01-20 21:54:11 +01:00
|
|
|
of opLoop:
|
2022-01-27 01:36:43 +01:00
|
|
|
let offset = readDU8()
|
|
|
|
ip = ip.psub(offset)
|
2022-01-27 05:56:09 +01:00
|
|
|
of opCheckArity:
|
|
|
|
let arity = readUI8()
|
|
|
|
let argcount = stack.high() - frameBottom
|
|
|
|
if arity != argcount:
|
|
|
|
runtimeError(&"Wrong number of arguments, expected {arity}, got {argcount}.")
|
|
|
|
break
|
2022-01-21 01:51:55 +01:00
|
|
|
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>
|
2022-01-27 05:56:09 +01:00
|
|
|
let argcount = readUI8()
|
2022-01-27 01:36:43 +01:00
|
|
|
let funct = stack[stack.high - argcount]
|
2022-01-27 05:37:10 +01:00
|
|
|
if funct.ndType != ndFunct:
|
2022-01-27 01:36:43 +01:00
|
|
|
runtimeError("Attempt to call a non-funct (a defunct?).") # here is a bad defunct joke
|
2022-01-21 01:51:55 +01:00
|
|
|
break
|
|
|
|
|
2022-01-27 05:37:10 +01:00
|
|
|
stack[stack.high - argcount] = toNdValue() # replace the function with nil: this is the return value slot
|
2022-01-21 01:51:55 +01:00
|
|
|
|
|
|
|
# create new frame
|
2022-01-27 01:36:43 +01:00
|
|
|
frames.add(Frame(stackBottom: stack.high - argcount, returnIp: ip))
|
2022-01-21 01:51:55 +01:00
|
|
|
|
2022-01-27 01:36:43 +01:00
|
|
|
ip = chunk.code[0].unsafeAddr.padd(funct.entryII) # jump to the entry point
|
2022-01-20 21:54:11 +01:00
|
|
|
|
2022-01-22 17:28:53 +01:00
|
|
|
when profileInstructions:
|
|
|
|
durations[ins] += getMonoTime() - startTime
|
|
|
|
|
2022-01-20 21:54:11 +01:00
|
|
|
when assertionsVM:
|
2022-01-27 01:36:43 +01:00
|
|
|
if not hadError and stack.len > 0:
|
|
|
|
runtimeError(&"VM Assertion failed: stack is of non-zero length {stack.len} after execution has finished.")
|
2022-01-22 17:28:53 +01:00
|
|
|
|
|
|
|
when profileInstructions:
|
|
|
|
for op in OpCode:
|
|
|
|
let dur = durations[op].inMilliseconds
|
|
|
|
echo &"OpCode: {op} total duration {dur} ms"
|
|
|
|
|
2022-01-27 01:36:43 +01:00
|
|
|
if hadError:
|
2022-01-20 21:54:11 +01:00
|
|
|
irRuntimeError
|
|
|
|
else:
|
|
|
|
irOK
|
|
|
|
|
|
|
|
|