diff --git a/src/backend/vm.nim b/src/backend/vm.nim index 8e7f675..d4c7652 100644 --- a/src/backend/vm.nim +++ b/src/backend/vm.nim @@ -40,6 +40,9 @@ when debugVM or debugMem or debugGC: import std/sequtils import std/terminal +when debugVM: + proc clearerr(stream: File) {.header: "stdio.h", importc.} + type PeonVM* = ref object @@ -53,20 +56,22 @@ type ## value represents a pointer to ## some stack- or heap-allocated ## object. The VM has no concept - ## of type by itself and it relies - ## on the compiler to produce the - ## correct results - ip: uint64 # Instruction pointer - chunk: Chunk # Piece of bytecode to execute - calls: seq[uint64] # Our call stack - operands: seq[uint64] # Our operand stack - cache: array[6, uint64] # Singletons cache + ## of type by itself: everything + ## is lost after the compilation + ## phase + ip: uint64 # The instruction pointer + chunk: Chunk # The chunk of bytecode to execute + calls: seq[uint64] # The call stack + operands: seq[uint64] # The operand stack + cache: array[6, uint64] # The singletons cache frames: seq[uint64] # Stores the bottom of stack frames - closures: seq[uint64] # Stores closure offsets - envs: seq[uint64] # Stores variables that do not have stack semantics - results: seq[uint64] # Stores function's results (return values) - gc: PeonGC # Our memory manager + closures: seq[uint64] # Stores closure environment offsets + envs: seq[uint64] # Stores closure variables + results: seq[uint64] # Stores function return values + gc: PeonGC # A reference to the VM's garbage collector breakpoints: seq[uint64] # Breakpoints where we call our debugger + debugNext: bool # Whether to debug the next instruction + lastDebugCommand: string # The last debugging command input by the user ObjectKind* = enum ## A tag for heap-allocated ## peon objects @@ -198,7 +203,7 @@ proc markRoots(self: PeonGC): seq[ptr HeapObject] = echo "DEBUG - GC: Starting mark phase" # Unlike what bob does in his book, # we keep track of objects in a different - # way due to how the whole thing is designed. + # way due to the difference of our design. # Specifically, we don't have neat structs for # all peon objects: When we allocate() an object, # we keep track of the small wrapper it created @@ -251,6 +256,7 @@ proc trace(self: PeonGC, roots: seq[ptr HeapObject]) = ## objects when debugGC: echo &"DEBUG - GC: Tracing indirect references from {len(roots)} roots" + var count = 0 for root in roots: case root.kind: of String: @@ -258,7 +264,7 @@ proc trace(self: PeonGC, roots: seq[ptr HeapObject]) = else: discard # TODO: Other types when debugGC: - echo &"DEBUG - GC: Tracing phase complete" + echo &"DEBUG - GC: Traced {count} indirect references" proc free(self: PeonGC, obj: ptr HeapObject) = @@ -332,6 +338,8 @@ proc collect(self: PeonGC) = echo &"DEBUG - GC: Next cycle at {self.nextGC} bytes" +# Implementation of the peon VM + proc initCache*(self: PeonVM) = ## Initializes the VM's ## singletons cache @@ -633,55 +641,84 @@ when debugVM: # So nim shuts up ## debugger styledEcho fgMagenta, "IP: ", fgYellow, &"{self.ip}" styledEcho fgBlue, "Instruction: ", fgRed, &"{OpCode(self.chunk.code[self.ip])} (", fgYellow, $self.chunk.code[self.ip], fgRed, ")" - if self.calls.len() !> 0: - stdout.styledWrite(fgGreen, "Call Stack: ", fgMagenta, "[") - for i, e in self.calls: - stdout.styledWrite(fgYellow, $e) - if i < self.calls.high(): - stdout.styledWrite(fgYellow, ", ") - styledEcho fgMagenta, "]" - if self.operands.len() !> 0: - stdout.styledWrite(fgBlue, "Operand Stack: ", fgMagenta, "[") - for i, e in self.operands: - stdout.styledWrite(fgYellow, $e) - if i < self.operands.high(): - stdout.styledWrite(fgYellow, ", ") - styledEcho fgMagenta, "]" - if self.frames.len() !> 0: - stdout.styledWrite(fgCyan, "Current Frame: ", fgMagenta, "[") - for i, e in self.calls[self.frames[^1]..^1]: - stdout.styledWrite(fgYellow, $e) - if i < (self.calls.high() - self.frames[^1].int): - stdout.styledWrite(fgYellow, ", ") - styledEcho fgMagenta, "]", fgCyan - stdout.styledWrite(fgRed, "Live stack frames: ", fgMagenta, "[") - for i, e in self.frames: - stdout.styledWrite(fgYellow, $e) - if i < self.frames.high(): - stdout.styledWrite(fgYellow, ", ") - styledEcho fgMagenta, "]" - if self.closures.len() !> 0: - stdout.styledWrite(fgBlue, "Closure offsets: ", fgMagenta, "[") - for i, e in self.closures: - stdout.styledWrite(fgYellow, $e) - if i < self.closures.high(): - stdout.styledWrite(fgYellow, ", ") - styledEcho fgMagenta, "]" - if self.envs.len() !> 0: - stdout.styledWrite(fgGreen, "Environments: ", fgMagenta, "[") - for i, e in self.envs: - stdout.styledWrite(fgYellow, $e) - if i < self.envs.high(): - stdout.styledWrite(fgYellow, ", ") - styledEcho fgMagenta, "]" - if self.results.len() !> 0: - stdout.styledWrite(fgYellow, "Function Results: ", fgMagenta, "[") - for i, e in self.results: - stdout.styledWrite(fgYellow, $e) - if i < self.results.high(): - stdout.styledWrite(fgYellow, ", ") - styledEcho fgMagenta, "]" - discard readLine stdin + var command = "" + while true: + stdout.styledWrite(fgGreen, "=> ") + stdout.flushFile() + try: + command = readLine(stdin) + except EOFError: + styledEcho(fgYellow, "Use Ctrl+C to exit") + clearerr(stdin) + break + except IOError: + styledEcho(fgRed, "An error occurred while reading command: ", fgYellow, getCurrentExceptionMsg()) + break + if command == "": + if self.lastDebugCommand == "": + command = "n" + else: + command = self.lastDebugCommand + case command: + of "n", "next": + self.debugNext = true + break + of "continue": + self.debugNext = false + break + of "s", "stack": + stdout.styledWrite(fgGreen, "Call Stack: ", fgMagenta, "[") + for i, e in self.calls: + stdout.styledWrite(fgYellow, $e) + if i < self.calls.high(): + stdout.styledWrite(fgYellow, ", ") + styledEcho fgMagenta, "]" + of "o", "operands": + stdout.styledWrite(fgBlue, "Operand Stack: ", fgMagenta, "[") + for i, e in self.operands: + stdout.styledWrite(fgYellow, $e) + if i < self.operands.high(): + stdout.styledWrite(fgYellow, ", ") + styledEcho fgMagenta, "]" + of "f", "frame": + stdout.styledWrite(fgCyan, "Current Frame: ", fgMagenta, "[") + for i, e in self.calls[self.frames[^1]..^1]: + stdout.styledWrite(fgYellow, $e) + if i < (self.calls.high() - self.frames[^1].int): + stdout.styledWrite(fgYellow, ", ") + styledEcho fgMagenta, "]", fgCyan + of "frames": + stdout.styledWrite(fgRed, "Live stack frames: ", fgMagenta, "[") + for i, e in self.frames: + stdout.styledWrite(fgYellow, $e) + if i < self.frames.high(): + stdout.styledWrite(fgYellow, ", ") + styledEcho fgMagenta, "]" + of "cl", "closures": + stdout.styledWrite(fgBlue, "Closure offsets: ", fgMagenta, "[") + for i, e in self.closures: + stdout.styledWrite(fgYellow, $e) + if i < self.closures.high(): + stdout.styledWrite(fgYellow, ", ") + styledEcho fgMagenta, "]" + of "e", "env", "environments": + stdout.styledWrite(fgGreen, "Environments: ", fgMagenta, "[") + for i, e in self.envs: + stdout.styledWrite(fgYellow, $e) + if i < self.envs.high(): + stdout.styledWrite(fgYellow, ", ") + styledEcho fgMagenta, "]" + of "r", "results": + stdout.styledWrite(fgYellow, "Function Results: ", fgMagenta, "[") + for i, e in self.results: + stdout.styledWrite(fgYellow, $e) + if i < self.results.high(): + stdout.styledWrite(fgYellow, ", ") + styledEcho fgMagenta, "]" + of "clear": + stdout.write("\x1Bc") + else: + styledEcho(fgRed, "Unknown command ", fgYellow, &"'{command}'") proc dispatch*(self: PeonVM) = @@ -690,7 +727,7 @@ proc dispatch*(self: PeonVM) = while true: {.computedgoto.} # https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma when debugVM: - if self.ip in self.breakpoints or self.breakpoints.len() == 0: + if self.ip in self.breakpoints or self.breakpoints.len() == 0 or self.debugNext: self.debug() instruction = OpCode(self.readByte()) case instruction: @@ -1046,6 +1083,7 @@ proc run*(self: PeonVM, chunk: Chunk, breakpoints: seq[uint64] = @[]) = self.breakpoints = breakpoints self.results = @[] self.ip = 0 + self.lastDebugCommand = "" try: self.dispatch() except NilAccessDefect: diff --git a/src/frontend/compiler.nim b/src/frontend/compiler.nim index 70cb979..f95e926 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -205,7 +205,7 @@ type # Stores the position of all jumps jumps: seq[tuple[patched: bool, offset: int]] # List of CFI start offsets into our CFI data - cfiOffsets: seq[tuple[start, stop: int, fn: Name]] + cfiOffsets: seq[tuple[start, stop, pos: int, fn: Name]] # We store these objects to compile modules lexer: Lexer parser: Parser @@ -450,20 +450,22 @@ proc fixCFIOffsets(self: Compiler, oldLen: int, modifiedAt: int) = let offset = self.chunk.code.len() - oldLen var newCFI: array[3, uint8] var tmp: int + var i = 0 for cfi in self.cfiOffsets.mitems(): if cfi.start >= modifiedAt: newCFI = (cfi.start + offset).toTriple() - self.chunk.cfi[cfi.start] = newCFI[0] - self.chunk.cfi[cfi.start + 1] = newCFI[1] - self.chunk.cfi[cfi.start + 2] = newCFI[2] - tmp = [self.chunk.cfi[cfi.start + 3], self.chunk.cfi[cfi.start + 4], self.chunk.cfi[cfi.start + 5]].fromTriple().int + self.chunk.cfi[cfi.pos] = newCFI[0] + self.chunk.cfi[cfi.pos + 1] = newCFI[1] + self.chunk.cfi[cfi.pos + 2] = newCFI[2] + tmp = [self.chunk.cfi[cfi.pos + 3], self.chunk.cfi[cfi.pos + 4], self.chunk.cfi[cfi.pos + 5]].fromTriple().int newCFI = (tmp + offset).toTriple() - self.chunk.cfi[cfi.start + 3] = newCFI[0] - self.chunk.cfi[cfi.start + 4] = newCFI[1] - self.chunk.cfi[cfi.start + 5] = newCFI[2] + self.chunk.cfi[cfi.pos + 3] = newCFI[0] + self.chunk.cfi[cfi.pos + 4] = newCFI[1] + self.chunk.cfi[cfi.pos + 5] = newCFI[2] cfi.fn.codePos += offset cfi.start += offset cfi.stop += offset + inc(i) proc fixJumps(self: Compiler, oldLen: int, modifiedAt: int) = @@ -572,7 +574,6 @@ proc getStackPos(self: Compiler, name: Name): int = found = true break inc(result) - echo variable if not found: return -1 @@ -637,11 +638,9 @@ proc compare(self: Compiler, a, b: Type): bool = # Functions are a bit trickier if a.args.len() != b.args.len(): return false - if a.isClosure != b.isClosure: - return false if a.isCoroutine != b.isCoroutine: return false - elif not self.compare(a.returnType, b.returnType): + if not self.compare(a.returnType, b.returnType): return false for (argA, argB) in zip(a.args, b.args): if not self.compare(argA.kind, argB.kind): @@ -1097,10 +1096,12 @@ proc endScope(self: Compiler) = continue if name.depth > self.scopeDepth: names.add(name) - if not name.resolved: + #[if not name.resolved: # TODO: Emit a warning? + continue]# + if name.owner != self.currentModule and self.scopeDepth > -1: continue - elif name.kind in [NameKind.Var, NameKind.Argument]: + if name.kind in [NameKind.Var, NameKind.Argument]: # We don't increase the pop count for some kinds of objects # because they're not stored the same way as regular variables # (for types, generics and function declarations) @@ -2128,7 +2129,8 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) = name.codePos = self.chunk.code.len() # We let our debugger know this function's boundaries self.chunk.cfi.add(self.chunk.code.high().toTriple()) - self.cfiOffsets.add((start: self.chunk.code.high(), stop: 0, fn: name)) + self.cfiOffsets.add((start: self.chunk.code.high(), stop: 0, pos: self.chunk.cfi.len() - 3, fn: name)) + var cfiOffset = self.cfiOffsets[^1] let idx = self.chunk.cfi.len() self.chunk.cfi.add(0.toTriple()) # Patched it later self.chunk.cfi.add(uint8(node.arguments.len())) @@ -2161,7 +2163,7 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) = of NodeKind.funDecl: hasVal = self.currentFunction.valueType.fun.hasExplicitReturn of NodeKind.lambdaExpr: - hasVal = LambdaExpr(Declaration(self.currentFunction.valueType.fun)).hasExplicitReturn + hasVal = LambdaExpr(self.currentFunction.node).hasExplicitReturn else: discard # Unreachable if not hasVal and not typ.isNil(): @@ -2184,7 +2186,7 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) = self.chunk.cfi[idx] = stop[0] self.chunk.cfi[idx + 1] = stop[1] self.chunk.cfi[idx + 2] = stop[2] - self.cfiOffsets[^1].stop = self.chunk.code.len() + cfiOffset.stop = self.chunk.code.len() # Currently defer is not functional, so we # just pop the instructions for _ in deferStart..self.deferred.high(): diff --git a/tests/closures.pn b/tests/closures.pn index 9dfbe79..836d40c 100644 --- a/tests/closures.pn +++ b/tests/closures.pn @@ -1,21 +1,13 @@ # Tests closures -import std; - -fn makeClosure(n: int): fn: fn: int { - fn inner: fn: int { - fn deep: int { - return n; - } - return deep; +fn makeClosure(n: int): fn: int { + fn inner: int { + return n; } return inner; } -var closure = makeClosure(38); -var inner = closure(); -print(inner()); # 38 -print(inner()); # 38 -print(makeClosure(420)()()); # 420 \ No newline at end of file +var c = makeClosure(38); +c();