diff --git a/src/backend/vm.nim b/src/backend/vm.nim index 70a8b31..ece8847 100644 --- a/src/backend/vm.nim +++ b/src/backend/vm.nim @@ -648,6 +648,13 @@ when debugVM: # So nim shuts up 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: @@ -655,13 +662,6 @@ when debugVM: # So nim shuts up if i < self.envs.high(): stdout.styledWrite(fgYellow, ", ") styledEcho fgMagenta, "]" - if self.closures.len() !> 0: - stdout.styledWrite(fgGreen, "Environment offsets: ", fgMagenta, "[") - for i, e in self.closures: - stdout.styledWrite(fgYellow, $e) - if i < self.closures.high(): - stdout.styledWrite(fgYellow, ", ") - styledEcho fgMagenta, "]" if self.results.len() !> 0: stdout.styledWrite(fgYellow, "Function Results: ", fgMagenta, "[") for i, e in self.results: @@ -669,13 +669,6 @@ when debugVM: # So nim shuts up if i < self.results.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, "]" discard readLine stdin @@ -771,7 +764,7 @@ proc dispatch*(self: PeonVM) = self.results.add(self.getNil()) # Creates a new call frame self.frames.add(uint64(self.calls.len() - 2)) - self.closures.add(offset) + self.closures.add(offset - 1) # Loads the arguments onto the stack for _ in 0.. self.scopeDepth: names.add(name) - if name.valueType.kind notin {Generic, CustomType} and not name.isFunDecl: + if name.valueType.kind notin {Generic, CustomType} and not name.isFunDecl and name.depth > 0: # We don't increase the pop count for these kinds of objects # because they're not stored the same way as regular variables + # (for types, generics and function declarations). We also don't + # pop for local scopes because the VM takes care of closing a function's + # stack frame inc(popCount) - if name.isFunDecl and not name.valueType.isClosure and name.valueType.children.len() > 0 and name.depth == 0: - # If a function at the top level contains any closures, - # when it goes out of scope all of the environments that - # belong to its inner functions also go out of - # scope + if name.isFunDecl and name.valueType.children.len() > 0 and name.depth == 0: + # When a closure goes out of scope, its environment is reclaimed. + # This includes the environments of every other closure that may + # have been contained within it, too var i = 0 - let f = name.valueType - for fn in flatten(f): - if fn.isClosure: - for y in 0..f.envLen: - self.closedOver.delete(y + i) - self.emitByte(PopClosure, self.peek().token.line) - self.emitBytes((y + i).toTriple(), self.peek().token.line) - inc(i) + var all = flatten(name.valueType) + # Why this? Well, it's simple: if a function returns + # a closure, that function becomes a closure too. The + # environments of closures are aligned one after the + # other, so if a and b are both closures, but only b + # closes over a value, both a and b will have an envLen + # of 1, which would cause us to emit one extra PopClosure + # instruction than what's actually needed. We can account + # for this easily by checking if the contained function's + # environment is larger than the contained one, which will + # guarantee there actually is some value that the contained + # function is closing over + var envLen = 0 + var lastEnvLen = 0 + for fn in all: + if fn.isClosure and fn.envLen > lastEnvLen: + envLen += fn.envLen + lastEnvLen = fn.envLen + for y in 0.. 1: # If we're popping less than 65535 variables, then # we can emit a PopN instruction. This is true for @@ -1434,8 +1480,8 @@ proc identifier(self: Compiler, node: IdentExpr) = else: if s.valueType.kind == Function and s.isFunDecl: # Functions have no runtime - # representation, so we need - # to create one on the fly + # representation: they're just + # 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: @@ -1446,15 +1492,18 @@ proc identifier(self: Compiler, node: IdentExpr) = var fn = self.currentFunction.valueType while true: fn.isClosure = true + fn.envLen += 1 if fn.parent.isNil(): break fn = fn.parent s.isClosedOver = true - self.currentFunction.valueType.envLen += 1 self.closedOver.add(s) let stackIdx = self.getStackPos(s).toTriple() let closeIdx = self.getClosurePos(s).toTriple() let oldLen = self.chunk.code.len() + # This madness makes it so that we can insert bytecode + # at arbitrary offsets into our alredy compiled code and + # have our metadata be up to date self.chunk.code.insert(StoreClosure.uint8, s.belongsTo.codePos) self.chunk.code.insert(stackIdx[0], s.belongsTo.codePos + 1) self.chunk.code.insert(stackIdx[1], s.belongsTo.codePos + 2) @@ -1581,7 +1630,12 @@ proc callExpr(self: Compiler, node: CallExpr): Name {.discardable.} = for exp in reversed(all): self.callExpr(exp) # TODO - self.generateCall(result, argExpr, onStack=true) + case all[^1].callee.kind: + of identExpr: + let fn = self.resolve(IdentExpr(all[^1].callee)) + self.generateCall(fn.valueType.returnType, argExpr, fn.line) + else: + discard # TODO: Lambdas # TODO: Calling lambdas on-the-fly (i.e. on the same line) else: let typ = self.inferType(node) diff --git a/src/util/debugger.nim b/src/util/debugger.nim index c5bc7ee..944b759 100644 --- a/src/util/debugger.nim +++ b/src/util/debugger.nim @@ -150,6 +150,17 @@ proc callInstruction(self: Debugger, instruction: OpCode) = self.current += 1 +proc callClosureInstruction(self: Debugger, instruction: OpCode) = + ## Debugs closure calls + var size = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple() + self.current += 3 + var envSize = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple() + self.current += 3 + printInstruction(instruction) + styledEcho fgGreen, &", creates frame of size ", fgYellow, $(size + 2), fgGreen, " with environment of size ", fgYellow, $envSize, fgGreen + self.current += 1 + + proc constantInstruction(self: Debugger, instruction: OpCode) = ## Debugs instructions that operate on the constant table var size: uint @@ -203,6 +214,8 @@ proc disassembleInstruction*(self: Debugger) = self.storeClosureInstruction(opcode) of Call: self.callInstruction(opcode) + of CallClosure: + self.callClosureInstruction(opcode) of jumpInstructions: self.jumpInstruction(opcode) else: diff --git a/tests/chainedCalls.pn b/tests/chainedCalls.pn index e8f5dd0..ffd0e0f 100644 --- a/tests/chainedCalls.pn +++ b/tests/chainedCalls.pn @@ -5,13 +5,13 @@ fn first(a, b, c: int): int { return a; } -fn second(a, b: int): int { - return first(b, a, 0); +fn second(a, b, c: int): int { + return first(b, a, c); } fn last(a, b, c: int): int { - return second(a, c); + return second(a, c, b); } @@ -20,8 +20,9 @@ fn middle(a, b, c: int): int { } +# These should all print true! print(first(1, 2, 3) == 1); -var x = first(second(1, 2), 3, 4); -print(x == 2); -print(middle(3, 1, 2) == 1); -print(first(last(second(2, 1), 3, 0), 4, 5) == 0); \ No newline at end of file +print(second(1, 2, 3) == 2); +print(last(1, 2, 3) == 3); +print(first(second(1, 2, 3), 2, 3) == 2); +print(last(1, 2, second(3, 4, 5)) == 4); \ No newline at end of file diff --git a/tests/functionObj.pn b/tests/functionObj.pn index 40d4cca..302c9da 100644 --- a/tests/functionObj.pn +++ b/tests/functionObj.pn @@ -11,19 +11,6 @@ fn outer: fn (n: int): int { } - -fn getAdder(a, b: int): fn (): int64 { - var x = a; - var y = b; - fn adder: int { - return x + y; - } - return adder; -} - - +var x = outer(); +print(x(50)); # 50 print(outer()(1)); # 1 -var a = 1; -var b = 2; -var adder = getAdder(a, b); -print(adder()); # 3 \ No newline at end of file