From ff0ae8fcbacb25a217a4481bfb040e7d35f9fee1 Mon Sep 17 00:00:00 2001 From: Mattia Giambirtone Date: Mon, 1 Aug 2022 10:36:06 +0200 Subject: [PATCH] Various fixes to frames, recursion, and more. Removed stack bottom from CFI data. Added comparison opcode for fib test as well as a clock opcode --- docs/bytecode.md | 8 +- src/backend/types.nim | 5 ++ src/backend/vm.nim | 82 +++++++++---------- src/frontend/compiler.nim | 144 +++++++++++++-------------------- src/frontend/meta/bytecode.nim | 10 ++- src/util/debugger.nim | 7 +- 6 files changed, 111 insertions(+), 145 deletions(-) diff --git a/docs/bytecode.md b/docs/bytecode.md index 387ef5c..c70aaf1 100644 --- a/docs/bytecode.md +++ b/docs/bytecode.md @@ -60,21 +60,19 @@ below: The CFI segment (where CFI stands for **C**all **F**rame **I**nformation), contains details about each function in the original file. The segment's size is fixed and is encoded at the beginning as a sequence of 4 bytes (i.e. a single 32 bit integer). -The data -in this segment can be decoded as explained in [this file](../src/frontend/meta/bytecode.nim#L41), which is quoted +The data in this segment can be decoded as explained in [this file](../src/frontend/meta/bytecode.nim#L41), which is quoted below: ``` [...] ## cfi represents Call Frame Information and encodes the following information: ## - Function name -## - Stack bottom ## - Argument count +## - Function boundaries ## The encoding for CFI data is the following: ## - First, the position into the bytecode where the function begins is encoded (as a 3 byte integer) ## - Second, the position into the bytecode where the function ends is encoded (as a 3 byte integer) -## - Then, the frame's stack bottom is encoded as a 3 byte integer -## - After the frame's stack bottom follows the argument count as a 1 byte integer +## - After that follows the argument count as a 1 byte integer ## - Lastly, the function's name (optional) is encoded in ASCII, prepended with ## its size as a 2-byte integer [...] diff --git a/src/backend/types.nim b/src/backend/types.nim index a924142..6894149 100644 --- a/src/backend/types.nim +++ b/src/backend/types.nim @@ -103,5 +103,10 @@ proc `$`*(self: PeonObject): string = result = "typevar" of String: result = self.str + of Bool: + if self.boolean: + result = "true" + else: + result = "false" else: discard \ No newline at end of file diff --git a/src/backend/vm.nim b/src/backend/vm.nim index 2b3a0ca..9f07ef5 100644 --- a/src/backend/vm.nim +++ b/src/backend/vm.nim @@ -13,8 +13,9 @@ # limitations under the License. ## The Peon runtime environment -import strutils -import strformat + +import std/monotimes + import types import ../config @@ -23,6 +24,10 @@ import ../util/multibyte export types +when DEBUG_TRACE_VM: + import strutils + import strformat + type PeonVM* = ref object @@ -396,14 +401,18 @@ proc dispatch*(self: PeonVM) = # in a hidden function, so this # will also exit the VM if we're # at the end of the program - let ret = self.popc() + while self.calls.len() > self.frames[^1] + 2: + # Discards the function's local variables, + # if there is any + discard self.popc() + let ret = self.popc() # Return address discard self.popc() # Function object if self.readByte() == 1: # Function is non-void! self.push(self.results.pop()) else: discard self.results.pop() - # Discard a stack frame + # Discard the topmost stack frame discard self.frames.pop() if self.frames.len() == 0: # End of the program! @@ -489,106 +498,87 @@ proc dispatch*(self: PeonVM) = else: discard self.pop() # Built-in operations on primitive types + # Note how of AddInt64: self.push(PeonObject(kind: Int64, long: self.pop().long + self.pop().long)) of SubInt64: - let second = self.pop() - self.push(PeonObject(kind: Int64, long: self.pop().long - second.long)) + self.push(PeonObject(kind: Int64, long: self.pop().long - self.pop().long)) of MulInt64: self.push(PeonObject(kind: Int64, long: self.pop().long * self.pop().long)) of DivInt64: - let second = self.pop() - self.push(PeonObject(kind: Int64, long: self.pop().long div second.long)) + self.push(PeonObject(kind: Int64, long: self.pop().long div self.pop().long)) of AddUInt64: self.push(PeonObject(kind: UInt64, uLong: self.pop().uLong + self.pop().uLong)) of SubUInt64: - let second = self.pop() - self.push(PeonObject(kind: UInt64, uLong: self.pop().uLong - second.uLong)) + self.push(PeonObject(kind: UInt64, uLong: self.pop().uLong - self.pop().uLong)) of MulUInt64: self.push(PeonObject(kind: UInt64, uLong: self.pop().uLong * self.pop().uLong)) of DivUInt64: - let second = self.pop() - self.push(PeonObject(kind: UInt64, uLong: self.pop().uLong div second.uLong)) + self.push(PeonObject(kind: UInt64, uLong: self.pop().uLong div self.pop().uLong)) of AddInt32: self.push(PeonObject(kind: Int32, `int`: self.pop().`int` + self.pop().`int`)) of SubInt32: - let second = self.pop() - self.push(PeonObject(kind: Int32, `int`: self.pop().`int` - second.`int`)) + self.push(PeonObject(kind: Int32, `int`: self.pop().`int` - self.pop().`int`)) of MulInt32: self.push(PeonObject(kind: Int32, `int`: self.pop().`int` * self.pop().`int`)) of DivInt32: - let second = self.pop() - self.push(PeonObject(kind: Int32, `int`: self.pop().`int` div second.`int`)) + self.push(PeonObject(kind: Int32, `int`: self.pop().`int` div self.pop().`int`)) of AddUInt32: self.push(PeonObject(kind: UInt32, uInt: self.pop().uInt + self.pop().uInt)) of SubUInt32: - let second = self.pop() - self.push(PeonObject(kind: UInt32, uInt: self.pop().uInt - second.uInt)) + self.push(PeonObject(kind: UInt32, uInt: self.pop().uInt - self.pop().uInt)) of MulUInt32: self.push(PeonObject(kind: UInt32, uInt: self.pop().uInt * self.pop().uInt)) of DivUInt32: - let second = self.pop() - self.push(PeonObject(kind: UInt32, uInt: self.pop().uInt div second.uInt)) + self.push(PeonObject(kind: UInt32, uInt: self.pop().uInt div self.pop().uInt)) of AddInt16: self.push(PeonObject(kind: Int16, short: self.pop().short + self.pop().short)) of SubInt16: - let second = self.pop() - self.push(PeonObject(kind: Int16, short: self.pop().short - second.short)) + self.push(PeonObject(kind: Int16, short: self.pop().short - self.pop().short)) of MulInt16: self.push(PeonObject(kind: Int16, short: self.pop().short * self.pop().short)) of DivInt16: - let second = self.pop() - self.push(PeonObject(kind: Int16, short: self.pop().short div second.short)) + self.push(PeonObject(kind: Int16, short: self.pop().short div self.pop().short)) of AddUInt16: self.push(PeonObject(kind: UInt16, uShort: self.pop().uShort + self.pop().uShort)) of SubUInt16: - let second = self.pop() - self.push(PeonObject(kind: UInt16, uShort: self.pop().uShort - second.uShort)) + self.push(PeonObject(kind: UInt16, uShort: self.pop().uShort - self.pop().uShort)) of MulUInt16: self.push(PeonObject(kind: UInt16, uShort: self.pop().uShort * self.pop().uShort)) of DivUInt16: - let second = self.pop() - self.push(PeonObject(kind: UInt16, uShort: self.pop().uShort div second.uShort)) + self.push(PeonObject(kind: UInt16, uShort: self.pop().uShort div self.pop().uShort)) of AddInt8: self.push(PeonObject(kind: Int8, tiny: self.pop().tiny + self.pop().tiny)) of SubInt8: - let second = self.pop() - self.push(PeonObject(kind: Int8, tiny: self.pop().tiny - second.tiny)) + self.push(PeonObject(kind: Int8, tiny: self.pop().tiny - self.pop().tiny)) of MulInt8: self.push(PeonObject(kind: Int8, tiny: self.pop().tiny * self.pop().tiny)) of DivInt8: - let second = self.pop() - self.push(PeonObject(kind: Int8, tiny: self.pop().tiny div second.tiny)) + self.push(PeonObject(kind: Int8, tiny: self.pop().tiny div self.pop().tiny)) of AddUInt8: self.push(PeonObject(kind: UInt8, uTiny: self.pop().uTiny + self.pop().uTiny)) of SubUInt8: - let second = self.pop() - self.push(PeonObject(kind: UInt8, uTiny: self.pop().uTiny - second.uTiny)) + self.push(PeonObject(kind: UInt8, uTiny: self.pop().uTiny - self.pop().uTiny)) of MulUInt8: self.push(PeonObject(kind: UInt8, uTiny: self.pop().uTiny * self.pop().uTiny)) of DivUInt8: - let second = self.pop() - self.push(PeonObject(kind: UInt8, uTiny: self.pop().uTiny div second.uTiny)) + self.push(PeonObject(kind: UInt8, uTiny: self.pop().uTiny div self.pop().uTiny)) of AddFloat64: self.push(PeonObject(kind: Float64, `float`: self.pop().`float` + self.pop().`float`)) of SubFloat64: - let second = self.pop() - self.push(PeonObject(kind: Float64, `float`: self.pop().`float` - second.`float`)) + self.push(PeonObject(kind: Float64, `float`: self.pop().`float` - self.pop().`float`)) of MulFloat64: self.push(PeonObject(kind: Float64, `float`: self.pop().`float` * self.pop().`float`)) of DivFloat64: - let second = self.pop() - self.push(PeonObject(kind: Float64, `float`: self.pop().`float` / second.`float`)) + self.push(PeonObject(kind: Float64, `float`: self.pop().`float` / self.pop().`float`)) of AddFloat32: self.push(PeonObject(kind: Float32, halfFloat: self.pop().halfFloat + self.pop().halfFloat)) of SubFloat32: - let second = self.pop() - self.push(PeonObject(kind: Float32, halfFloat: self.pop().halfFloat - second.halfFloat)) + self.push(PeonObject(kind: Float32, halfFloat: self.pop().halfFloat - self.pop().halfFloat)) of MulFloat32: self.push(PeonObject(kind: Float32, halfFloat: self.pop().halfFloat * self.pop().halfFloat)) of DivFloat32: - let second = self.pop() - self.push(PeonObject(kind: Float32, halfFloat: self.pop().halfFloat / second.halfFloat)) + self.push(PeonObject(kind: Float32, halfFloat: self.pop().halfFloat / self.pop().halfFloat)) of NegInt64: self.push(PeonObject(kind: Int64, long: -self.pop().long)) of NegInt32: @@ -601,6 +591,10 @@ proc dispatch*(self: PeonVM) = self.push(PeonObject(kind: Float64, `float`: -self.pop().`float`)) of NegFloat32: self.push(PeonObject(kind: Float32, halfFloat: -self.pop().halfFloat)) + of LessThanInt64: + self.push(PeonObject(kind: Bool, boolean: self.pop().long < self.pop().long)) + of SysClock64: + self.push(PeonObject(kind: Float64, `float`: getMonoTime().ticks().float() / 1_000_000_000)) else: discard diff --git a/src/frontend/compiler.nim b/src/frontend/compiler.nim index a81916a..55c9041 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -373,27 +373,23 @@ proc resolve(self: Compiler, name: IdentExpr, return nil -proc getStackPos(self: Compiler, name: IdentExpr, depth: int = self.scopeDepth): int = +proc getStackPos(self: Compiler, name: Name, depth: int = self.scopeDepth): int = ## Returns the predicted call stack position of a given name, relative ## to the current frame var found = false - result = self.names.len() - for variable in reversed(self.names): + result = 2 + for variable in self.names: if variable.isFunDecl: continue - dec(result) - if name.name.lexeme == variable.name.name.lexeme: - if variable.isPrivate and variable.owner != self.currentModule: - continue - else: - found = true - inc(result) - break + if name == variable: + found = true + break + inc(result) if not found: return -1 -proc getClosurePos(self: Compiler, name: IdentExpr): int = +proc getClosurePos(self: Compiler, name: Name): 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 @@ -401,12 +397,9 @@ proc getClosurePos(self: Compiler, name: IdentExpr): int = result = self.closedOver.high() var found = false for variable in reversed(self.closedOver): - if name.name.lexeme == variable.name.name.lexeme: - if variable.isPrivate and variable.owner != self.currentModule: - continue - else: - found = true - break + if name == variable: + found = true + break dec(result) if not found: return -1 @@ -446,18 +439,16 @@ proc detectClosureVariable(self: Compiler, name: var Name, depth: int = self.sco # 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) - if self.closedOver.len() >= 16777216: - self.error("too many consecutive closed-over variables (max is 16777215)") - name.isClosedOver = true if not name.isFunctionArgument: # We handle closed-over function arguments later + self.closedOver.add(name) + if self.closedOver.len() >= 16777216: + self.error("too many consecutive closed-over variables (max is 16777215)") + name.isClosedOver = true self.chunk.code[name.codePos] = StoreClosure.uint8() for i, b in self.closedOver.high().toTriple(): self.chunk.code[name.codePos + i + 1] = b - else: - discard - # self.error("it is currently not possible to close over function arguments") + proc compareTypes(self: Compiler, a, b: Type): bool = @@ -613,14 +604,15 @@ proc inferType(self: Compiler, node: Expression): Type = else: result = node.name.lexeme.toIntrinsic() of unaryExpr: - return self.inferType(UnaryExpr(node).a) + let f = self.inferType(newIdentExpr(UnaryExpr(node).operator)) + if f.isNil(): + return f + return f.returnType of binaryExpr: - let node = BinaryExpr(node) - var a = self.inferType(node.a) - var b = self.inferType(node.b) - if not self.compareTypes(a, b): - return nil - return a + let f = self.inferType(newIdentExpr(BinaryExpr(node).operator)) + if f.isNil(): + return f + return f.returnType of {intExpr, hexExpr, binExpr, octExpr, strExpr, falseExpr, trueExpr, infExpr, nanExpr, floatExpr, nilExpr @@ -655,6 +647,8 @@ proc inferType(self: Compiler, node: Expression): Type = result = Type(kind: Reference, value: self.inferType(Ref(node).value)) of ptrExpr: result = Type(kind: Pointer, value: self.inferType(Ptr(node).value)) + of groupingExpr: + result = self.inferType(GroupingExpr(node).expression) else: discard # Unreachable @@ -787,7 +781,7 @@ proc check(self: Compiler, term: Expression, kind: Type) = self.error(&"call to undeclared function '{CallExpr(term).callee.token.lexeme}'") self.error(&"expecting value of type '{self.typeToStr(kind)}', but expression has no type") elif not self.compareTypes(k, kind): - self.error(&"expecting value of type '{self.typeToStr(k)}', got '{self.typeToStr(k)}' instead") + self.error(&"expecting value of type '{self.typeToStr(kind)}', got '{self.typeToStr(k)}' instead") @@ -803,10 +797,10 @@ proc emitFunction(self: Compiler, name: Name) = # load it elif not name.isClosedOver: self.emitByte(LoadVar) - self.emitBytes(self.getStackPos(name.name).toTriple()) + self.emitBytes(self.getStackPos(name).toTriple()) else: self.emitByte(LoadClosure) - self.emitBytes(self.getClosurePos(name.name).toTriple()) + self.emitBytes(self.getClosurePos(name).toTriple()) ## End of utility functions @@ -898,7 +892,9 @@ proc handleBuiltinFunction(self: Compiler, fn: Name, args: seq[Expression]) = if fn.valueType.builtinOp notin ["GenericLogicalOr", "GenericLogicalAnd"]: if len(args) == 2: self.expression(args[1]) - self.expression(args[0]) + self.expression(args[0]) + elif len(args) == 1: + self.expression(args[0]) case fn.valueType.builtinOp: of "AddInt64": self.emitByte(AddInt64) @@ -965,13 +961,13 @@ proc handleBuiltinFunction(self: Compiler, fn: Name, args: seq[Expression]) = of "MulUInt8": self.emitByte(MulUInt8) of "AddFloat64": - self.emitByte(AddInt8) + self.emitByte(AddFloat64) of "SubFloat64": - self.emitByte(SubInt8) + self.emitByte(SubFloat64) of "DivFloat64": - self.emitByte(DivInt8) + self.emitByte(DivFloat64) of "MulFloat64": - self.emitByte(MulInt8) + self.emitByte(MulFloat64) of "AddFloat32": self.emitByte(AddFloat32) of "SubFloat32": @@ -1002,6 +998,10 @@ proc handleBuiltinFunction(self: Compiler, fn: Name, args: seq[Expression]) = var jump = self.emitJump(JumpIfFalseOrPop) self.expression(args[1]) self.patchJump(jump) + of "LessThanInt64": + self.emitByte(LessThanInt64) + of "SysClock64": + self.emitByte(SysClock64) else: self.error(&"unknown built-in: '{fn.valueType.builtinOp}'") @@ -1186,13 +1186,13 @@ proc identifier(self: Compiler, node: IdentExpr) = # Static name resolution, loads value at index in the stack. Very fast. Much wow. self.emitByte(LoadVar) # No need to check for -1 here: we already did a nil-check above! - self.emitBytes(self.getStackPos(s.name).toTriple()) + self.emitBytes(self.getStackPos(s).toTriple()) else: # 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) self.emitByte(LoadClosure) - self.emitBytes(self.getClosurePos(s.name).toTriple()) + self.emitBytes(self.getClosurePos(s).toTriple()) proc assignment(self: Compiler, node: ASTNode) = @@ -1212,13 +1212,13 @@ proc assignment(self: Compiler, node: ASTNode) = self.detectClosureVariable(r) if not r.isClosedOver: self.emitByte(StoreVar) - self.emitBytes(self.getStackPos(name).toTriple()) + self.emitBytes(self.getStackPos(r).toTriple()) else: # 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) self.emitByte(StoreClosure) - self.emitBytes(self.getClosurePos(name).toTriple()) + self.emitBytes(self.getClosurePos(r).toTriple()) of setItemExpr: let node = SetItemExpr(node) let typ = self.inferType(node) @@ -1436,44 +1436,14 @@ proc deferStmt(self: Compiler, node: DeferStmt) = self.chunk.code.delete(i) # TODO: Do not change bytecode size -proc endFunctionBeforeReturn(self: Compiler) = - ## Emits code to clear a function's - ## stack frame right before executing - ## its return instruction - var popped = 0 - for name in self.names: - if name.depth == self.scopeDepth and name.valueType.kind notin {Generic, CustomType} and not name.isFunDecl: - inc(popped) - if popped > 1: - self.emitByte(PopN) - self.emitBytes(popped.toDouble()) - dec(popped, uint16.high().int) - while popped > 0: - self.emitByte(PopC) - dec(popped) - proc returnStmt(self: Compiler, node: ReturnStmt) = ## Compiles return statements - let actual = self.inferType(node.value) 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 name '{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)}', but expression has no type") - elif expected.isNil() and not actual.isNil(): - self.error("void function cannot return any value") - elif not self.compareTypes(actual, expected): - self.error(&"expected return value of type '{self.typeToStr(expected)}', got '{self.typeToStr(actual)}' instead") + self.check(node.value, expected) if not node.value.isNil(): self.expression(node.value) self.emitByte(OpCode.SetResult) - self.endFunctionBeforeReturn() self.emitByte(OpCode.Return) if not node.value.isNil(): self.emitByte(1) @@ -1617,7 +1587,7 @@ proc varDecl(self: Compiler, node: VarDecl) = self.expression(node.value) self.declareName(node, mutable=node.token.kind == TokenType.Var) self.emitByte(StoreVar) - self.emitBytes((self.getStackPos(self.names[^1].name) + 1).toTriple()) + self.emitBytes((self.getStackPos(self.names[^1]) + 1).toTriple()) proc typeDecl(self: Compiler, node: TypeDecl) = @@ -1683,6 +1653,7 @@ proc fixGenericFunc(self: Compiler, name: Name, args: seq[Expression]): Type = self.error(&"cannot specialize generic function: argument {i + 1} has no type") + proc funDecl(self: Compiler, node: FunDecl, fn: Name = nil, args: seq[Expression] = @[]) = ## Compiles function declarations #[if not node.isNil(): @@ -1697,18 +1668,16 @@ proc funDecl(self: Compiler, node: FunDecl, fn: Name = nil, args: seq[Expression var fn = if fn.isNil(): self.names[^(node.arguments.len() + 1)] else: fn 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 + # because they become temporaries on the stack. # Builtin functions (usually) map to a single # bytecode instruction to avoid unnecessary # overhead from peon's calling convention # This also means that peon's fast builtins # can only be relatively simple - self.frames.add(self.names.high()) + self.names = self.names[0..^node.arguments.len() + 1] + else: + var function = self.currentFunction + var jmp: int # A function's code is just compiled linearly # and then jumped over jmp = self.emitJump(JumpForwards) @@ -1776,7 +1745,6 @@ proc funDecl(self: Compiler, node: FunDecl, fn: Name = nil, args: seq[Expression # Some debugging info here 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 node.name.isNil(): self.chunk.cfi.add(fn.name.token.lexeme.len().toDouble()) @@ -1786,7 +1754,7 @@ proc funDecl(self: Compiler, node: FunDecl, fn: Name = nil, args: seq[Expression self.chunk.cfi.add(s.toBytes()) else: self.chunk.cfi.add(0.toDouble()) - # Currently defer is not functional so we + # Currently defer is not functional, so we # just pop the instructions for _ in deferStart..self.deferred.high(): discard self.deferred.pop() @@ -1834,6 +1802,7 @@ proc compile*(self: Compiler, ast: seq[Declaration], file: string): Chunk = self.currentFunction = nil self.currentModule = self.file.extractFilename() self.current = 0 + self.frames = @[0] # Every peon program has a hidden entry point in # which user code is wrapped. Think of it as if # peon is implicitly writing the main() function @@ -1853,7 +1822,8 @@ proc compile*(self: Compiler, ast: seq[Declaration], file: string): Chunk = valueType: Type(kind: Function, name: "", returnType: nil, - args: @[]), + args: @[], + ), codePos: 13, # Jump address is hardcoded name: newIdentExpr(Token(lexeme: "", kind: Identifier)), isFunDecl: true, @@ -1871,5 +1841,5 @@ proc compile*(self: Compiler, ast: seq[Declaration], file: string): Chunk = self.endScope() self.patchReturnAddress(pos) self.emitByte(OpCode.Return) - self.emitByte(0) # Entry point has no return value + self.emitByte(0) # Entry point has no return value (TODO: Add easter eggs, cuz why not) result = self.chunk diff --git a/src/frontend/meta/bytecode.nim b/src/frontend/meta/bytecode.nim index 2ffd7f6..6e8f2af 100644 --- a/src/frontend/meta/bytecode.nim +++ b/src/frontend/meta/bytecode.nim @@ -40,13 +40,12 @@ type ## the same line number multiple times and waste considerable amounts of space. ## cfi represents Call Frame Information and encodes the following information: ## - Function name - ## - Stack bottom ## - Argument count + ## - Function boundaries ## The encoding for CFI data is the following: ## - First, the position into the bytecode where the function begins is encoded (as a 3 byte integer) ## - Second, the position into the bytecode where the function ends is encoded (as a 3 byte integer) - ## - Then, the frame's stack bottom is encoded as a 3 byte integer - ## - After the frame's stack bottom follows the argument count as a 1 byte integer + ## - After that follows the argument count as a 1 byte integer ## - Lastly, the function's name (optional) is encoded in ASCII, prepended with ## its size as a 2-byte integer consts*: seq[uint8] @@ -137,6 +136,8 @@ type SubFloat32, DivFloat32, MulFloat32, + LessThanInt64, + SysClock64, ## Basic stack operations Pop, # Pops an element off the stack and discards it PopRepl, # Same as Pop, but also prints the value of what's popped (used in REPL mode) @@ -198,7 +199,8 @@ const simpleInstructions* = {Return, LoadNil, SubFloat64, DivFloat64, MulFloat64, AddFloat32, SubFloat32, DivFloat32, MulFloat32, NegFloat32, NegFloat64, - } + LessThanInt64, SysClock64 + } # 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 f64b5fd..b921268 100644 --- a/src/util/debugger.nim +++ b/src/util/debugger.nim @@ -71,7 +71,6 @@ proc checkFrameStart(self: Debugger, n: int) = 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 - styledEcho fgGreen, "\t- Frame bottom: ", fgYellow, $e.bottom styledEcho fgGreen, "\t- Argument count: ", fgYellow, $e.argc @@ -217,7 +216,7 @@ proc disassembleInstruction*(self: Debugger) = proc parseCFIData(self: Debugger) = ## Parses CFI information in the chunk var - start, stop, bottom, argc: int + start, stop, argc: int name: string idx = 0 size = 0 @@ -226,15 +225,13 @@ proc parseCFIData(self: Debugger) = idx += 3 stop = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1], self.chunk.cfi[idx + 2]].fromTriple()) idx += 3 - bottom = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1], self.chunk.cfi[idx + 2]].fromTriple()) - idx += 3 argc = int(self.chunk.cfi[idx]) inc(idx) size = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1]].fromDouble()) idx += 2 name = self.chunk.cfi[idx..