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
This commit is contained in:
parent
e38610fdbd
commit
6116c127c6
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue