diff --git a/src/backend/vm.nim b/src/backend/vm.nim index 5627ac9..e83a540 100644 --- a/src/backend/vm.nim +++ b/src/backend/vm.nim @@ -31,7 +31,7 @@ when debugVM or debugMem or debugGC: import std/terminal -{.push checks:not disableVMChecks.} # The VM is a critical point where checks are deleterious +{.push checks:enableVMChecks.} # The VM is a critical point where checks are deleterious type PeonVM* = ref object @@ -339,10 +339,10 @@ proc newPeonVM*: PeonVM = result.initCache() result.gc = newPeonGC() result.frames = @[] - result.calls = @[] result.operands = @[] result.results = @[] result.envs = @[] + result.calls = @[] result.gc.vm = result @@ -625,7 +625,7 @@ when debugVM: # So nim shuts up stdout.styledWrite(fgGreen, "Call Stack: ", fgMagenta, "[") for i, e in self.calls: stdout.styledWrite(fgYellow, $e) - if i < self.calls.high(): + if i < self.calls.len(): stdout.styledWrite(fgYellow, ", ") styledEcho fgMagenta, "]" if self.operands.len() !> 0: @@ -709,8 +709,6 @@ proc dispatch*(self: PeonVM) = of LoadUInt8: self.push(uint64(self.constReadUInt8(int(self.readLong())))) of LoadString: - # TODO: Use constReadString with own memory manager - # Strings are broken rn!! self.push(cast[uint64](self.constReadString(int(self.readLong()), int(self.readLong())))) # We cast instead of converting because, unlike with integers, # we don't want nim to touch any of the bits of the underlying diff --git a/src/config.nim b/src/config.nim index 4f2fe27..5e876fb 100644 --- a/src/config.nim +++ b/src/config.nim @@ -27,7 +27,7 @@ const debugStressGC* {.booldefine.} = false # Make the GC run a collection at const PeonBytecodeMarker* = "PEON_BYTECODE" # Magic value at the beginning of bytecode files const HeapGrowFactor* = 2 # The growth factor used by the GC to schedule the next collection const FirstGC* = 1024 * 1024; # How many bytes to allocate before running the first GC -const disableVMChecks* {.booldefine.} = true; # Disables all types of compiler (nim-wise) checks in the VM +const enableVMChecks* {.booldefine.} = true; # Enables all types of compiler (nim-wise) checks in the VM when HeapGrowFactor <= 1: {.fatal: "Heap growth factor must be > 1".} const PeonVersion* = (major: 0, minor: 1, patch: 0) @@ -35,7 +35,7 @@ const PeonRelease* = "alpha" const PeonCommitHash* = "9884975fbd0bd2880f0969d21ee7b6ae44ecbafa" when len(PeonCommitHash) != 40: {.fatal: "The git commit hash must be exactly 40 characters long".} -const PeonBranch* = "master" +const PeonBranch* = "fixed-size-stack" when len(PeonBranch) > 255 or len(PeonBranch) == 0: {.fatal: "The git branch name's length must be within 1 and 255 characters".} const PeonVersionString* = &"Peon {PeonVersion.major}.{PeonVersion.minor}.{PeonVersion.patch} {PeonRelease} ({PeonBranch}, {CompileDate}, {CompileTime}, {PeonCommitHash[0..8]}) [Nim {NimVersion}] on {hostOS} ({hostCPU})" diff --git a/src/frontend/compiler.nim b/src/frontend/compiler.nim index ef517ac..bf43e4e 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -146,6 +146,8 @@ type # The current scope depth. If > 0, we're # in a local scope, otherwise it's global scopeDepth: int + # Scope ownership data + scopeOwners: seq[tuple[owner: Name, depth: int]] # The current function being compiled currentFunction: Name # Are optimizations turned on? @@ -237,6 +239,8 @@ proc newCompiler*(enableOptimizations: bool = true, replMode: bool = false): Com result.compilerProcs["magic"] = handleMagicPragma result.compilerProcs["pure"] = handlePurePragma result.source = "" + result.scopeOwners = @[] + ## Public getters for nicer error formatting proc getCurrentNode*(self: Compiler): ASTNode = (if self.current >= @@ -1031,6 +1035,7 @@ proc beginScope(self: Compiler) = ## Begins a new local scope by incrementing the current ## scope's depth inc(self.scopeDepth) + self.scopeOwners.add((self.currentFunction, self.scopeDepth)) proc `$`(self: Type): string = $self[] @@ -1053,6 +1058,7 @@ proc endScope(self: Compiler) = if self.scopeDepth < 0: self.error("cannot call endScope with scopeDepth < 0 (This is an internal error and most likely a bug)") dec(self.scopeDepth) + discard self.scopeOwners.pop() var names: seq[Name] = @[] var popCount = 0 for name in self.names: @@ -1346,6 +1352,7 @@ proc beginProgram(self: Compiler, incremental: bool = false): int = isFunDecl: true, line: -1) self.names.add(main) + self.scopeOwners.add((main, 0)) self.emitByte(LoadInt64, 1) self.emitBytes(self.chunk.writeConstant(main.codePos.toLong()), 1) self.emitByte(LoadUInt32, 1) @@ -1485,7 +1492,7 @@ proc identifier(self: Compiler, node: IdentExpr) = # a location to jump to self.emitByte(LoadInt64, node.token.line) self.emitBytes(self.chunk.writeConstant(s.codePos.toLong()), node.token.line) - elif self.scopeDepth > 0 and not self.currentFunction.isNil() and s.depth != self.scopeDepth: + elif self.scopeDepth > 0 and not self.currentFunction.isNil() and s.depth != self.scopeDepth and self.scopeOwners[s.depth].owner != self.currentFunction: # Loads a closure variable. Stored in a separate "closure array" in the VM that does not # align its semantics with the call stack. This makes closures work as expected and is # not much slower than indexing our stack (since they're both dynamic arrays at runtime anyway)