Moved the templates inside vm.run into separate procedures. Fixed a couple of logical mistakes and renamed SliceRange to Slice and Slice to GetItem. Added the cache optimization to other singletons (nil, nan, inf)

This commit is contained in:
nocturn9x 2020-12-21 21:53:45 +01:00
parent 18823ccf0d
commit d27cc60891
3 changed files with 130 additions and 105 deletions

View File

@ -329,19 +329,24 @@ proc bracketAssign(self: ref Compiler, canAssign: bool) =
proc bracket(self: ref Compiler, canAssign: bool) =
## Parses slice expressions, such as "hello"[0].
## Slice can take up to two arguments, a start
## Parses getitem/slice expressions, such as "hello"[0:1]
## or someList[5]. Slices can take up to two arguments, a start
## and an end index in the chosen iterable.
## Both arguments are optional, so doing "hi"[::]
## will basically copy the string into a new object.
## Indexes start from 0, and while the start index is
## inclusive, the end index is not. If an end index is
## not specified like this "hello"[0:], then the it is
## not specified--like in "hello"[0:]--, then the it is
## assumed to be the length of the iterable. Likewise,
## if the start index is missing, it is assumed to be 0.
## Like in Python, using an end index that's out of bounds
## will not raise an error. Doing "hello"[0:999] will just
## return the whole string instead
## return the whole string instead.
## It has to be noted that negative indexes are allowed: -1
## means the last element in the iterable, -2 the element
## before that and so on, but that if a negative index's value
## goes back too far it does NOT loop back to the end of the
## iterable and will cause an IndexError at runtime instead
if self.parser.peek.kind == TokenType.COLON:
self.emitByte(OpCode.Nil)
discard self.parser.advance()
@ -349,18 +354,18 @@ proc bracket(self: ref Compiler, canAssign: bool) =
self.emitByte(OpCode.Nil)
else:
self.parsePrecedence(Precedence.Term)
self.emitByte(OpCode.SliceRange)
self.emitByte(OpCode.Slice)
else:
self.parsePrecedence(Precedence.Term)
if self.parser.peek().kind == TokenType.RS:
self.emitByte(OpCode.Slice)
self.emitByte(OpCode.GetItem)
elif self.parser.peek().kind == TokenType.COLON:
discard self.parser.advance()
if self.parser.peek().kind == TokenType.RS:
self.emitByte(OpCode.Nil)
else:
self.parsePrecedence(Precedence.Term)
self.emitByte(OpCode.SliceRange)
self.emitByte(OpCode.Slice)
if self.parser.peek().kind == TokenType.EQ:
discard self.parser.advance()
self.parsePrecedence(Precedence.Term)

View File

@ -37,8 +37,8 @@ type
Less,
Equal,
Not,
GetItem,
Slice,
SliceRange,
Pop,
DefineGlobal,
GetGlobal,
@ -69,8 +69,8 @@ const simpleInstructions* = {OpCode.Return, OpCode.Add, OpCode.Multiply,
OpCode.True, OpCode.False, OpCode.Nan,
OpCode.Inf, OpCode.Shl, OpCode.Shr,
OpCode.Xor, OpCode.Not, OpCode.Equal,
OpCode.Greater, OpCode.Less, OpCode.Slice,
OpCode.SliceRange, OpCode.Pop, OpCode.DefineGlobal,
OpCode.Greater, OpCode.Less, OpCode.GetItem,
OpCode.Slice, OpCode.Pop, OpCode.DefineGlobal,
OpCode.GetGlobal, OpCode.SetGlobal,
OpCode.DeleteGlobal}
const constantInstructions* = {OpCode.Constant}

View File

@ -17,8 +17,6 @@
import algorithm
import strformat
import math
import lenientops
import config
import compiler
import tables
@ -31,8 +29,6 @@ when DEBUG_TRACE_VM:
import util/debug
## Move these into appropriate int/float modules
type
KeyboardInterrupt* = object of CatchableError
## Custom exception to handle Ctrl+C
@ -52,7 +48,7 @@ type
stackTop*: int
objects*: seq[ptr Obj]
globals*: Table[string, ptr Obj]
cached: array[2, ptr Obj]
cached: array[5, ptr Obj]
file*: string
@ -80,7 +76,6 @@ proc getBoolean(self: var VM, kind: bool): ptr Obj =
return self.cached[1]
proc error*(self: var VM, error: ptr JAPLException) =
## Reports runtime errors with a nice traceback
# TODO: Exceptions
@ -238,50 +233,60 @@ proc callObject(self: var VM, callee: ptr Obj, argCount: uint8): bool =
return false
proc readByte(self: CallFrame): byte =
## Reads a single byte from the given
## frame's chunk of bytecode
inc(self.ip)
result = self.function.chunk.code[self.ip - 1]
proc readBytes(self: CallFrame): int =
## Reads and decodes 3 bytes from the
## given frame's chunk into an integer
var arr = [self.readByte(), self.readByte(), self.readByte()]
var index: int
copyMem(index.addr, unsafeAddr(arr), sizeof(arr))
result = index
proc readShort(self: CallFrame): uint16 =
## Reads a 16 bit number from the
## given frame's chunk
inc(self.ip)
inc(self.ip)
cast[uint16]((self.function.chunk.code[self.ip - 2] shl 8) or self.function.chunk.code[self.ip - 1])
proc readConstant(self: CallFrame): ptr Obj =
## Reads a constant from the given
## frame's constant table
result = self.function.chunk.consts[int(self.readByte())]
proc readLongConstant(self: CallFrame): ptr Obj =
## Reads a long constant from the
## given frame's constant table
var arr = [self.readByte(), self.readByte(), self.readByte()]
var idx: int
copyMem(idx.addr, unsafeAddr(arr), sizeof(arr))
result = self.function.chunk.consts[idx]
proc run(self: var VM, repl: bool): InterpretResult =
## Chews trough bytecode instructions executing
## them one at a time, this is the runtime's
## them one at a time: this is the runtime's
## main loop
var frame = self.frames[self.frameCount - 1]
template readByte: untyped =
## Reads a single byte from the current
## frame's chunk of bytecode
inc(frame.ip)
frame.function.chunk.code[frame.ip - 1]
template readBytes: untyped =
## Reads and decodes 3 bytes from the
## current frame's chunk into an integer
var arr = [readByte(), readByte(), readByte()]
var index: int
copyMem(index.addr, unsafeAddr(arr), sizeof(arr))
index
template readShort: untyped =
## Reads a 16 bit number from the
## current frame's chunk
inc(frame.ip)
inc(frame.ip)
cast[uint16]((frame.function.chunk.code[frame.ip - 2] shl 8) or frame.function.chunk.code[frame.ip - 1])
template readConstant: ptr Obj =
## Reads a constant from the current
## frame's constant table
frame.function.chunk.consts[int(readByte())]
template readLongConstant: ptr Obj =
## Reads a long constant from the
## current frame's constant table
var arr = [readByte(), readByte(), readByte()]
var idx: int
copyMem(idx.addr, unsafeAddr(arr), sizeof(arr))
frame.function.chunk.consts[idx]
var instruction: uint8
var opcode: OpCode
var stackOffset: int = 2
while true:
{.computedgoto.} # See https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma
instruction = readByte()
instruction = frame.readByte()
opcode = OpCode(instruction)
## This offset dictates how the call frame behaves when converting
## relative opcode indexes to absolute stack indexes, since the behavior
## in the function local vs. global/scope-local scope is different
## relative frame indexes to absolute stack indexes, since the behavior
## in function local vs. global/scope-local scope is different
if frame.function.name == nil:
stackOffset = 2
else:
@ -313,9 +318,9 @@ proc run(self: var VM, repl: bool): InterpretResult =
discard disassembleInstruction(frame.function.chunk, frame.ip - 1)
case opcode: # Main OpCodes dispatcher
of OpCode.Constant:
self.push(readConstant())
self.push(frame.readConstant())
of OpCode.ConstantLong:
self.push(readLongConstant())
self.push(frame.readLongConstant())
of OpCode.Negate:
try:
self.push(self.pop().negate())
@ -323,34 +328,34 @@ proc run(self: var VM, repl: bool): InterpretResult =
self.error(newTypeError(getCurrentExceptionMsg()))
return RuntimeError
of OpCode.Shl: # Bitwise left-shift
var left = self.pop()
var right = self.pop()
var left = self.pop()
try:
self.push(right.binaryShl(left))
self.push(left.binaryShl(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
return RuntimeError
of OpCode.Shr: # Bitwise right-shift
var left = self.pop()
var right = self.pop()
var left = self.pop()
try:
self.push(right.binaryShr(left))
self.push(left.binaryShr(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
return RuntimeError
of OpCode.Xor: # Bitwise xor
var left = self.pop()
var right = self.pop()
var left = self.pop()
try:
self.push(right.binaryXor(left))
self.push(left.binaryXor(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
return RuntimeError
of OpCode.Bor: # Bitwise or
var left = self.pop()
var right = self.pop()
var left = self.pop()
try:
self.push(right.binaryOr(left))
self.push(left.binaryOr(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
return RuntimeError
@ -361,58 +366,58 @@ proc run(self: var VM, repl: bool): InterpretResult =
self.error(newTypeError(getCurrentExceptionMsg()))
return RuntimeError
of OpCode.Band: # Bitwise and
var left = self.pop()
var right = self.pop()
var left = self.pop()
try:
self.push(right.binaryAnd(left))
self.push(left.binaryAnd(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
return RuntimeError
of OpCode.Add:
var left = self.pop()
var right = self.pop()
var left = self.pop()
try:
self.push(right.sum(left))
self.push(left.sum(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
return RuntimeError
of OpCode.Subtract:
var left = self.pop()
var right = self.pop()
var left = self.pop()
try:
self.push(right.sub(left))
self.push(left.sub(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
return RuntimeError
of OpCode.Divide:
var left = self.pop()
var right = self.pop()
var left = self.pop()
try:
self.push(right.trueDiv(left))
self.push(left.trueDiv(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
return RuntimeError
of OpCode.Multiply:
var left = self.pop()
var right = self.pop()
var left = self.pop()
try:
self.push(right.mul(left))
self.push(left.mul(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
return RuntimeError
of OpCode.Mod:
var left = self.pop()
var right = self.pop()
var left = self.pop()
try:
self.push(right.divMod(left))
self.push(left.divMod(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
return RuntimeError
of OpCode.Pow:
var left = self.pop()
var right = self.pop()
var left = self.pop()
try:
self.push(right.pow(left))
self.push(left.pow(right))
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
return RuntimeError
@ -421,11 +426,11 @@ proc run(self: var VM, repl: bool): InterpretResult =
of OpCode.False:
self.push(cast[ptr Bool](self.getBoolean(false)))
of OpCode.Nil:
self.push(asNil())
self.push(cast[ptr Nil](self.cached[2]))
of OpCode.Nan:
self.push(asNan())
self.push(cast[ptr NotANumber](self.cached[4]))
of OpCode.Inf:
self.push(asInf())
self.push(cast[ptr Infinity](self.cached[3]))
of OpCode.Not:
self.push(self.pop().isFalsey().asBool())
of OpCode.Equal:
@ -435,43 +440,44 @@ proc run(self: var VM, repl: bool): InterpretResult =
# Doesn't this chain of calls look beautifully
# intuitive?
of OpCode.Less:
var left = self.pop()
var right = self.pop()
var left = self.pop()
try:
self.push(right.lt(left).asBool())
self.push(left.lt(right).asBool())
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
return RuntimeError
of OpCode.Greater:
var left = self.pop()
var right = self.pop()
var left = self.pop()
try:
self.push(right.gt(left).asBool())
self.push(left.gt(right).asBool())
except NotImplementedError:
self.error(newTypeError(getCurrentExceptionMsg()))
return RuntimeError
of OpCode.Slice:
of OpCode.GetItem:
# TODO: More generic method
if not self.slice():
return RuntimeError
of OpCode.SliceRange:
of OpCode.Slice:
if not self.sliceRange():
return RuntimeError
of OpCode.DefineGlobal:
if frame.function.chunk.consts.len > 255:
self.globals[readLongConstant().toStr()] = self.peek(0)
self.globals[frame.readLongConstant().toStr()] = self.peek(0)
else:
self.globals[readConstant().toStr()] = self.peek(0)
discard self.pop() # This will help when we have a custom GC
self.globals[frame.readConstant().toStr()] = self.peek(0)
discard self.pop()
of OpCode.GetGlobal:
if frame.function.chunk.consts.len > 255:
var constant = readLongConstant().toStr()
var constant = frame.readLongConstant().toStr()
if constant notin self.globals:
self.error(newReferenceError(&"undefined name '{constant}'"))
return RuntimeError
else:
self.push(self.globals[constant])
else:
var constant = readConstant().toStr()
var constant = frame.readConstant().toStr()
if constant notin self.globals:
self.error(newReferenceError(&"undefined name '{constant}'"))
return RuntimeError
@ -479,30 +485,31 @@ proc run(self: var VM, repl: bool): InterpretResult =
self.push(self.globals[constant])
of OpCode.SetGlobal:
if frame.function.chunk.consts.len > 255:
var constant = readLongConstant().toStr()
var constant = frame.readLongConstant().toStr()
if constant notin self.globals:
self.error(newReferenceError(&"assignment to undeclared name '{constant}'"))
return RuntimeError
else:
self.globals[constant] = self.peek(0)
else:
var constant = readConstant().toStr()
var constant = frame.readConstant().toStr()
if constant notin self.globals:
self.error(newReferenceError(&"assignment to undeclared name '{constant}'"))
return RuntimeError
else:
self.globals[constant] = self.peek(0)
discard self.pop()
of OpCode.DeleteGlobal:
# This OpCode, as well as DeleteLocal, is currently unused due to potential issues with the GC
# This opcode, as well as DeleteLocal, is currently unused due to potential issues with the GC
if frame.function.chunk.consts.len > 255:
var constant = readLongConstant().toStr()
var constant = frame.readLongConstant().toStr()
if constant notin self.globals:
self.error(newReferenceError(&"undefined name '{constant}'"))
return RuntimeError
else:
self.globals.del(constant)
else:
var constant = readConstant().toStr()
var constant = frame.readConstant().toStr()
if constant notin self.globals:
self.error(newReferenceError(&"undefined name '{constant}'"))
return RuntimeError
@ -510,34 +517,35 @@ proc run(self: var VM, repl: bool): InterpretResult =
self.globals.del(constant)
of OpCode.GetLocal:
if frame.len > 255:
self.push(frame[readBytes(), stackOffset])
self.push(frame[frame.readBytes(), stackOffset])
else:
self.push(frame[int readByte(), stackOffset])
self.push(frame[int frame.readByte(), stackOffset])
of OpCode.SetLocal:
if frame.len > 255:
frame[readBytes(), stackOffset] = self.peek(0)
frame[frame.readBytes(), stackOffset] = self.peek(0)
else:
frame[int readByte(), stackOffset] = self.peek(0)
frame[int frame.readByte(), stackOffset] = self.peek(0)
discard self.pop()
of OpCode.DeleteLocal:
# Unused due to GC potential issues
if frame.len > 255:
var slot = readBytes()
var slot = frame.readBytes()
frame.delete(slot, stackOffset)
else:
var slot = readByte()
var slot = frame.readByte()
frame.delete(int slot, stackOffset)
of OpCode.Pop:
self.lastPop = self.pop()
of OpCode.JumpIfFalse:
let jmpOffset = int readShort()
let jmpOffset = int frame.readShort()
if isFalsey(self.peek(0)):
frame.ip += int jmpOffset
of OpCode.Jump:
frame.ip += int readShort()
frame.ip += int frame.readShort()
of OpCode.Loop:
frame.ip -= int readShort()
frame.ip -= int frame.readShort()
of OpCode.Call:
var argCount = readByte()
var argCount = frame.readByte()
if not self.callObject(self.peek(int argCount), argCount):
return RuntimeError
frame = self.frames[self.frameCount - 1]
@ -546,7 +554,7 @@ proc run(self: var VM, repl: bool): InterpretResult =
of OpCode.Return:
var retResult = self.pop()
if repl:
if not self.lastPop.isNil() and self.frameCount == 1: # This is to avoid
if not self.lastPop.isNil() and self.frameCount == 1:
# This avoids unwanted output with recursive calls
echo stringify(self.lastPop)
self.lastPop = asNil()
@ -583,13 +591,13 @@ proc freeObject(obj: ptr Obj) =
proc freeObjects(self: var VM) =
## Frees all the allocated objects
## from the VM
var objCount = len(self.objects)
for obj in reversed(self.objects):
freeObject(obj)
discard self.objects.pop()
for cached_obj in self.cached:
freeObject(cached_obj)
when DEBUG_TRACE_ALLOCATION:
var objCount = len(self.objects)
echo &"DEBUG: Freed {objCount} objects"
@ -605,11 +613,23 @@ proc freeVM*(self: var VM) =
quit(71)
proc initVMCache: array[5, ptr Obj] =
## Initializes the static cache for singletons
## such as nil, true, false and nan
return [cast[ptr Obj](true.asBool()),
cast[ptr Obj](false.asBool()),
cast[ptr Obj](asNil()),
cast[ptr Obj](asInf()),
cast[ptr Obj](asNan())]
proc initVM*(): VM =
## Initializes the VM
setControlCHook(handleInterrupt)
var globals: Table[string, ptr Obj] = initTable[string, ptr Obj]()
result = VM(lastPop: asNil(), objects: @[], globals: globals, cached: [cast[ptr Obj](true.asBool()), cast[ptr Obj](false.asBool())], source: "", file: "")
let cache = initVMCache()
result = VM(lastPop: asNil(), objects: @[], globals: globals, cached: cache, source: "", file: "")
proc interpret*(self: var VM, source: string, repl: bool = false, file: string): InterpretResult =