From 6116c127c60b95bdfd0ae226f1eefe7a9d3a6355 Mon Sep 17 00:00:00 2001 From: Mattia Giambirtone Date: Sun, 10 Jul 2022 13:19:57 +0200 Subject: [PATCH] Moved for peon types to its own module, added support for negating primitives in the compiler and for floats in the VM, added some more docs to the VM and restored the old empty-jump mechanism for closure declaration. Minor changes to endScope --- src/backend/types.nim | 43 +++++++++++++++++++ src/backend/vm.nim | 76 +++++++++++++--------------------- src/frontend/compiler.nim | 36 +++++++++++++--- src/frontend/meta/bytecode.nim | 6 ++- 4 files changed, 106 insertions(+), 55 deletions(-) diff --git a/src/backend/types.nim b/src/backend/types.nim index 0688218..a924142 100644 --- a/src/backend/types.nim +++ b/src/backend/types.nim @@ -11,6 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import strformat +import strutils type @@ -62,3 +64,44 @@ type value*: ptr PeonObject else: discard # TODO + + +proc `$`*(self: PeonObject): string = + ## Returns a string representation + ## of a peon object + 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})" + of CustomType: + result = "typevar" + of String: + result = self.str + else: + discard \ No newline at end of file diff --git a/src/backend/vm.nim b/src/backend/vm.nim index db55717..2b3a0ca 100644 --- a/src/backend/vm.nim +++ b/src/backend/vm.nim @@ -23,6 +23,7 @@ import ../util/multibyte export types + type PeonVM* = ref object ## The Peon Virtual Machine @@ -58,7 +59,7 @@ proc newPeonVM*: PeonVM = result.initCache() -## Getters for singleton types (they are cached!) +# Getters for singleton types (they are cached!) proc getNil*(self: PeonVM): PeonObject {.inline.} = self.cache[0] @@ -74,10 +75,10 @@ proc getInf*(self: PeonVM, positive: bool): PeonObject {.inline.} = proc getNan*(self: PeonVM): PeonObject {.inline.} = self.cache[5] -## Stack primitives. Note: all stack accessing that goes -## through the get/set wrappers is frame-relative, meaning -## that the index is added to the current stack frame's -## bottom to obtain an absolute stack index. +# Stack primitives. Note: all stack accessing that goes +# through the get(c)/set(c)/peek(c) wrappers is frame-relative, +# meaning that the index is added to the current stack frame's +# bottom to obtain an absolute stack index. proc push(self: PeonVM, obj: PeonObject) = ## Pushes a Peon object onto the @@ -111,7 +112,7 @@ proc popc(self: PeonVM): PeonObject = return self.calls.pop() -proc peekc(self: PeonVM, distance: int = 0): PeonObject = +proc peekc(self: PeonVM, distance: int = 0): PeonObject {.used.} = ## Returns the Peon object at the ## given distance from the top of ## the call stack without consuming it @@ -131,8 +132,8 @@ proc setc(self: PeonVM, idx: int, val: PeonObject) = ## frames self.calls[idx + self.frames[^1]] = val -## Byte-level primitives to read/decode -## bytecode +# Byte-level primitives to read and decode +# bytecode proc readByte(self: PeonVM): uint8 = ## Reads a single byte from the @@ -168,6 +169,9 @@ proc readUInt(self: PeonVM): uint32 = return uint32([self.readByte(), self.readByte(), self.readByte(), self.readByte()].fromQuad()) +# Functions to read primitives from the chunk's +# constants table + proc constReadInt64(self: PeonVM, idx: int): PeonObject = ## Reads a constant from the ## chunk's constant table and @@ -287,45 +291,11 @@ 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 while true: + {.computedgoto.} # https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma when DEBUG_TRACE_VM: echo &"IP: {self.ip}" echo &"Instruction: {OpCode(self.chunk.code[self.ip])}" @@ -343,7 +313,7 @@ proc dispatch*(self: PeonVM) = discard readLine stdin instruction = OpCode(self.readByte()) case instruction: - # Constant loading + # Constant loading instructions of LoadTrue: self.push(self.getBool(true)) of LoadFalse: @@ -381,8 +351,8 @@ proc dispatch*(self: PeonVM) = # instruction pointer self.push(PeonObject(kind: Function, ip: self.readLong())) of LoadReturnAddress: - # Loads a 32-bit unsigned integer. Used to load function - # return addresses + # Loads a 32-bit unsigned integer onto the operand stack. + # Used to load function return addresses self.push(PeonObject(kind: UInt32, uInt: self.readUInt())) of Call: # Calls a function. The calling convention for peon @@ -390,7 +360,9 @@ proc dispatch*(self: PeonVM) = # frame is a function object which contains the new # instruction pointer to jump to, followed by a 32-bit # return address. After that, all arguments and locals - # follow + # follow. Note that, due to how the stack works, all + # arguments before the call are in the reverse order in + # which they are passed to the function var argc {.used.} = self.readLong().int let retAddr = self.peek(-argc) # Return address let fnObj = self.peek(-argc - 1) # Function object @@ -439,7 +411,11 @@ proc dispatch*(self: PeonVM) = self.ip = ret.uInt of SetResult: # Sets the result of the - # current function + # current function. A Return + # instruction will pop this + # off the results array and + # onto the operand stack when + # the current function exits. self.results[self.frames.high()] = self.pop() of StoreVar: # Stores the value at the top of the operand stack @@ -621,6 +597,10 @@ proc dispatch*(self: PeonVM) = self.push(PeonObject(kind: Int16, short: -self.pop().short)) of NegInt8: self.push(PeonObject(kind: Int8, tiny: -self.pop().tiny)) + of NegFloat64: + self.push(PeonObject(kind: Float64, `float`: -self.pop().`float`)) + of NegFloat32: + self.push(PeonObject(kind: Float32, halfFloat: -self.pop().halfFloat)) else: discard diff --git a/src/frontend/compiler.nim b/src/frontend/compiler.nim index 188ea4c..4c95162 100644 --- a/src/frontend/compiler.nim +++ b/src/frontend/compiler.nim @@ -874,7 +874,8 @@ proc handleBuiltinFunction(self: Compiler, fn: Name, args: seq[Expression]) = ## Emits instructions for builtin functions ## such as addition or subtraction if fn.valueType.builtinOp notin ["GenericLogicalOr", "GenericLogicalAnd"]: - self.expression(args[1]) + if len(args) == 2: + self.expression(args[1]) self.expression(args[0]) case fn.valueType.builtinOp: of "AddInt64": @@ -957,6 +958,18 @@ proc handleBuiltinFunction(self: Compiler, fn: Name, args: seq[Expression]) = self.emitByte(DivFloat32) of "MulFloat32": self.emitByte(MulFloat32) + of "NegInt64": + self.emitByte(NegInt64) + of "NegInt32": + self.emitByte(NegInt32) + of "NegInt16": + self.emitByte(NegInt16) + of "NegInt8": + self.emitByte(NegInt8) + of "NegFloat64": + self.emitByte(NegFloat64) + of "NegFloat32": + self.emitByte(NegFloat32) of "LogicalOr": self.expression(args[0]) let jump = self.emitJump(JumpIfTrue) @@ -1062,6 +1075,21 @@ 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 @@ -1128,8 +1156,6 @@ proc identifier(self: Compiler, node: IdentExpr) = # no matter the scope depth self.emitConstant(node, self.inferType(node)) else: - self.emitByte(JumpForwards) - self.emitBytes(0.toTriple()) self.detectClosureVariable(s) if s.valueType.kind == Function: # Functions have no runtime @@ -1197,7 +1223,7 @@ proc endScope(self: Compiler) = dec(self.scopeDepth) var names: seq[Name] = @[] for name in self.names: - if name.depth > self.scopeDepth: + if name.depth > self.scopeDepth and name.valueType.kind notin {Generic, CustomType, Function}: names.add(name) if len(names) > 1: # If we're popping less than 65535 variables, then @@ -1409,7 +1435,7 @@ 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, CustomType} and not name.isClosedOver: + if name.depth == self.scopeDepth and name.valueType.kind notin {Function, Generic, CustomType}: inc(popped) if popped > 1: self.emitByte(PopN) diff --git a/src/frontend/meta/bytecode.nim b/src/frontend/meta/bytecode.nim index c8d1035..2ffd7f6 100644 --- a/src/frontend/meta/bytecode.nim +++ b/src/frontend/meta/bytecode.nim @@ -95,6 +95,8 @@ type NegInt32, NegInt16, NegInt8, + NegFloat32, + NegFloat64, AddInt64, AddUInt64, AddInt32, @@ -195,8 +197,8 @@ const simpleInstructions* = {Return, LoadNil, DivInt8, DivUInt8, AddFloat64, SubFloat64, DivFloat64, MulFloat64, AddFloat32, SubFloat32, DivFloat32, - MulFloat32 - } + MulFloat32, NegFloat32, NegFloat64, + } # Constant instructions are instructions that operate on the bytecode constant table const constantInstructions* = {LoadInt64, LoadUInt64,