nondescript/src/ndspkg/vm.nim

319 lines
9.1 KiB
Nim
Raw Normal View History

2022-01-20 21:54:11 +01:00
import strformat
2022-01-29 05:12:33 +01:00
2022-01-20 21:54:11 +01:00
import chunk
import config
import pointerutils
2022-01-27 01:36:43 +01:00
import types/stack
2022-01-28 04:17:11 +01:00
import types/ndstring
import bitops # needed for value's templates
import types/value
import types/hashtable
2022-02-03 03:18:11 +01:00
import types/ndlist
2022-01-29 05:12:33 +01:00
2022-01-22 17:28:53 +01:00
when profileInstructions:
import times
import std/monotimes
2022-01-20 21:54:11 +01:00
2022-01-28 04:17:11 +01:00
# 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]
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
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-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
stack: Stack[NdValue] = newStack[NdValue](256)
2022-01-27 01:36:43 +01:00
hadError: bool
globals: Table[NdValue, NdValue]
2022-01-28 04:17:11 +01:00
frames: Stack[Frame] = newStack[Frame](4)
2022-01-27 01:36:43 +01:00
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-28 04:17:11 +01:00
frames.add(Frame(stackBottom: 0))
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-29 06:12:08 +01:00
template frameBottom: int = frames.peek().stackBottom
2022-01-20 21:54:11 +01:00
while true:
{.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-29 07:24:58 +01:00
let ii = ip.pdiff(chunk.code[0].unsafeAddr) - 1
2022-01-21 01:51:55 +01:00
let opname = ($ins)
2022-01-29 07:24:58 +01:00
var msg = &"[{ii:04}] {opname}"
2022-01-20 21:54:11 +01:00
msg &= " Stack: [ "
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-29 06:17:51 +01:00
runcounts[ins] += 1f
2022-01-22 17:28:53 +01:00
2022-01-20 21:54:11 +01:00
case ins:
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:
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)
2022-01-20 21:54:11 +01:00
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:
2022-01-27 01:36:43 +01:00
let right = stack.pop()
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-28 04:17:11 +01:00
if frames.len() == 1:
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-29 05:12:33 +01:00
stack.add(fromBool(true))
2022-01-20 21:54:11 +01:00
of opFalse:
2022-01-29 05:12:33 +01:00
stack.add(fromBool(false))
2022-01-20 21:54:11 +01:00
of opNil:
2022-01-29 05:12:33 +01:00
stack.add(fromNil())
of opNot: # TODO hayago += optimization
stack.add(fromBool(stack.pop().isFalsey()))
2022-01-20 21:54:11 +01:00
of opEqual:
2022-01-29 05:12:33 +01:00
stack.add(fromBool(stack.pop().equal(stack.pop())))
2022-01-20 21:54:11 +01:00
of opLess:
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:
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-02-05 02:45:29 +01:00
echo $stack.peek()
2022-01-20 21:54:11 +01:00
of opDefineGlobal:
let name = readConstant().asString()
let existed = globals.tableSet(name.fromNdString(), stack.pop())
if existed:
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:
let name = readConstant().asString()
var val: NdValue
let existed = globals.tableGet(name.fromNdString(), val)
if existed:
stack.add(val)
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:
let name = readConstant().asString()
2022-01-29 07:24:58 +01:00
let existed = globals.tableSet(name.fromNdString(), stack.peek())
if not existed:
runtimeError("Attempt to redefine an existing global variable.")
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()
2022-01-29 07:24:58 +01:00
if stack.peek().isFalsey():
2022-01-27 01:36:43 +01:00
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()
2022-01-29 07:24:58 +01:00
if stack.pop().isFalsey():
2022-01-27 01:36:43 +01:00
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-29 20:43:13 +01:00
of opFunctionDef:
let offset = readDU8()
let faddr: ptr uint8 = ip
ip = ip.padd(offset)
stack.push(faddr.fromFunct())
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-28 04:17:11 +01:00
let funct = stack.getIndexNeg(argcount)
2022-01-29 05:12:33 +01:00
if not funct.isFunct():
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-29 05:12:33 +01:00
stack.setIndexNeg(argcount, fromNil()) # 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-29 20:43:13 +01:00
ip = funct.asFunct() # jump to the entry point
2022-02-03 03:18:11 +01:00
of opCreateList:
let listLen = readDU8()
if listLen == 0:
stack.push(newList[NdValue]().fromList())
else:
let start = stack.getIndexNeg(listLen - 1).addr
var list = newListCopymem[NdValue](start, listLen)
2022-02-05 03:24:30 +01:00
stack.deleteTopN(listLen)
stack.push(list.fromList())
2022-02-03 03:18:11 +01:00
of opCreateTable:
let tblLen = readDU8()
var tbl = newNdTable[NdValue, NdValue](tblLen)
for i in countup(0, tblLen - 1):
2022-02-03 03:18:11 +01:00
let val = stack.pop()
let key = stack.pop()
if tbl[].tableSet(key, val):
runtimeError("Attempt to redefine an existing value inside table declaration.")
break
2022-02-05 03:24:30 +01:00
stack.deleteTopN(tblLen * 2)
2022-02-03 03:18:11 +01:00
stack.push(tbl.fromTable())
of opLen:
let res = stack.peek().getLength()
if not res.ok:
runtimeError(res.msg)
break
of opGetIndex:
let index = stack.pop()
2022-02-05 02:45:29 +01:00
let res = stack.peek().getIndex(index) # getIndex modifies the top value of the stack
2022-02-03 03:18:11 +01:00
if not res.ok:
runtimeError(res.msg)
break
of opSetIndex:
let value = stack.pop()
let index = stack.pop()
let res = stack.peek().setIndex(index, value)
if not res.ok:
runtimeError(res.msg)
break
2022-02-03 22:28:25 +01:00
of opChr:
2022-02-05 02:45:29 +01:00
let val = stack.peek()
if not val.isFloat():
runtimeError("chr on not float")
break
let floatval = val.asFloat()
if floatval > 255f or floatval < 0f:
runtimeError("chr on a float out of range")
2022-02-03 22:28:25 +01:00
break
2022-02-05 02:45:29 +01:00
let chr = floatval.char()
stack.settip((chr).newString().fromNdString())
2022-02-03 22:28:25 +01:00
of opInt:
2022-02-05 02:45:29 +01:00
let val = stack.peek()
2022-02-03 22:28:25 +01:00
if not val.isString():
runtimeError("int on non string")
break
2022-02-05 02:45:29 +01:00
let strval = val.asString()
if strval.getLength() == 0:
runtimeError("int on empty string")
break
let code = val.asString().getIndexAsChar(0).float()
stack.settip(code.fromFloat())
of opPutchar:
write stdout, $stack.peek()
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
2022-01-28 04:17:11 +01:00
let times = runcounts[op]
echo &"OpCode: {op} total duration {dur} ms {times} times"
2022-01-28 22:00:21 +01:00
stack.free()
frames.free()
2022-01-29 06:12:08 +01:00
globals.free()
2022-01-22 17:28:53 +01:00
2022-01-27 01:36:43 +01:00
if hadError:
2022-01-20 21:54:11 +01:00
irRuntimeError
else:
irOK