diff --git a/src/backend/vm.nim b/src/backend/vm.nim index bde82cc..db55717 100644 --- a/src/backend/vm.nim +++ b/src/backend/vm.nim @@ -287,6 +287,41 @@ proc constReadFloat64(self: PeonVM, idx: int): PeonObject = copyMem(result.`float`.addr, arr.addr, sizeof(arr)) +proc `$`(self: PeonObject): string = + case self.kind: + of Int64: + result = &"{self.long}'i64" + of UInt64: + result = &"{self.uLong}'u64" + of Int32: + result = &"{self.`int`}'i32" + of UInt32: + result = &"{self.uInt}'u32" + of Int16: + result = &"{self.short}'i16" + of UInt16: + result = &"{self.uShort}'u16" + of Int8: + result = &"{self.tiny}'i8" + of UInt8: + result = &"{self.uTiny}'u8" + of Float32: + result = &"{self.halfFloat}'f32" + of Float64: + result = &"{self.`float`}'f64" + of ObjectKind.Inf: + if self.positive: + result ="inf" + else: + result ="-inf" + of ObjectKind.Nan, Nil: + result =($self.kind).toLowerAscii() + of Function: + result = &"fn(ip: {self.ip})" + else: + discard + + proc dispatch*(self: PeonVM) = ## Main bytecode dispatch loop var instruction {.register.}: OpCode @@ -342,11 +377,13 @@ proc dispatch*(self: PeonVM) = of LoadFloat64: self.push(self.constReadFloat64(int(self.readLong()))) of LoadFunction: - self.pushc(PeonObject(kind: Function, ip: self.readLong())) - of LoadFunctionObj: + # Loads a function onto the operand stack by reading its + # instruction pointer self.push(PeonObject(kind: Function, ip: self.readLong())) of LoadReturnAddress: - self.pushc(PeonObject(kind: UInt32, uInt: self.readUInt())) + # Loads a 32-bit unsigned integer. Used to load function + # return addresses + self.push(PeonObject(kind: UInt32, uInt: self.readUInt())) of Call: # Calls a function. The calling convention for peon # functions is pretty simple: the first item in the @@ -354,20 +391,34 @@ proc dispatch*(self: PeonVM) = # instruction pointer to jump to, followed by a 32-bit # return address. After that, all arguments and locals # follow - var size {.used.} = self.readLong().int - self.frames.add(self.calls.len() - 2) - self.ip = self.peekc(-1).ip + var argc {.used.} = self.readLong().int + let retAddr = self.peek(-argc) # Return address + let fnObj = self.peek(-argc - 1) # Function object + self.ip = fnObj.ip + self.pushc(fnObj) + self.pushc(retAddr) + # Creates a new result slot for the + # function's return value self.results.add(self.getNil()) - # TODO: Use the frame size once + # Creates a new call frame + self.frames.add(self.calls.len() - 2) + # Loads the arguments onto the stack + for _ in 0.. 0: - dec(size) + #[while argc > 0: + dec(argc) self.pushc(self.getNil()) ]# - of LoadArgument: - self.pushc(self.pop()) - of OpCode.Return: + of Return: # Returns from a function. # Every peon program is wrapped # in a hidden function, so this @@ -380,6 +431,7 @@ proc dispatch*(self: PeonVM) = self.push(self.results.pop()) else: discard self.results.pop() + # Discard a stack frame discard self.frames.pop() if self.frames.len() == 0: # End of the program! @@ -392,11 +444,11 @@ proc dispatch*(self: PeonVM) = of StoreVar: # Stores the value at the top of the operand stack # into the given call stack index - let idx = int(self.readLong()) + self.frames[^1] - if idx <= self.calls.high(): + let idx = int(self.readLong()) + if idx + self.frames[^1] <= self.calls.high(): self.setc(idx, self.pop()) else: - self.calls.add(self.pop()) + self.pushc(self.pop()) of StoreClosure: # Stores/updates the value of a closed-over # variable @@ -411,56 +463,30 @@ proc dispatch*(self: PeonVM) = # Loads a closed-over variable onto the # stack self.push(self.closedOver[self.readLong()]) - of PopClosure: - # Removes a closed-over variable from the closure - # array - discard self.closedOver.pop() of LoadVar: + # Pushes a variable onto the operand + # stack self.push(self.getc(int(self.readLong()))) of NoOp: + # Does nothing continue of PopC: + # Pops a value off the call stack discard self.popc() of Pop: + # Pops a value off the operand stack discard self.pop() of PushC: + # Pushes a value from the operand stack + # onto the call stack self.pushc(self.pop()) of PopRepl: if self.frames.len() > 1: discard self.pop() continue - let popped = self.pop() - 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 Float32: - echo &"{popped.halfFloat}'f32" - of Float64: - echo &"{popped.`float`}'f64" - of ObjectKind.Inf: - if popped.positive: - echo "inf" - else: - echo "-inf" - of ObjectKind.Nan, Nil: - echo ($popped.kind).toLowerAscii() - else: - discard + echo self.pop() of PopN: + # Pops N elements off the call stack for _ in 0.. 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 diff --git a/src/frontend/compiler.nim b/src/frontend/compiler.nim index 266a27a..b43d8e5 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -102,6 +102,10 @@ type isFunctionArgument: bool # Where is this node declared in the file? line: int + # Is this a function declaration or a variable + # with a function as value? (The distinction *is* + # important! Check emitFunction()) + isFunDecl: bool Loop = object ## A "loop object" used ## by the compiler to emit @@ -181,11 +185,11 @@ proc declaration(self: Compiler, node: Declaration) proc peek(self: Compiler, distance: int = 0): ASTNode proc identifier(self: Compiler, node: IdentExpr) proc varDecl(self: Compiler, node: VarDecl) -proc inferType(self: Compiler, node: LiteralExpr, strictMutable: bool = true): Type -proc inferType(self: Compiler, node: Expression, strictMutable: bool = true): Type +proc inferType(self: Compiler, node: LiteralExpr): Type +proc inferType(self: Compiler, node: Expression): Type proc findByName(self: Compiler, name: string): seq[Name] -proc findByType(self: Compiler, name: string, kind: Type, strictMutable: bool = true): seq[Name] -proc compareTypes(self: Compiler, a, b: Type, strictMutable: bool = true): bool +proc findByType(self: Compiler, name: string, kind: Type): seq[Name] +proc compareTypes(self: Compiler, a, b: Type): bool proc patchReturnAddress(self: Compiler, pos: int) proc handleMagicPragma(self: Compiler, pragma: Pragma, node: ASTnode) proc handlePurePragma(self: Compiler, pragma: Pragma, node: ASTnode) @@ -372,22 +376,25 @@ proc resolve(self: Compiler, name: IdentExpr, proc getStackPos(self: Compiler, name: IdentExpr, depth: int = self.scopeDepth): int = ## Returns the predicted call stack position of a given name, relative ## to the current frame - result = 2 var found = false - for variable in reversed(self.names): + result = 2 + for variable in self.names: + if variable.valueType.kind == Function: + continue + inc(result) if name.name.lexeme == variable.name.name.lexeme: if variable.isPrivate and variable.owner != self.currentModule: continue - if variable.depth == depth or variable.depth == 0: + elif variable.depth == depth or variable.depth == 0: # variable.depth == 0 for globals! found = true + dec(result) break - inc(result) if not found: return -1 -proc getClosurePos(self: Compiler, name: IdentExpr, depth: int = self.scopeDepth): int = +proc getClosurePos(self: Compiler, name: IdentExpr): int = ## Iterates the internal list of declared closure names backwards and ## returns the predicted closure array position of a given name. ## Returns -1 if the name can't be found (this includes names that @@ -398,7 +405,7 @@ proc getClosurePos(self: Compiler, name: IdentExpr, depth: int = self.scopeDepth if name.name.lexeme == variable.name.name.lexeme: if variable.isPrivate and variable.owner != self.currentModule: continue - elif variable.depth == depth: + else: found = true break dec(result) @@ -423,7 +430,7 @@ proc resolve(self: Compiler, name: string, return nil -proc detectClosureVariable(self: Compiler, name: Name, depth: int = self.scopeDepth) = +proc detectClosureVariable(self: Compiler, name: var Name, depth: int = self.scopeDepth, decl: bool = false) = ## Detects if the given name is used in a local scope deeper ## than the given one and modifies the code emitted for it ## to store it as a closure variable if it is. Does nothing if the name @@ -434,27 +441,25 @@ proc detectClosureVariable(self: Compiler, name: Name, depth: int = self.scopeDe ## unpredictably or crash if name.isNil() or name.depth == 0: return - elif name.depth < depth and not name.isClosedOver: + elif name.depth < depth: # Ding! The given name is closed over: we need to # change the dummy Jump instruction that self.declareName # put in place for us into a StoreClosure. We also update # the name's isClosedOver field so that self.identifier() # can emit a LoadClosure instruction instead of a LoadVar - self.closedOver.add(name) - let idx = self.closedOver.high().toTriple() + if not name.isClosedOver: + self.closedOver.add(name) if self.closedOver.len() >= 16777216: self.error("too many consecutive closed-over variables (max is 16777215)") - self.chunk.code[name.codePos] = StoreClosure.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 + if decl: + self.emitByte(StoreClosure) + self.emitBytes(self.closedOver.high().toTriple()) -proc compareTypes(self: Compiler, a, b: Type, strictMutable: bool = true): bool = +proc compareTypes(self: Compiler, a, b: Type): bool = ## Compares two type objects ## for equality (works with nil!) - # The nil code here is for void functions (when # we compare their return types) if a.isNil(): @@ -477,10 +482,6 @@ proc compareTypes(self: Compiler, a, b: Type, strictMutable: bool = true): bool # If they're different, then they can't # be the same type! return false - elif a.mutable != b.mutable and strictMutable: - # Are they both (im)mutable? If not, - # they're different - return false case a.kind: # If all previous checks pass, it's time # to go through each possible type peon @@ -504,10 +505,11 @@ proc compareTypes(self: Compiler, a, b: Type, strictMutable: bool = true): bool elif not self.compareTypes(a.returnType, b.returnType): return false for (argA, argB) in zip(a.args, b.args): - if not self.compareTypes(argA.kind, argB.kind, strictMutable): + if not self.compareTypes(argA.kind, argB.kind): return false return true else: + # TODO: Custom types discard @@ -553,7 +555,7 @@ proc toIntrinsic(name: string): Type = return nil -proc inferType(self: Compiler, node: LiteralExpr, strictMutable: bool = true): Type = +proc inferType(self: Compiler, node: LiteralExpr): Type = ## Infers the type of a given literal expression if node.isNil(): return nil @@ -565,7 +567,7 @@ proc inferType(self: Compiler, node: LiteralExpr, strictMutable: bool = true): T if size.len() == 1: return Type(kind: Int64) let typ = size[1].toIntrinsic() - if not self.compareTypes(typ, nil, strictMutable): + if not self.compareTypes(typ, nil): return typ else: self.error(&"invalid type specifier '{size[1]}' for int") @@ -576,7 +578,7 @@ proc inferType(self: Compiler, node: LiteralExpr, strictMutable: bool = true): T if size.len() == 1 or size[1] == "f64": return Type(kind: Float64) let typ = size[1].toIntrinsic() - if not self.compareTypes(typ, nil, strictMutable): + if not self.compareTypes(typ, nil): return typ else: self.error(&"invalid type specifier '{size[1]}' for float") @@ -594,7 +596,7 @@ proc inferType(self: Compiler, node: LiteralExpr, strictMutable: bool = true): T discard # TODO -proc inferType(self: Compiler, node: Expression, strictMutable: bool = true): Type = +proc inferType(self: Compiler, node: Expression): Type = ## Infers the type of a given expression and ## returns it if node.isNil(): @@ -611,9 +613,9 @@ proc inferType(self: Compiler, node: Expression, strictMutable: bool = true): Ty return self.inferType(UnaryExpr(node).a) of binaryExpr: let node = BinaryExpr(node) - var a = self.inferType(node.a, strictMutable) - var b = self.inferType(node.b, strictMutable) - if not self.compareTypes(a, b, strictMutable): + var a = self.inferType(node.a) + var b = self.inferType(node.b) + if not self.compareTypes(a, b): return nil return a of {intExpr, hexExpr, binExpr, octExpr, @@ -627,7 +629,7 @@ proc inferType(self: Compiler, node: Expression, strictMutable: bool = true): Ty if not node.returnType.isNil(): result.returnType = self.inferType(node.returnType) for argument in node.arguments: - result.args.add((argument.name.token.lexeme, self.inferType(argument.valueType, strictMutable))) + result.args.add((argument.name.token.lexeme, self.inferType(argument.valueType))) of callExpr: var node = CallExpr(node) case node.callee.kind: @@ -640,16 +642,16 @@ proc inferType(self: Compiler, node: Expression, strictMutable: bool = true): Ty else: result = nil of lambdaExpr: - result = self.inferType(LambdaExpr(node.callee).returnType, strictMutable) + result = self.inferType(LambdaExpr(node.callee).returnType) else: discard # Unreachable of varExpr: result = self.inferType(Var(node).value) result.mutable = true of refExpr: - result = Type(kind: Reference, value: self.inferType(Ref(node).value, strictMutable)) + result = Type(kind: Reference, value: self.inferType(Ref(node).value)) of ptrExpr: - result = Type(kind: Pointer, value: self.inferType(Ptr(node).value, strictMutable)) + result = Type(kind: Pointer, value: self.inferType(Ptr(node).value)) else: discard # Unreachable @@ -715,19 +717,29 @@ proc findByName(self: Compiler, name: string): seq[Name] = result.add(obj) -proc findByType(self: Compiler, name: string, kind: Type, strictMutable: bool = true): seq[Name] = +proc findByType(self: Compiler, name: string, kind: Type): seq[Name] = ## Looks for objects that have already been declared ## with the given name and type for obj in self.findByName(name): - if self.compareTypes(obj.valueType, kind, strictMutable): + if self.compareTypes(obj.valueType, kind): result.add(obj) +#[ +proc findAtDepth(self: Compiler, name: string, depth: int): seq[Name] = + ## Looks for objects that have been already declared + ## with the given name at the given scope depth. + ## Returns all objects that apply + for obj in self.findByName(name): + if obj.depth == depth: + result.add(obj) +]# -proc matchImpl(self: Compiler, name: string, kind: Type, strictMutable: bool = true): Name = + +proc matchImpl(self: Compiler, name: string, kind: Type): Name = ## Tries to find a matching function implementation ## compatible with the given type and returns its ## name object - let impl = self.findByType(name, kind, strictMutable) + let impl = self.findByType(name, kind) if impl.len() == 0: var msg = &"cannot find a suitable implementation for '{name}'" let names = self.findByName(name) @@ -761,9 +773,20 @@ proc matchImpl(self: Compiler, name: string, kind: Type, strictMutable: bool = t proc emitFunction(self: Compiler, name: Name) = ## Wrapper to emit LoadFunction instructions - self.emitByte(LoadFunction) - self.emitBytes(name.codePos.toTriple()) - + if name.isFunDecl: + self.emitByte(LoadFunction) + self.emitBytes(name.codePos.toTriple()) + # If we're not loading a statically declared + # function, then it must be a function object + # created by previous LoadFunction instructions + # that is now bound to some variable, so we just + # load it + elif not name.isClosedOver: + self.emitByte(LoadVar) + self.emitBytes(self.getStackPos(name.name).toTriple()) + else: + self.emitByte(LoadClosure) + self.emitBytes(self.getClosurePos(name.name).toTriple()) ## End of utility functions @@ -850,11 +873,11 @@ proc literal(self: Compiler, node: ASTNode) = proc handleBuiltinFunction(self: Compiler, fn: Name, args: seq[Expression]) = - ## Emits single instructions for builtin functions + ## Emits instructions for builtin functions ## such as addition or subtraction if fn.valueType.builtinOp notin ["GenericLogicalOr", "GenericLogicalAnd"]: - for argument in args: - self.expression(argument) + self.expression(args[1]) + self.expression(args[0]) case fn.valueType.builtinOp: of "AddInt64": self.emitByte(AddInt64) @@ -936,29 +959,27 @@ proc handleBuiltinFunction(self: Compiler, fn: Name, args: seq[Expression]) = self.emitByte(DivFloat32) of "MulFloat32": self.emitByte(MulFloat32) - of "GenericLogicalOr": + of "LogicalOr": self.expression(args[0]) let jump = self.emitJump(JumpIfTrue) self.expression(args[1]) self.patchJump(jump) - of "GenericLogicalAnd": + of "LogicalAnd": self.expression(args[0]) - var jump: int - if self.enableOptimizations: - jump = self.emitJump(JumpIfFalseOrPop) - else: - jump = self.emitJump(JumpIfFalse) - self.emitByte(Pop) + var jump = self.emitJump(JumpIfFalseOrPop) self.expression(args[1]) self.patchJump(jump) else: - discard # Unreachable + self.error(&"unknown built-in: '{fn.valueType.builtinOp}'") proc generateCall(self: Compiler, fn: Name, args: seq[Expression]) = ## Small wrapper that abstracts emitting a call instruction ## for a given function if fn.valueType.isBuiltinFunction: + # Builtins map to individual instructions + # (usually 1, but some use more) so we handle + # them differently self.handleBuiltinFunction(fn, args) return if any(fn.valueType.args, proc (arg: tuple[name: string, kind: Type]): bool = arg[1].kind == Generic): @@ -968,41 +989,19 @@ proc generateCall(self: Compiler, fn: Name, args: seq[Expression]) = self.emitFunction(fn) self.emitByte(LoadReturnAddress) let pos = self.chunk.code.len() + # We initially emit a dummy return + # address. It is patched later self.emitBytes(0.toQuad()) - for argument in args: + for argument in reversed(args): + # We pass the arguments in reverse + # because of how stack semantics + # work. They'll be fixed at runtime self.expression(argument) - self.emitByte(Call) # Creates a new call frame - var size = 2 # We start at 2 because each call frame - # contains at least 2 elements (function - # object and return address) - for name in reversed(self.names): - # Then, for each local variable - # we increase the frame size by 1 - if name.depth == self.scopeDepth: - inc(size) - self.emitBytes(size.toTriple()) - self.patchReturnAddress(pos) - - -proc generateObjCall(self: Compiler, args: seq[Expression]) = - ## Small wrapper that abstracts emitting a call instruction - ## for a given function already loaded on the operand stack - self.emitByte(PushC) # Pops the function off the operand stack onto the call stack - self.emitByte(LoadReturnAddress) - let pos = self.chunk.code.len() - self.emitBytes(0.toQuad()) - for argument in args: - self.expression(argument) - self.emitByte(Call) # Creates a new call frame - var size = 2 # We start at 2 because each call frame - # contains at least 2 elements (function - # object and return address) - for name in reversed(self.names): - # Then, for each local variable - # we increase the frame size by 1 - if name.depth == self.scopeDepth: - inc(size) - self.emitBytes(size.toTriple()) + # Creates a new call frame and jumps + # to the function's first instruction + # in the code + self.emitByte(Call) + self.emitBytes(fn.valueType.args.len().toTriple()) self.patchReturnAddress(pos) @@ -1013,23 +1012,22 @@ proc callUnaryOp(self: Compiler, fn: Name, op: UnaryExpr) = proc callBinaryOp(self: Compiler, fn: Name, op: BinaryExpr) = ## Emits the code to call a binary operator - # Pushes the return address self.generateCall(fn, @[op.a, op.b]) proc unary(self: Compiler, node: UnaryExpr) = ## Compiles unary expressions such as decimal ## and bitwise negation - let valueType = self.inferType(node.a, strictMutable=false) - let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", valueType)]), strictMutable=false) + let valueType = self.inferType(node.a) + let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", valueType)])) self.callUnaryOp(funct, node) proc binary(self: Compiler, node: BinaryExpr) = ## Compiles all binary expressions - let typeOfA = self.inferType(node.a, strictMutable=false) - let typeOfB = self.inferType(node.b, strictMutable=false) - let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", typeOfA), ("", typeOfB)]), strictMutable=false) + let typeOfA = self.inferType(node.a) + let typeOfB = self.inferType(node.b) + let funct = self.matchImpl(node.token.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: @[("", typeOfA), ("", typeOfB)])) self.callBinaryOp(funct, node) @@ -1049,8 +1047,8 @@ proc declareName(self: Compiler, node: Declaration, mutable: bool = false) = # slap myself 100 times with a sign saying "I'm dumb". Mark my words self.error("cannot declare more than 16777215 variables at a time") for name in self.findByName(node.name.token.lexeme): - if name.depth == self.scopeDepth and name.valueType.kind notin {Function, CustomType} and not name.isFunctionArgument: - # Trying to redeclare a variable in the same module is an error, but it's okay + if name.depth == self.scopeDepth and not name.isFunctionArgument: + # Trying to redeclare a variable in the same scope/context is an error, but it's okay # if it's a function argument (for example, if you want to copy a number to # mutate it) self.error(&"attempt to redeclare '{node.name.token.lexeme}', which was previously defined in '{name.owner}' at line {name.line}") @@ -1066,21 +1064,6 @@ proc declareName(self: Compiler, node: Declaration, mutable: bool = false) = line: node.token.line)) if mutable: self.names[^1].valueType.mutable = true - # We emit a jump of 0 because this may become a - # StoreHeap instruction. If they variable is - # not closed over, we'll sadly be wasting a - # VM cycle. The previous implementation used 4 no-op - # instructions, which wasted 4 times as many clock - # cycles. - # TODO: Optimize this. It's a bit tricky because - # deleting bytecode would render all of our - # jump offsets and other absolute indeces in the - # bytecode wrong - if self.scopeDepth > 0: - # Closure variables are only used in local - # scopes - self.emitByte(JumpForwards) - self.emitBytes(0.toTriple()) of NodeKind.funDecl: var node = FunDecl(node) # We declare the generics before the function so we @@ -1106,14 +1089,15 @@ proc declareName(self: Compiler, node: Declaration, mutable: bool = false) = name: node.name, isLet: false, isClosedOver: false, - line: node.token.line)) + line: node.token.line, + isFunDecl: true)) let fn = self.names[^1] var name: Name for argument in node.arguments: if self.names.high() > 16777215: self.error("cannot declare more than 16777215 variables at a time") # wait, no LoadVar? Yes! That's because when calling functions, - # arguments will already be on the stack so there's no need to + # arguments will already be on the stack, so there's no need to # load them here name = Name(depth: self.scopeDepth + 1, isPrivate: true, @@ -1138,7 +1122,7 @@ proc declareName(self: Compiler, node: Declaration, mutable: bool = false) = proc identifier(self: Compiler, node: IdentExpr) = ## Compiles access to identifiers - let s = self.resolve(node) + var s = self.resolve(node) if s.isNil(): self.error(&"reference to undeclared name '{node.token.lexeme}'") elif s.isConst: @@ -1148,11 +1132,11 @@ proc identifier(self: Compiler, node: IdentExpr) = else: self.detectClosureVariable(s) if s.valueType.kind == Function: - if not s.valueType.isBuiltinFunction: - self.emitByte(LoadFunctionObj) - self.emitBytes(s.codePos.toTriple()) - else: - self.emitByte(LoadNil) + # Functions have no runtime + # representation, so we need + # to create one on the fly + self.emitByte(LoadFunction) + self.emitBytes(s.codePos.toTriple()) elif not s.isClosedOver: # Static name resolution, loads value at index in the stack. Very fast. Much wow. self.emitByte(LoadVar) @@ -1163,7 +1147,7 @@ proc identifier(self: Compiler, node: IdentExpr) = # 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) self.emitByte(LoadClosure) - self.emitBytes(self.closedOver.high().toTriple()) + self.emitBytes(self.getClosurePos(s.name).toTriple()) @@ -1173,7 +1157,7 @@ proc assignment(self: Compiler, node: ASTNode) = of assignExpr: let node = AssignExpr(node) let name = IdentExpr(node.name) - let r = self.resolve(name) + var r = self.resolve(name) if r.isNil(): self.error(&"assignment to undeclared name '{name.token.lexeme}'") elif r.isConst: @@ -1207,7 +1191,7 @@ proc beginScope(self: Compiler) = inc(self.scopeDepth) -proc endScope(self: Compiler, deleteNames: bool = true, fromFunc: bool = false) = +proc endScope(self: Compiler) = ## Ends the current local scope if self.scopeDepth < 0: self.error("cannot call endScope with scopeDepth < 0 (This is an internal error and most likely a bug)") @@ -1216,11 +1200,7 @@ proc endScope(self: Compiler, deleteNames: bool = true, fromFunc: bool = false) for name in self.names: if name.depth > self.scopeDepth: names.add(name) - if not self.enableOptimizations and not fromFunc: - # All variables with a scope depth larger than the current one - # are now out of scope. Begone, you're now homeless! - self.emitByte(PopC) - if self.enableOptimizations and len(names) > 1 and not fromFunc: + if len(names) > 1: # If we're popping less than 65535 variables, then # we can emit a PopN instruction. This is true for # 99.99999% of the use cases of the language (who the @@ -1234,32 +1214,25 @@ proc endScope(self: Compiler, deleteNames: bool = true, fromFunc: bool = false) for i in countdown(self.names.high(), len(names) - uint16.high().int()): if self.names[i].depth > self.scopeDepth: self.emitByte(PopC) - elif len(names) == 1 and not fromFunc: + elif len(names) == 1: # We only emit PopN if we're popping more than one value self.emitByte(PopC) # This seems *really* slow, but # what else should I do? Nim doesn't # allow the removal of items during # seq iteration so ¯\_(ツ)_/¯ - if deleteNames: - var idx = 0 - while idx < self.names.len(): - for name in names: - if self.names[idx] == name: - self.names.delete(idx) - inc(idx) - idx = 0 - while idx < self.closedOver.len(): - for name in names: - if name.isClosedOver: - self.closedOver.delete(idx) - self.emitByte(PopClosure) - inc(idx) + var idx = 0 + while idx < self.names.len(): + for name in names: + if self.names[idx] == name: + self.names.delete(idx) + inc(idx) + proc blockStmt(self: Compiler, node: BlockStmt) = ## Compiles block statements, which create a new - ## local scope. + ## local scope self.beginScope() for decl in node.code: self.declaration(decl) @@ -1270,25 +1243,13 @@ proc ifStmt(self: Compiler, node: IfStmt) = ## Compiles if/else statements for conditional ## execution of code var cond = self.inferType(node.condition) + self.expression(node.condition) if not self.compareTypes(cond, Type(kind: Bool)): if cond.isNil(): - if node.condition.kind == identExpr: - self.error(&"reference to undeclared identifier '{IdentExpr(node.condition).name.lexeme}'") - elif node.condition.kind == callExpr and CallExpr(node.condition).callee.kind == identExpr: - self.error(&"reference to undeclared identifier '{IdentExpr(CallExpr(node.condition).callee).name.lexeme}'") - else: - self.error(&"expecting value of type 'bool', but expression has no type") + self.error(&"expecting value of type 'bool', but expression has no type") else: self.error(&"expecting value of type 'bool', got '{self.typeToStr(cond)}' instead") - self.expression(node.condition) - var jumpCode: OpCode - if self.enableOptimizations: - jumpCode = JumpIfFalsePop - else: - jumpCode = JumpIfFalse - let jump = self.emitJump(jumpCode) - if not self.enableOptimizations: - self.emitByte(Pop) + let jump = self.emitJump(JumpIfFalsePop) self.statement(node.thenBranch) let jump2 = self.emitJump(JumpForwards) self.patchJump(jump) @@ -1310,51 +1271,23 @@ proc emitLoop(self: Compiler, begin: int) = proc whileStmt(self: Compiler, node: WhileStmt) = ## Compiles C-style while loops and ## desugared C-style for loops - let start = self.chunk.code.len() + var cond = self.inferType(node.condition) self.expression(node.condition) - var jump: int - if self.enableOptimizations: - jump = self.emitJump(JumpIfFalsePop) - else: - jump = self.emitJump(JumpIfFalse) - self.emitByte(Pop) + if not self.compareTypes(cond, Type(kind: Bool)): + if cond.isNil(): + self.error(&"expecting value of type 'bool', but expression has no type") + else: + self.error(&"expecting value of type 'bool', got '{self.typeToStr(cond)}' instead") + let start = self.chunk.code.len() + var jump = self.emitJump(JumpIfFalsePop) self.statement(node.body) self.patchJump(jump) self.emitLoop(start) -proc isPure(self: Compiler, node: ASTNode): bool = - ## Checks if a function has any side effects - var pragmas: seq[Pragma] - case node.kind: - of lambdaExpr: - pragmas = LambdaExpr(node).pragmas - else: - pragmas = Declaration(node).pragmas - if pragmas.len() == 0: - return false - for pragma in pragmas: - if pragma.name.name.lexeme == "pure": - return true - return false - - proc checkCallIsPure(self: Compiler, node: ASTnode): bool = ## Checks if a call has any side effects - if not self.isPure(node): - return true - var pragmas: seq[Pragma] - case node.kind: - of lambdaExpr: - pragmas = LambdaExpr(node).pragmas - else: - pragmas = Declaration(node).pragmas - if pragmas.len() == 0: - return false - for pragma in pragmas: - if pragma.name.name.lexeme == "pure": - return true - return false + return true # TODO proc callExpr(self: Compiler, node: CallExpr) = @@ -1362,7 +1295,6 @@ proc callExpr(self: Compiler, node: CallExpr) = var args: seq[tuple[name: string, kind: Type]] = @[] var argExpr: seq[Expression] = @[] var kind: Type - var strictMutable = true # TODO: Keyword arguments for i, argument in node.arguments.positionals: kind = self.inferType(argument) @@ -1370,8 +1302,6 @@ proc callExpr(self: Compiler, node: CallExpr) = if argument.kind == identExpr: self.error(&"reference to undeclared identifier '{IdentExpr(argument).name.lexeme}'") self.error(&"cannot infer the type of argument {i + 1} in function call") - if kind.mutable: - strictMutable = false args.add(("", kind)) argExpr.add(argument) for argument in node.arguments.keyword: @@ -1381,7 +1311,7 @@ proc callExpr(self: Compiler, node: CallExpr) = var funct: Name case node.callee.kind: of identExpr: - funct = self.matchImpl(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: args), strictMutable) + funct = self.matchImpl(IdentExpr(node.callee).name.lexeme, Type(kind: Function, returnType: Type(kind: Any), args: args)) of NodeKind.callExpr: var node = node.callee while node.kind == callExpr: @@ -1394,13 +1324,15 @@ proc callExpr(self: Compiler, node: CallExpr) = self.error(&"expression has no type") else: self.error(&"object of type '{self.typeToStr(typ)}' is not callable") - if not funct.isNil(): - if funct.valueType.isBuiltinFunction: - self.handleBuiltinFunction(funct, argExpr) - else: - self.generateCall(funct, argExpr) + if any(funct.valueType.args, proc (arg: tuple[name: string, kind: Type]): bool = arg[1].kind == Generic): + # The function has generic arguments! We need to compile a version + # of it with the right type data + self.funDecl(nil, funct, argExpr) + # TODO: What next? + elif funct.valueType.isBuiltinFunction: + self.handleBuiltinFunction(funct, argExpr) else: - self.generateObjCall(argExpr) + self.generateCall(funct, argExpr) if self.scopeDepth > 0 and not self.checkCallIsPure(node.callee): if self.currentFunction.name != "": self.error(&"cannot make sure that calls to '{self.currentFunction.name}' are side-effect free") @@ -1477,9 +1409,9 @@ proc endFunctionBeforeReturn(self: Compiler) = ## its return instruction var popped = 0 for name in self.names: - if name.depth == self.scopeDepth and name.valueType.kind notin {Function, Generic}: + if name.depth == self.scopeDepth and name.valueType.kind notin {Function, Generic, CustomType} and not name.isClosedOver: inc(popped) - if self.enableOptimizations and popped > 1: + if popped > 1: self.emitByte(PopN) self.emitBytes(popped.toDouble()) dec(popped, uint16.high().int) @@ -1491,17 +1423,19 @@ proc endFunctionBeforeReturn(self: Compiler) = proc returnStmt(self: Compiler, node: ReturnStmt) = ## Compiles return statements let actual = self.inferType(node.value) - let expected = self.currentFunction - if actual.isNil() and not expected.returnType.isNil(): + var expected = self.currentFunction.returnType + if not expected.isNil() and expected.kind == Generic: + expected = actual + if actual.isNil() and not expected.isNil(): if not node.value.isNil(): if node.value.kind == identExpr: self.error(&"reference to undeclared identifier '{node.value.token.lexeme}'") elif node.value.kind == callExpr and CallExpr(node.value).callee.kind == identExpr: self.error(&"call to undeclared function '{CallExpr(node.value).callee.token.lexeme}'") - self.error(&"expected return value of type '{self.typeToStr(expected.returnType)}', but expression has no type") - elif expected.returnType.isNil() and not actual.isNil(): - self.error("non-empty return statement is not allowed in void functions") - elif not self.compareTypes(actual, expected.returnType): + self.error(&"expected return value of type '{self.typeToStr(expected)}', but expression has no type") + elif expected.isNil() and not actual.isNil(): + self.error("empty return statement is only allowed in void functions") + elif not self.compareTypes(actual, expected): self.error(&"expected return value of type '{self.typeToStr(expected)}', got '{self.typeToStr(actual)}' instead") if not node.value.isNil(): self.expression(node.value) @@ -1653,8 +1587,10 @@ proc varDecl(self: Compiler, node: VarDecl) = self.error(&"expected value of type '{self.typeToStr(expected)}', but '{node.name.token.lexeme}' is of type '{self.typeToStr(actual)}'") self.expression(node.value) self.declareName(node, mutable=node.token.kind == TokenType.Var) + var r = self.names[^1] + self.detectClosureVariable(r, decl=true) self.emitByte(StoreVar) - self.emitBytes(self.names.len().toTriple()) + self.emitBytes((self.getStackPos(r.name) + 1).toTriple()) proc typeDecl(self: Compiler, node: TypeDecl) = @@ -1708,12 +1644,16 @@ proc dispatchPragmas(self: Compiler, node: ASTnode) = proc fixGenericFunc(self: Compiler, name: Name, args: seq[Expression]): Type = ## Specializes generic arguments in functions - var fn = name.valueType - result = fn.deepCopy() - var node = fn.fun + var fn = name.valueType.deepCopy() + result = fn + var typ: Type for i in 0..args.high(): if fn.args[i].kind.kind == Generic: - self.resolve(fn.args[i].name).valueType = self.inferType(args[i]) + typ = self.inferType(args[i]) + fn.args[i].kind = typ + self.resolve(fn.args[i].name).valueType = typ + if fn.args[i].kind.isNil(): + self.error(&"cannot specialize generic function: argument {i + 1} has no type") proc funDecl(self: Compiler, node: FunDecl, fn: Name = nil, args: seq[Expression] = @[]) = @@ -1728,9 +1668,12 @@ proc funDecl(self: Compiler, node: FunDecl, fn: Name = nil, args: seq[Expression self.dispatchPragmas(node) var node = node var fn = if fn.isNil(): self.names[^(node.arguments.len() + 1)] else: fn - if fn.valueType.returnType.isNil(): - self.error(&"cannot infer the type of '{node.returnType.token.lexeme}'") - if not fn.valueType.isBuiltinFunction: + if fn.valueType.isBuiltinFunction: + # We take the arguments off of our name list + # because they become temporaries on the stack + for i in self.names.high() - node.arguments.high()..self.names.high(): + self.names.delete(i) + else: var function = self.currentFunction var jmp: int # Builtin functions map to a single @@ -1747,11 +1690,6 @@ proc funDecl(self: Compiler, node: FunDecl, fn: Name = nil, args: seq[Expression fn.codePos = self.chunk.code.len() # We store the current function self.currentFunction = fn.valueType - let argLen = if node.isNil(): fn.valueType.args.len() else: node.arguments.len() - for _ in 0.. 0: + path &= "/" + path &= splitFile(f).name & ".pbc" + serializer.dumpFile(compiled, f, path) + serialized = serializer.loadFile(path) when debugSerializer: var hashMatches = computeSHA256(input).toHex().toLowerAscii() == serialized.fileHash styledEcho fgCyan, "Serialization step: " diff --git a/src/util/debugger.nim b/src/util/debugger.nim index 51d5289..62aaa97 100644 --- a/src/util/debugger.nim +++ b/src/util/debugger.nim @@ -63,8 +63,8 @@ proc printInstruction(instruction: OpCode, newline: bool = false) = proc checkFrameStart(self: Debugger, n: int) = - ## Todo: Checking frame end offsets needs - ## fixes + ## Checks if a call frame begins at the given + ## bytecode offset for i, e in self.cfiData: if n == e.start and not (e.started or e.stopped): e.started = true @@ -76,6 +76,8 @@ proc checkFrameStart(self: Debugger, n: int) = proc checkFrameEnd(self: Debugger, n: int) = + ## Checks if a call frame ends at the given + ## bytecode offset for i, e in self.cfiData: if n == e.stop and e.started and not e.stopped: e.stopped = true @@ -83,6 +85,7 @@ proc checkFrameEnd(self: Debugger, n: int) = proc simpleInstruction(self: Debugger, instruction: OpCode) = + ## Debugs simple instructions printInstruction(instruction, true) self.current += 1 if instruction == Return: @@ -152,7 +155,7 @@ proc loadAddressInstruction(self: Debugger, instruction: OpCode) = var address = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3], self.chunk.code[self.current + 4]].fromQuad() printInstruction(instruction) - styledEcho fgGreen, &" loads address ", fgYellow, $address + styledEcho fgGreen, &", loads address ", fgYellow, $address self.current += 5