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
This commit is contained in:
parent
b4628109ce
commit
ff0ae8fcba
|
@ -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
|
||||
[...]
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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..<idx + size].fromBytes()
|
||||
inc(idx, size)
|
||||
self.cfiData.add(CFIElement(start: start, stop: stop, bottom: bottom,
|
||||
self.cfiData.add(CFIElement(start: start, stop: stop,
|
||||
argc: argc, name: name))
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue