Various fixes to stack frame alignment and added incremental compilation to REPL

This commit is contained in:
Mattia Giambirtone 2022-05-30 12:31:15 +02:00
parent 0887dab246
commit df105125c4
5 changed files with 70 additions and 16 deletions

View File

@ -21,6 +21,8 @@ import ../frontend/meta/bytecode
import ../util/multibyte
export types
type
PeonVM* = ref object
## The Peon Virtual Machine
@ -268,7 +270,7 @@ proc dispatch*(self: PeonVM) =
of Pop:
self.lastPop = self.pop()
of PopN:
for _ in 0..<int(self.readLong()):
for _ in 0..<int(self.readShort()):
discard self.pop()
of Jump:
self.ip = int(self.readShort())
@ -319,4 +321,5 @@ proc run*(self: PeonVM, chunk: Chunk) =
self.frames = @[0]
self.stack = @[]
self.ip = 0
self.lastPop = self.getNil()
self.dispatch()

View File

@ -27,7 +27,7 @@ when len(PEON_COMMIT_HASH) != 40:
const PEON_BRANCH* = "master"
when len(PEON_BRANCH) > 255:
{.fatal: "The git branch name's length must be less than or equal to 255 characters".}
const DEBUG_TRACE_VM* = false # Traces VM execution
const DEBUG_TRACE_VM* = true # Traces VM execution
const DEBUG_TRACE_GC* = false # Traces the garbage collector (TODO)
const DEBUG_TRACE_ALLOCATION* = false # Traces memory allocation/deallocation
const DEBUG_TRACE_COMPILER* = false # Traces the compiler

View File

@ -403,7 +403,7 @@ proc detectClosureVariable(self: Compiler, name: Name,
## unpredictably or crash
if name == nil:
return
if name.depth < depth:
if name.depth > 0 and name.depth < depth:
# Ding! The given name is closed over: we need to
# change the NoOp instructions that self.declareName
# put in place for us into a StoreHeap. We don't need to change
@ -595,7 +595,11 @@ proc inferType(self: Compiler, node: Expression): Type =
var node = CallExpr(node)
case node.callee.kind:
of identExpr:
result = self.resolve(IdentExpr(node.callee)).valueType.returnType
let resolved = self.resolve(IdentExpr(node.callee))
if resolved != nil:
result = resolved.valueType.returnType
else:
result = nil
of lambdaExpr:
result = self.inferType(LambdaExpr(node.callee).returnType)
else:
@ -962,7 +966,9 @@ proc identifier(self: Compiler, node: IdentExpr) =
if not t.closedOver:
# Static name resolution, loads value at index in the stack. Very fast. Much wow.
self.emitByte(LoadVar)
self.emitBytes((index - self.frames[self.scopeDepth]).toTriple())
if self.frames.len() > 1:
inc(index, 2)
self.emitBytes((index - self.frames[^1]).toTriple())
else:
# Heap-allocated closure variable. Stored in a separate "closure array" in the VM that does not have stack semantics.
# This makes closures work as expected and is not much slower than indexing our stack (since they're both
@ -1019,7 +1025,7 @@ proc endScope(self: Compiler) =
var names: seq[Name] = @[]
for name in self.names:
if name.depth > self.scopeDepth:
if name.valueType.kind != Function and OpCode(self.chunk.code[name.codePos]) == NoOp:
if name.valueType.kind != Function and OpCode(self.chunk.code[name.codePos]) == NoOp:
for _ in countup(0, 3):
# Since by deleting it the size of the
# sequence decreases, we don't need to
@ -1369,10 +1375,11 @@ proc funDecl(self: Compiler, node: FunDecl) =
## Compiles function declarations
# A function's code is just compiled linearly
# and then jumped over
self.emitByte(LoadNil) # Aligns the stack
let jmp = self.emitJump(JumpForwards)
var function = self.currentFunction
self.declareName(node)
self.frames.add(self.names.high())
self.frames.add(self.names.len())
# TODO: Forward declarations
if node.body != nil:
if BlockStmt(node.body).code.len() == 0:
@ -1384,7 +1391,7 @@ proc funDecl(self: Compiler, node: FunDecl) =
# the same function with the same name! Error!
var msg = &"multiple matching implementations of '{node.name.token.lexeme}' found:\n"
for fn in reversed(impl):
msg &= &"- '{fn.name}' at line {fn.line} of type {self.typeToStr(fn.valueType)}\n"
msg &= &"- '{fn.name.token.lexeme}' at line {fn.line} of type {self.typeToStr(fn.valueType)}\n"
self.error(msg)
# We store the current function
self.currentFunction = node
@ -1490,8 +1497,9 @@ proc compile*(self: Compiler, ast: seq[Declaration], file: string): Chunk =
self.declaration(Declaration(self.step()))
if self.ast.len() > 0:
# *Technically* an empty program is a valid program
self.endScope()
self.emitByte(OpCode.Return) # Exits the VM's main loop when used at the global scope
result = self.chunk
if self.ast.len() > 0 and self.scopeDepth != 0:
self.error(&"invalid state: invalid scopeDepth value (expected 0, got {self.scopeDepth}), did you forget to call endScope/beginScope?")
self.endScope()
if self.ast.len() > 0 and self.scopeDepth != -1:
self.error(&"invalid state: invalid scopeDepth value (expected -1, got {self.scopeDepth}), did you forget to call endScope/beginScope?")
echo self.chunk.code

View File

@ -53,20 +53,33 @@ proc repl =
vm = newPeonVM()
editor = getLineEditor()
input: string
current: string
tokenizer.fillSymbolTable()
editor.bindEvent(jeQuit):
stdout.styledWriteLine(fgGreen, "Goodbye!")
editor.prompt = ""
keep = false
input = ""
editor.bindKey("ctrl+a"):
editor.content.home()
editor.bindKey("ctrl+e"):
editor.content.`end`()
while keep:
try:
input = editor.read()
if input.len() == 0:
# We incrementally add content to the input
# so that you can, for example, define a function
# then press enter and use it at the next iteration
# of the read loop
current = editor.read()
if current.len() == 0:
continue
elif current == "#clearInput":
input = ""
continue
elif current == "#clear":
stdout.write("\x1Bc")
continue
input &= &"\n{current}"
tokens = tokenizer.lex(input, "stdin")
if tokens.len() == 0:
continue
@ -123,8 +136,35 @@ proc repl =
when debugRuntime:
styledEcho fgCyan, "\n\nExecution step: "
vm.run(serialized.chunk)
echo vm.lastPop
var popped = vm.lastPop
case popped.kind:
of Int64:
echo &"{popped.long}'i64"
of UInt64:
echo &"{popped.uLong}'u64"
of Int32:
echo &"{popped.`int`}'i32"
of UInt32:
echo &"{popped.uInt}'u32"
of Int16:
echo &"{popped.short}'i16"
of UInt16:
echo &"{popped.uShort}'u16"
of Int8:
echo &"{popped.tiny}'i8"
of UInt8:
echo &"{popped.uTiny}'u8"
of ObjectKind.Inf:
if popped.positive:
echo "inf"
else:
echo "-inf"
of ObjectKind.Nan, Nil:
echo ($popped.kind).toLowerAscii()
else:
discard
except LexingError:
input = ""
let exc = LexingError(getCurrentException())
let relPos = tokenizer.getRelPos(exc.line)
let line = tokenizer.getSource().splitLines()[exc.line - 1].strip()
@ -134,6 +174,7 @@ proc repl =
styledEcho fgBlue, "Source line: " , fgDefault, line
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
except ParseError:
input = ""
let exc = ParseError(getCurrentException())
let lexeme = exc.token.lexeme
let lineNo = exc.token.line
@ -149,6 +190,7 @@ proc repl =
styledEcho fgBlue, "Source line: " , fgDefault, line
styledEcho fgCyan, " ".repeat(len("Source line: ")) & "^".repeat(relPos.stop - relPos.start)
except CompileError:
input = ""
let exc = CompileError(getCurrentException())
let lexeme = exc.node.token.lexeme
let lineNo = exc.node.token.line

View File

@ -63,7 +63,8 @@ proc printInstruction(instruction: OpCode, newline: bool = false) =
proc checkFrameStart(self: Debugger, n: int) =
return
## Todo: Checking frame end offsets needs
## fixes
for i, e in self.cfiData:
if n == e.start and not (e.started or e.stopped):
e.started = true
@ -131,7 +132,7 @@ proc callInstruction(self: Debugger, instruction: OpCode) =
printInstruction(instruction)
stdout.styledWrite(fgGreen, &", jumps to address ", fgYellow, $slot, fgGreen, " with ", fgYellow, $args, fgGreen, " argument")
if args > 1:
stdout.styledWrite(fgYellow, "s")
stdout.styledWrite(fgGreen, "s")
nl()
self.current += 7