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.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
import strformat
|
||||||
|
import strutils
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
|
@ -62,3 +64,44 @@ type
|
||||||
value*: ptr PeonObject
|
value*: ptr PeonObject
|
||||||
else:
|
else:
|
||||||
discard # TODO
|
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
|
export types
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
PeonVM* = ref object
|
PeonVM* = ref object
|
||||||
## The Peon Virtual Machine
|
## The Peon Virtual Machine
|
||||||
|
@ -58,7 +59,7 @@ proc newPeonVM*: PeonVM =
|
||||||
result.initCache()
|
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]
|
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]
|
proc getNan*(self: PeonVM): PeonObject {.inline.} = self.cache[5]
|
||||||
|
|
||||||
## Stack primitives. Note: all stack accessing that goes
|
# Stack primitives. Note: all stack accessing that goes
|
||||||
## through the get/set wrappers is frame-relative, meaning
|
# through the get(c)/set(c)/peek(c) wrappers is frame-relative,
|
||||||
## that the index is added to the current stack frame's
|
# meaning that the index is added to the current stack frame's
|
||||||
## bottom to obtain an absolute stack index.
|
# bottom to obtain an absolute stack index.
|
||||||
|
|
||||||
proc push(self: PeonVM, obj: PeonObject) =
|
proc push(self: PeonVM, obj: PeonObject) =
|
||||||
## Pushes a Peon object onto the
|
## Pushes a Peon object onto the
|
||||||
|
@ -111,7 +112,7 @@ proc popc(self: PeonVM): PeonObject =
|
||||||
return self.calls.pop()
|
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
|
## Returns the Peon object at the
|
||||||
## given distance from the top of
|
## given distance from the top of
|
||||||
## the call stack without consuming it
|
## the call stack without consuming it
|
||||||
|
@ -131,8 +132,8 @@ proc setc(self: PeonVM, idx: int, val: PeonObject) =
|
||||||
## frames
|
## frames
|
||||||
self.calls[idx + self.frames[^1]] = val
|
self.calls[idx + self.frames[^1]] = val
|
||||||
|
|
||||||
## Byte-level primitives to read/decode
|
# Byte-level primitives to read and decode
|
||||||
## bytecode
|
# bytecode
|
||||||
|
|
||||||
proc readByte(self: PeonVM): uint8 =
|
proc readByte(self: PeonVM): uint8 =
|
||||||
## Reads a single byte from the
|
## 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())
|
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 =
|
proc constReadInt64(self: PeonVM, idx: int): PeonObject =
|
||||||
## Reads a constant from the
|
## Reads a constant from the
|
||||||
## chunk's constant table and
|
## chunk's constant table and
|
||||||
|
@ -287,45 +291,11 @@ proc constReadFloat64(self: PeonVM, idx: int): PeonObject =
|
||||||
copyMem(result.`float`.addr, arr.addr, sizeof(arr))
|
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) =
|
proc dispatch*(self: PeonVM) =
|
||||||
## Main bytecode dispatch loop
|
## Main bytecode dispatch loop
|
||||||
var instruction {.register.}: OpCode
|
var instruction {.register.}: OpCode
|
||||||
while true:
|
while true:
|
||||||
|
{.computedgoto.} # https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma
|
||||||
when DEBUG_TRACE_VM:
|
when DEBUG_TRACE_VM:
|
||||||
echo &"IP: {self.ip}"
|
echo &"IP: {self.ip}"
|
||||||
echo &"Instruction: {OpCode(self.chunk.code[self.ip])}"
|
echo &"Instruction: {OpCode(self.chunk.code[self.ip])}"
|
||||||
|
@ -343,7 +313,7 @@ proc dispatch*(self: PeonVM) =
|
||||||
discard readLine stdin
|
discard readLine stdin
|
||||||
instruction = OpCode(self.readByte())
|
instruction = OpCode(self.readByte())
|
||||||
case instruction:
|
case instruction:
|
||||||
# Constant loading
|
# Constant loading instructions
|
||||||
of LoadTrue:
|
of LoadTrue:
|
||||||
self.push(self.getBool(true))
|
self.push(self.getBool(true))
|
||||||
of LoadFalse:
|
of LoadFalse:
|
||||||
|
@ -381,8 +351,8 @@ proc dispatch*(self: PeonVM) =
|
||||||
# instruction pointer
|
# instruction pointer
|
||||||
self.push(PeonObject(kind: Function, ip: self.readLong()))
|
self.push(PeonObject(kind: Function, ip: self.readLong()))
|
||||||
of LoadReturnAddress:
|
of LoadReturnAddress:
|
||||||
# Loads a 32-bit unsigned integer. Used to load function
|
# Loads a 32-bit unsigned integer onto the operand stack.
|
||||||
# return addresses
|
# Used to load function return addresses
|
||||||
self.push(PeonObject(kind: UInt32, uInt: self.readUInt()))
|
self.push(PeonObject(kind: UInt32, uInt: self.readUInt()))
|
||||||
of Call:
|
of Call:
|
||||||
# Calls a function. The calling convention for peon
|
# 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
|
# frame is a function object which contains the new
|
||||||
# instruction pointer to jump to, followed by a 32-bit
|
# instruction pointer to jump to, followed by a 32-bit
|
||||||
# return address. After that, all arguments and locals
|
# 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
|
var argc {.used.} = self.readLong().int
|
||||||
let retAddr = self.peek(-argc) # Return address
|
let retAddr = self.peek(-argc) # Return address
|
||||||
let fnObj = self.peek(-argc - 1) # Function object
|
let fnObj = self.peek(-argc - 1) # Function object
|
||||||
|
@ -439,7 +411,11 @@ proc dispatch*(self: PeonVM) =
|
||||||
self.ip = ret.uInt
|
self.ip = ret.uInt
|
||||||
of SetResult:
|
of SetResult:
|
||||||
# Sets the result of the
|
# 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()
|
self.results[self.frames.high()] = self.pop()
|
||||||
of StoreVar:
|
of StoreVar:
|
||||||
# Stores the value at the top of the operand stack
|
# 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))
|
self.push(PeonObject(kind: Int16, short: -self.pop().short))
|
||||||
of NegInt8:
|
of NegInt8:
|
||||||
self.push(PeonObject(kind: Int8, tiny: -self.pop().tiny))
|
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:
|
else:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
|
|
@ -874,7 +874,8 @@ proc handleBuiltinFunction(self: Compiler, fn: Name, args: seq[Expression]) =
|
||||||
## Emits instructions for builtin functions
|
## Emits instructions for builtin functions
|
||||||
## such as addition or subtraction
|
## such as addition or subtraction
|
||||||
if fn.valueType.builtinOp notin ["GenericLogicalOr", "GenericLogicalAnd"]:
|
if fn.valueType.builtinOp notin ["GenericLogicalOr", "GenericLogicalAnd"]:
|
||||||
self.expression(args[1])
|
if len(args) == 2:
|
||||||
|
self.expression(args[1])
|
||||||
self.expression(args[0])
|
self.expression(args[0])
|
||||||
case fn.valueType.builtinOp:
|
case fn.valueType.builtinOp:
|
||||||
of "AddInt64":
|
of "AddInt64":
|
||||||
|
@ -957,6 +958,18 @@ proc handleBuiltinFunction(self: Compiler, fn: Name, args: seq[Expression]) =
|
||||||
self.emitByte(DivFloat32)
|
self.emitByte(DivFloat32)
|
||||||
of "MulFloat32":
|
of "MulFloat32":
|
||||||
self.emitByte(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":
|
of "LogicalOr":
|
||||||
self.expression(args[0])
|
self.expression(args[0])
|
||||||
let jump = self.emitJump(JumpIfTrue)
|
let jump = self.emitJump(JumpIfTrue)
|
||||||
|
@ -1062,6 +1075,21 @@ proc declareName(self: Compiler, node: Declaration, mutable: bool = false) =
|
||||||
line: node.token.line))
|
line: node.token.line))
|
||||||
if mutable:
|
if mutable:
|
||||||
self.names[^1].valueType.mutable = true
|
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:
|
of NodeKind.funDecl:
|
||||||
var node = FunDecl(node)
|
var node = FunDecl(node)
|
||||||
# We declare the generics before the function so we
|
# We declare the generics before the function so we
|
||||||
|
@ -1128,8 +1156,6 @@ proc identifier(self: Compiler, node: IdentExpr) =
|
||||||
# no matter the scope depth
|
# no matter the scope depth
|
||||||
self.emitConstant(node, self.inferType(node))
|
self.emitConstant(node, self.inferType(node))
|
||||||
else:
|
else:
|
||||||
self.emitByte(JumpForwards)
|
|
||||||
self.emitBytes(0.toTriple())
|
|
||||||
self.detectClosureVariable(s)
|
self.detectClosureVariable(s)
|
||||||
if s.valueType.kind == Function:
|
if s.valueType.kind == Function:
|
||||||
# Functions have no runtime
|
# Functions have no runtime
|
||||||
|
@ -1197,7 +1223,7 @@ proc endScope(self: Compiler) =
|
||||||
dec(self.scopeDepth)
|
dec(self.scopeDepth)
|
||||||
var names: seq[Name] = @[]
|
var names: seq[Name] = @[]
|
||||||
for name in self.names:
|
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)
|
names.add(name)
|
||||||
if len(names) > 1:
|
if len(names) > 1:
|
||||||
# If we're popping less than 65535 variables, then
|
# If we're popping less than 65535 variables, then
|
||||||
|
@ -1409,7 +1435,7 @@ proc endFunctionBeforeReturn(self: Compiler) =
|
||||||
## its return instruction
|
## its return instruction
|
||||||
var popped = 0
|
var popped = 0
|
||||||
for name in self.names:
|
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)
|
inc(popped)
|
||||||
if popped > 1:
|
if popped > 1:
|
||||||
self.emitByte(PopN)
|
self.emitByte(PopN)
|
||||||
|
|
|
@ -95,6 +95,8 @@ type
|
||||||
NegInt32,
|
NegInt32,
|
||||||
NegInt16,
|
NegInt16,
|
||||||
NegInt8,
|
NegInt8,
|
||||||
|
NegFloat32,
|
||||||
|
NegFloat64,
|
||||||
AddInt64,
|
AddInt64,
|
||||||
AddUInt64,
|
AddUInt64,
|
||||||
AddInt32,
|
AddInt32,
|
||||||
|
@ -195,8 +197,8 @@ const simpleInstructions* = {Return, LoadNil,
|
||||||
DivInt8, DivUInt8, AddFloat64,
|
DivInt8, DivUInt8, AddFloat64,
|
||||||
SubFloat64, DivFloat64, MulFloat64,
|
SubFloat64, DivFloat64, MulFloat64,
|
||||||
AddFloat32, SubFloat32, DivFloat32,
|
AddFloat32, SubFloat32, DivFloat32,
|
||||||
MulFloat32
|
MulFloat32, NegFloat32, NegFloat64,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Constant instructions are instructions that operate on the bytecode constant table
|
# Constant instructions are instructions that operate on the bytecode constant table
|
||||||
const constantInstructions* = {LoadInt64, LoadUInt64,
|
const constantInstructions* = {LoadInt64, LoadUInt64,
|
||||||
|
|
Loading…
Reference in New Issue