diff --git a/src/backend/vm.nim b/src/backend/vm.nim index 85a73fc..f25fe09 100644 --- a/src/backend/vm.nim +++ b/src/backend/vm.nim @@ -248,7 +248,13 @@ proc dispatch*(self: PeonVM) = # into the given stack index self.set(int(self.readLong()), self.pop()) of StoreHeap: - self.heapVars.add(self.pop()) + # Stores/updates the value of a closed-over + # variable + let idx = self.readLong().int + if idx > self.heapVars.high(): + self.heapVars.add(self.pop()) + else: + self.heapVars[idx] = self.pop() of LoadHeap: self.push(self.heapVars[self.readLong()]) of LoadVar: diff --git a/src/frontend/compiler.nim b/src/frontend/compiler.nim index 7fc4315..948e8fc 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -50,6 +50,7 @@ type node*: ASTNode case kind*: TypeKind: of Function: + isLambda: bool args*: seq[Type] returnType*: Type else: @@ -83,6 +84,7 @@ type # code begins. For variables, this stores their # position in the stack (used for closures) codePos: int + isClosedOver: bool Loop = object ## A "loop object" used ## by the compiler to emit @@ -402,13 +404,14 @@ proc detectClosureVariable(self: Compiler, name: Name, # emits LoadHeap if it detects the variable is closed over, # whether or not this function is called self.closedOver.add(name) + let idx = self.closedOver.high().toTriple() if self.closedOver.len() >= 16777216: self.error("too many consecutive closed-over variables (max is 16777216)") - let idx = self.closedOver.high().toTriple() self.chunk.code[name.codePos] = StoreHeap.uint8 self.chunk.code[name.codePos + 1] = idx[0] self.chunk.code[name.codePos + 2] = idx[1] self.chunk.code[name.codePos + 3] = idx[2] + name.isClosedOver = true proc compareTypesWithNullNode(self: Compiler, a, b: Type): bool = @@ -452,28 +455,92 @@ proc compareTypes(self: Compiler, a, b: Type): bool = of Function: if a.node == nil or b.node == nil: return self.compareTypesWithNullNode(a, b) - let - a = FunDecl(a.node) - b = FunDecl(b.node) - typeOfA = self.inferType(a.returnType) - typeOfB = self.inferType(b.returnType) - if a.name.token.lexeme != b.name.token.lexeme: - return false - elif a.arguments.len() != b.arguments.len(): - return false - elif not self.compareTypes(typeOfA, typeOfB): - if typeOfA.kind != Any and typeOfB.kind != Any: + if not a.isLambda and not b.isLambda: + let + a = FunDecl(a.node) + b = FunDecl(b.node) + typeOfA = self.inferType(a.returnType) + typeOfB = self.inferType(b.returnType) + if a.name.token.lexeme != b.name.token.lexeme: return false - for (argA, argB) in zip(a.arguments, b.arguments): - if argA.mutable != argB.mutable: + elif a.arguments.len() != b.arguments.len(): return false - elif argA.isRef != argB.isRef: + elif not self.compareTypes(typeOfA, typeOfB): + if typeOfA.kind != Any and typeOfB.kind != Any: + return false + for (argA, argB) in zip(a.arguments, b.arguments): + if argA.mutable != argB.mutable: + return false + elif argA.isRef != argB.isRef: + return false + elif argA.isPtr != argB.isPtr: + return false + elif not self.compareTypes(self.inferType(argA.valueType), self.inferType(argB.valueType)): + return false + return true + elif a.isLambda and not b.isLambda: + let + a = LambdaExpr(a.node) + b = FunDecl(b.node) + typeOfA = self.inferType(a.returnType) + typeOfB = self.inferType(b.returnType) + if a.arguments.len() != b.arguments.len(): return false - elif argA.isPtr != argB.isPtr: + elif not self.compareTypes(typeOfA, typeOfB): + if typeOfA.kind != Any and typeOfB.kind != Any: + return false + for (argA, argB) in zip(a.arguments, b.arguments): + if argA.mutable != argB.mutable: + return false + elif argA.isRef != argB.isRef: + return false + elif argA.isPtr != argB.isPtr: + return false + elif not self.compareTypes(self.inferType(argA.valueType), self.inferType(argB.valueType)): + return false + return true + elif b.isLambda and not a.isLambda: + let + a = FunDecl(a.node) + b = LambdaExpr(b.node) + typeOfA = self.inferType(a.returnType) + typeOfB = self.inferType(b.returnType) + if a.arguments.len() != b.arguments.len(): return false - elif not self.compareTypes(self.inferType(argA.valueType), self.inferType(argB.valueType)): + elif not self.compareTypes(typeOfA, typeOfB): + if typeOfA.kind != Any and typeOfB.kind != Any: + return false + for (argA, argB) in zip(a.arguments, b.arguments): + if argA.mutable != argB.mutable: + return false + elif argA.isRef != argB.isRef: + return false + elif argA.isPtr != argB.isPtr: + return false + elif not self.compareTypes(self.inferType(argA.valueType), self.inferType(argB.valueType)): + return false + return true + else: + let + a = LambdaExpr(a.node) + b = LambdaExpr(b.node) + typeOfA = self.inferType(a.returnType) + typeOfB = self.inferType(b.returnType) + if a.arguments.len() != b.arguments.len(): return false - return true + elif not self.compareTypes(typeOfA, typeOfB): + if typeOfA.kind != Any and typeOfB.kind != Any: + return false + for (argA, argB) in zip(a.arguments, b.arguments): + if argA.mutable != argB.mutable: + return false + elif argA.isRef != argB.isRef: + return false + elif argA.isPtr != argB.isPtr: + return false + elif not self.compareTypes(self.inferType(argA.valueType), self.inferType(argB.valueType)): + return false + return true else: discard @@ -607,6 +674,13 @@ proc inferType(self: Compiler, node: Expression): Type = nanExpr, floatExpr, nilExpr }: return self.inferType(LiteralExpr(node)) + of lambdaExpr: + var node = LambdaExpr(node) + result = Type(kind: Function, returnType: nil, node: node, args: @[], isLambda: true) + if node.returnType != nil: + result.returnType = self.inferType(node.returnType) + for argument in node.arguments: + result.args.add(self.inferType(argument.valueType)) else: discard # Unreachable @@ -639,7 +713,8 @@ proc typeToStr(self: Compiler, typ: Type): string = result &= ")" else: discard # Unreachable - result &= &": {self.typeToStr(typ.returnType)}" + if typ.returnType != nil: + result &= &": {self.typeToStr(typ.returnType)}" else: discard @@ -807,7 +882,7 @@ proc callBinaryOp(self: Compiler, fn: Name, op: BinaryExpr) = self.expression(op.b) self.emitByte(Call) # Creates a stack frame self.emitBytes(fn.codePos.toTriple()) - self.emitBytes(1.toTriple()) + self.emitBytes(2.toTriple()) self.patchReturnAddress(idx) @@ -876,7 +951,11 @@ proc declareName(self: Compiler, node: Declaration) = isConst: node.isConst, valueType: Type(kind: self.inferType(node.value).kind, node: node), codePos: self.chunk.code.len(), - isLet: node.isLet)) + isLet: node.isLet, + isClosedOver: false)) + # We emit 4 No-Ops because they may become a + # StoreHeap instruction. If not, they'll be + # removed before the compiler is finished self.emitBytes([NoOp, NoOp, NoOp, NoOp]) of NodeKind.funDecl: var node = FunDecl(node) @@ -894,7 +973,8 @@ proc declareName(self: Compiler, node: Declaration) = args: @[]), codePos: self.chunk.code.high(), name: node.name, - isLet: false)) + isLet: false, + isClosedOver: false)) let fn = self.names[^1] for argument in node.arguments: if self.names.high() > 16777215: @@ -909,7 +989,8 @@ proc declareName(self: Compiler, node: Declaration) = name: argument.name, valueType: nil, codePos: self.chunk.code.len(), - isLet: false)) + isLet: false, + isClosedOver: false)) self.names[^1].valueType = self.inferType(argument.valueType) # We check if the argument's type is a generic if self.names[^1].valueType == nil and argument.valueType.kind == identExpr: @@ -923,7 +1004,7 @@ proc declareName(self: Compiler, node: Declaration) = self.names[^1].valueType.node = argument.name fn.valueType.args.add(self.names[^1].valueType) else: - discard # Unreachable + discard # TODO: Types, enums proc identifier(self: Compiler, node: IdentExpr) = @@ -949,7 +1030,7 @@ proc identifier(self: Compiler, node: IdentExpr) = self.emitBytes((index - self.frames[self.scopeDepth]).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 comparatively slower than indexing our stack (since they're both + # This makes closures work as expected and is not much slower than indexing our stack (since they're both # dynamic arrays at runtime anyway) self.emitByte(LoadHeap) self.emitBytes(self.closedOver.high().toTriple()) @@ -1338,7 +1419,6 @@ proc funDecl(self: Compiler, node: FunDecl) = var msg = &"multiple matching implementations of '{node.name.token.lexeme}' found:\n" for fn in reversed(impl): var node = FunDecl(fn.valueType.node) - discard self.typeToStr(fn.valueType) msg &= &"- '{node.name.token.lexeme}' at line {node.token.line} of type {self.typeToStr(fn.valueType)}\n" self.error(msg) # We store the current function @@ -1369,19 +1449,6 @@ proc funDecl(self: Compiler, node: FunDecl) = # of boilerplate code to make closures work, but # that's about it - # Function is ending! - self.chunk.cfi.add(start.toTriple()) - self.chunk.cfi.add(self.chunk.code.high().toTriple()) - self.chunk.cfi.add(self.frames[^1].toTriple()) - self.chunk.cfi.add(uint8(node.arguments.len())) - if not system.`==`(node.name, nil): - self.chunk.cfi.add(node.name.token.lexeme.len().toDouble()) - var s = node.name.token.lexeme - if node.name.token.lexeme.len() >= uint16.high().int: - s = node.name.token.lexeme[0..uint16.high()] - self.chunk.cfi.add(s.toBytes()) - else: - self.chunk.cfi.add(0.toDouble()) case self.currentFunction.kind: of NodeKind.funDecl: if not self.currentFunction.hasExplicitReturn: @@ -1396,6 +1463,19 @@ proc funDecl(self: Compiler, node: FunDecl) = self.emitByte(OpCode.Return) else: discard # Unreachable + # Function is ending! + self.chunk.cfi.add(start.toTriple()) + self.chunk.cfi.add(self.chunk.code.high().toTriple()) + self.chunk.cfi.add(self.frames[^1].toTriple()) + self.chunk.cfi.add(uint8(node.arguments.len())) + if not system.`==`(node.name, nil): + self.chunk.cfi.add(node.name.token.lexeme.len().toDouble()) + var s = node.name.token.lexeme + if node.name.token.lexeme.len() >= uint16.high().int: + s = node.name.token.lexeme[0..uint16.high()] + self.chunk.cfi.add(s.toBytes()) + else: + self.chunk.cfi.add(0.toDouble()) # Currently defer is not functional so we # just pop the instructions for i in countup(deferStart, self.deferred.len() - 1, 1): diff --git a/src/frontend/meta/bytecode.nim b/src/frontend/meta/bytecode.nim index 9c75869..5a206f0 100644 --- a/src/frontend/meta/bytecode.nim +++ b/src/frontend/meta/bytecode.nim @@ -134,14 +134,12 @@ type # We group instructions by their operation/operand types for easier handling when debugging # Simple instructions encompass instructions that push onto/pop off the stack unconditionally (True, False, Pop, etc.) -const simpleInstructions* = {OpCode.Return, LoadNil, +const simpleInstructions* = {Return, LoadNil, LoadTrue, LoadFalse, LoadNan, LoadInf, - Pop, OpCode.Raise, - BeginTry, FinishTry, - OpCode.Yield, OpCode.Await, - OpCode.NoOp, OpCode.Return, - OpCode.ReturnValue} + Pop, Raise, BeginTry, + FinishTry, Yield, + Await, NoOp, ReturnValue} # Constant instructions are instructions that operate on the bytecode constant table const constantInstructions* = {LoadInt64, LoadUInt64, diff --git a/src/util/debugger.nim b/src/util/debugger.nim index 19e7913..2529145 100644 --- a/src/util/debugger.nim +++ b/src/util/debugger.nim @@ -25,6 +25,7 @@ type CFIElement = ref object start, stop, bottom, argc: int name: string + started, stopped: bool Debugger* = ref object chunk: Chunk cfiData: seq[CFIElement] @@ -63,7 +64,8 @@ proc printInstruction(instruction: OpCode, newline: bool = false) = proc checkFrameStart(self: Debugger, n: int) = for i, e in self.cfiData: - if n == e.start: + if n == e.start and not (e.started or e.stopped): + e.started = true styledEcho fgBlue, "\n==== Peon Bytecode Debugger - Begin Frame ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ====" styledEcho fgGreen, "\t- Start offset: ", fgYellow, $e.start styledEcho fgGreen, "\t- End offset: ", fgYellow, $e.stop @@ -73,7 +75,8 @@ proc checkFrameStart(self: Debugger, n: int) = proc checkFrameEnd(self: Debugger, n: int) = for i, e in self.cfiData: - if n == e.stop: + if n == e.stop and e.started and not e.stopped: + e.stopped = true styledEcho fgBlue, "\n==== Peon Bytecode Debugger - End Frame ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ====" @@ -94,6 +97,7 @@ proc stackTripleInstruction(self: Debugger, instruction: OpCode) = stdout.styledWriteLine(fgGreen, &", points to index ", fgYellow, $slot) self.current += 4 + proc stackDoubleInstruction(self: Debugger, instruction: OpCode) = ## Debugs instructions that operate on a single value on the stack using a 16-bit operand var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2]].fromDouble()