Some work for heap vars, wip fixes for higher-order functions
This commit is contained in:
parent
71c05ec1bf
commit
bf9b9389ce
|
@ -248,7 +248,13 @@ proc dispatch*(self: PeonVM) =
|
|||
# into the given stack index
|
||||
self.set(int(self.readLong()), self.pop())
|
||||
of StoreHeap:
|
||||
self.heapVars.add(self.pop())
|
||||
# Stores/updates the value of a closed-over
|
||||
# variable
|
||||
let idx = self.readLong().int
|
||||
if idx > self.heapVars.high():
|
||||
self.heapVars.add(self.pop())
|
||||
else:
|
||||
self.heapVars[idx] = self.pop()
|
||||
of LoadHeap:
|
||||
self.push(self.heapVars[self.readLong()])
|
||||
of LoadVar:
|
||||
|
|
|
@ -50,6 +50,7 @@ type
|
|||
node*: ASTNode
|
||||
case kind*: TypeKind:
|
||||
of Function:
|
||||
isLambda: bool
|
||||
args*: seq[Type]
|
||||
returnType*: Type
|
||||
else:
|
||||
|
@ -83,6 +84,7 @@ type
|
|||
# code begins. For variables, this stores their
|
||||
# position in the stack (used for closures)
|
||||
codePos: int
|
||||
isClosedOver: bool
|
||||
Loop = object
|
||||
## A "loop object" used
|
||||
## by the compiler to emit
|
||||
|
@ -402,13 +404,14 @@ proc detectClosureVariable(self: Compiler, name: Name,
|
|||
# emits LoadHeap if it detects the variable is closed over,
|
||||
# whether or not this function is called
|
||||
self.closedOver.add(name)
|
||||
let idx = self.closedOver.high().toTriple()
|
||||
if self.closedOver.len() >= 16777216:
|
||||
self.error("too many consecutive closed-over variables (max is 16777216)")
|
||||
let idx = self.closedOver.high().toTriple()
|
||||
self.chunk.code[name.codePos] = StoreHeap.uint8
|
||||
self.chunk.code[name.codePos + 1] = idx[0]
|
||||
self.chunk.code[name.codePos + 2] = idx[1]
|
||||
self.chunk.code[name.codePos + 3] = idx[2]
|
||||
name.isClosedOver = true
|
||||
|
||||
|
||||
proc compareTypesWithNullNode(self: Compiler, a, b: Type): bool =
|
||||
|
@ -452,28 +455,92 @@ proc compareTypes(self: Compiler, a, b: Type): bool =
|
|||
of Function:
|
||||
if a.node == nil or b.node == nil:
|
||||
return self.compareTypesWithNullNode(a, b)
|
||||
let
|
||||
a = FunDecl(a.node)
|
||||
b = FunDecl(b.node)
|
||||
typeOfA = self.inferType(a.returnType)
|
||||
typeOfB = self.inferType(b.returnType)
|
||||
if a.name.token.lexeme != b.name.token.lexeme:
|
||||
return false
|
||||
elif a.arguments.len() != b.arguments.len():
|
||||
return false
|
||||
elif not self.compareTypes(typeOfA, typeOfB):
|
||||
if typeOfA.kind != Any and typeOfB.kind != Any:
|
||||
if not a.isLambda and not b.isLambda:
|
||||
let
|
||||
a = FunDecl(a.node)
|
||||
b = FunDecl(b.node)
|
||||
typeOfA = self.inferType(a.returnType)
|
||||
typeOfB = self.inferType(b.returnType)
|
||||
if a.name.token.lexeme != b.name.token.lexeme:
|
||||
return false
|
||||
for (argA, argB) in zip(a.arguments, b.arguments):
|
||||
if argA.mutable != argB.mutable:
|
||||
elif a.arguments.len() != b.arguments.len():
|
||||
return false
|
||||
elif argA.isRef != argB.isRef:
|
||||
elif not self.compareTypes(typeOfA, typeOfB):
|
||||
if typeOfA.kind != Any and typeOfB.kind != Any:
|
||||
return false
|
||||
for (argA, argB) in zip(a.arguments, b.arguments):
|
||||
if argA.mutable != argB.mutable:
|
||||
return false
|
||||
elif argA.isRef != argB.isRef:
|
||||
return false
|
||||
elif argA.isPtr != argB.isPtr:
|
||||
return false
|
||||
elif not self.compareTypes(self.inferType(argA.valueType), self.inferType(argB.valueType)):
|
||||
return false
|
||||
return true
|
||||
elif a.isLambda and not b.isLambda:
|
||||
let
|
||||
a = LambdaExpr(a.node)
|
||||
b = FunDecl(b.node)
|
||||
typeOfA = self.inferType(a.returnType)
|
||||
typeOfB = self.inferType(b.returnType)
|
||||
if a.arguments.len() != b.arguments.len():
|
||||
return false
|
||||
elif argA.isPtr != argB.isPtr:
|
||||
elif not self.compareTypes(typeOfA, typeOfB):
|
||||
if typeOfA.kind != Any and typeOfB.kind != Any:
|
||||
return false
|
||||
for (argA, argB) in zip(a.arguments, b.arguments):
|
||||
if argA.mutable != argB.mutable:
|
||||
return false
|
||||
elif argA.isRef != argB.isRef:
|
||||
return false
|
||||
elif argA.isPtr != argB.isPtr:
|
||||
return false
|
||||
elif not self.compareTypes(self.inferType(argA.valueType), self.inferType(argB.valueType)):
|
||||
return false
|
||||
return true
|
||||
elif b.isLambda and not a.isLambda:
|
||||
let
|
||||
a = FunDecl(a.node)
|
||||
b = LambdaExpr(b.node)
|
||||
typeOfA = self.inferType(a.returnType)
|
||||
typeOfB = self.inferType(b.returnType)
|
||||
if a.arguments.len() != b.arguments.len():
|
||||
return false
|
||||
elif not self.compareTypes(self.inferType(argA.valueType), self.inferType(argB.valueType)):
|
||||
elif not self.compareTypes(typeOfA, typeOfB):
|
||||
if typeOfA.kind != Any and typeOfB.kind != Any:
|
||||
return false
|
||||
for (argA, argB) in zip(a.arguments, b.arguments):
|
||||
if argA.mutable != argB.mutable:
|
||||
return false
|
||||
elif argA.isRef != argB.isRef:
|
||||
return false
|
||||
elif argA.isPtr != argB.isPtr:
|
||||
return false
|
||||
elif not self.compareTypes(self.inferType(argA.valueType), self.inferType(argB.valueType)):
|
||||
return false
|
||||
return true
|
||||
else:
|
||||
let
|
||||
a = LambdaExpr(a.node)
|
||||
b = LambdaExpr(b.node)
|
||||
typeOfA = self.inferType(a.returnType)
|
||||
typeOfB = self.inferType(b.returnType)
|
||||
if a.arguments.len() != b.arguments.len():
|
||||
return false
|
||||
return true
|
||||
elif not self.compareTypes(typeOfA, typeOfB):
|
||||
if typeOfA.kind != Any and typeOfB.kind != Any:
|
||||
return false
|
||||
for (argA, argB) in zip(a.arguments, b.arguments):
|
||||
if argA.mutable != argB.mutable:
|
||||
return false
|
||||
elif argA.isRef != argB.isRef:
|
||||
return false
|
||||
elif argA.isPtr != argB.isPtr:
|
||||
return false
|
||||
elif not self.compareTypes(self.inferType(argA.valueType), self.inferType(argB.valueType)):
|
||||
return false
|
||||
return true
|
||||
else:
|
||||
discard
|
||||
|
||||
|
@ -607,6 +674,13 @@ proc inferType(self: Compiler, node: Expression): Type =
|
|||
nanExpr, floatExpr, nilExpr
|
||||
}:
|
||||
return self.inferType(LiteralExpr(node))
|
||||
of lambdaExpr:
|
||||
var node = LambdaExpr(node)
|
||||
result = Type(kind: Function, returnType: nil, node: node, args: @[], isLambda: true)
|
||||
if node.returnType != nil:
|
||||
result.returnType = self.inferType(node.returnType)
|
||||
for argument in node.arguments:
|
||||
result.args.add(self.inferType(argument.valueType))
|
||||
else:
|
||||
discard # Unreachable
|
||||
|
||||
|
@ -639,7 +713,8 @@ proc typeToStr(self: Compiler, typ: Type): string =
|
|||
result &= ")"
|
||||
else:
|
||||
discard # Unreachable
|
||||
result &= &": {self.typeToStr(typ.returnType)}"
|
||||
if typ.returnType != nil:
|
||||
result &= &": {self.typeToStr(typ.returnType)}"
|
||||
else:
|
||||
discard
|
||||
|
||||
|
@ -807,7 +882,7 @@ proc callBinaryOp(self: Compiler, fn: Name, op: BinaryExpr) =
|
|||
self.expression(op.b)
|
||||
self.emitByte(Call) # Creates a stack frame
|
||||
self.emitBytes(fn.codePos.toTriple())
|
||||
self.emitBytes(1.toTriple())
|
||||
self.emitBytes(2.toTriple())
|
||||
self.patchReturnAddress(idx)
|
||||
|
||||
|
||||
|
@ -876,7 +951,11 @@ proc declareName(self: Compiler, node: Declaration) =
|
|||
isConst: node.isConst,
|
||||
valueType: Type(kind: self.inferType(node.value).kind, node: node),
|
||||
codePos: self.chunk.code.len(),
|
||||
isLet: node.isLet))
|
||||
isLet: node.isLet,
|
||||
isClosedOver: false))
|
||||
# We emit 4 No-Ops because they may become a
|
||||
# StoreHeap instruction. If not, they'll be
|
||||
# removed before the compiler is finished
|
||||
self.emitBytes([NoOp, NoOp, NoOp, NoOp])
|
||||
of NodeKind.funDecl:
|
||||
var node = FunDecl(node)
|
||||
|
@ -894,7 +973,8 @@ proc declareName(self: Compiler, node: Declaration) =
|
|||
args: @[]),
|
||||
codePos: self.chunk.code.high(),
|
||||
name: node.name,
|
||||
isLet: false))
|
||||
isLet: false,
|
||||
isClosedOver: false))
|
||||
let fn = self.names[^1]
|
||||
for argument in node.arguments:
|
||||
if self.names.high() > 16777215:
|
||||
|
@ -909,7 +989,8 @@ proc declareName(self: Compiler, node: Declaration) =
|
|||
name: argument.name,
|
||||
valueType: nil,
|
||||
codePos: self.chunk.code.len(),
|
||||
isLet: false))
|
||||
isLet: false,
|
||||
isClosedOver: false))
|
||||
self.names[^1].valueType = self.inferType(argument.valueType)
|
||||
# We check if the argument's type is a generic
|
||||
if self.names[^1].valueType == nil and argument.valueType.kind == identExpr:
|
||||
|
@ -923,7 +1004,7 @@ proc declareName(self: Compiler, node: Declaration) =
|
|||
self.names[^1].valueType.node = argument.name
|
||||
fn.valueType.args.add(self.names[^1].valueType)
|
||||
else:
|
||||
discard # Unreachable
|
||||
discard # TODO: Types, enums
|
||||
|
||||
|
||||
proc identifier(self: Compiler, node: IdentExpr) =
|
||||
|
@ -949,7 +1030,7 @@ proc identifier(self: Compiler, node: IdentExpr) =
|
|||
self.emitBytes((index - self.frames[self.scopeDepth]).toTriple())
|
||||
else:
|
||||
# Heap-allocated closure variable. Stored in a separate "closure array" in the VM that does not have stack semantics.
|
||||
# This makes closures work as expected and is not comparatively slower than indexing our stack (since they're both
|
||||
# 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(LoadHeap)
|
||||
self.emitBytes(self.closedOver.high().toTriple())
|
||||
|
@ -1338,7 +1419,6 @@ proc funDecl(self: Compiler, node: FunDecl) =
|
|||
var msg = &"multiple matching implementations of '{node.name.token.lexeme}' found:\n"
|
||||
for fn in reversed(impl):
|
||||
var node = FunDecl(fn.valueType.node)
|
||||
discard self.typeToStr(fn.valueType)
|
||||
msg &= &"- '{node.name.token.lexeme}' at line {node.token.line} of type {self.typeToStr(fn.valueType)}\n"
|
||||
self.error(msg)
|
||||
# We store the current function
|
||||
|
@ -1369,19 +1449,6 @@ proc funDecl(self: Compiler, node: FunDecl) =
|
|||
# of boilerplate code to make closures work, but
|
||||
# that's about it
|
||||
|
||||
# Function is ending!
|
||||
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 system.`==`(node.name, nil):
|
||||
self.chunk.cfi.add(node.name.token.lexeme.len().toDouble())
|
||||
var s = node.name.token.lexeme
|
||||
if node.name.token.lexeme.len() >= uint16.high().int:
|
||||
s = node.name.token.lexeme[0..uint16.high()]
|
||||
self.chunk.cfi.add(s.toBytes())
|
||||
else:
|
||||
self.chunk.cfi.add(0.toDouble())
|
||||
case self.currentFunction.kind:
|
||||
of NodeKind.funDecl:
|
||||
if not self.currentFunction.hasExplicitReturn:
|
||||
|
@ -1396,6 +1463,19 @@ proc funDecl(self: Compiler, node: FunDecl) =
|
|||
self.emitByte(OpCode.Return)
|
||||
else:
|
||||
discard # Unreachable
|
||||
# Function is ending!
|
||||
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 system.`==`(node.name, nil):
|
||||
self.chunk.cfi.add(node.name.token.lexeme.len().toDouble())
|
||||
var s = node.name.token.lexeme
|
||||
if node.name.token.lexeme.len() >= uint16.high().int:
|
||||
s = node.name.token.lexeme[0..uint16.high()]
|
||||
self.chunk.cfi.add(s.toBytes())
|
||||
else:
|
||||
self.chunk.cfi.add(0.toDouble())
|
||||
# Currently defer is not functional so we
|
||||
# just pop the instructions
|
||||
for i in countup(deferStart, self.deferred.len() - 1, 1):
|
||||
|
|
|
@ -134,14 +134,12 @@ type
|
|||
# We group instructions by their operation/operand types for easier handling when debugging
|
||||
|
||||
# Simple instructions encompass instructions that push onto/pop off the stack unconditionally (True, False, Pop, etc.)
|
||||
const simpleInstructions* = {OpCode.Return, LoadNil,
|
||||
const simpleInstructions* = {Return, LoadNil,
|
||||
LoadTrue, LoadFalse,
|
||||
LoadNan, LoadInf,
|
||||
Pop, OpCode.Raise,
|
||||
BeginTry, FinishTry,
|
||||
OpCode.Yield, OpCode.Await,
|
||||
OpCode.NoOp, OpCode.Return,
|
||||
OpCode.ReturnValue}
|
||||
Pop, Raise, BeginTry,
|
||||
FinishTry, Yield,
|
||||
Await, NoOp, ReturnValue}
|
||||
|
||||
# Constant instructions are instructions that operate on the bytecode constant table
|
||||
const constantInstructions* = {LoadInt64, LoadUInt64,
|
||||
|
|
|
@ -25,6 +25,7 @@ type
|
|||
CFIElement = ref object
|
||||
start, stop, bottom, argc: int
|
||||
name: string
|
||||
started, stopped: bool
|
||||
Debugger* = ref object
|
||||
chunk: Chunk
|
||||
cfiData: seq[CFIElement]
|
||||
|
@ -63,7 +64,8 @@ proc printInstruction(instruction: OpCode, newline: bool = false) =
|
|||
|
||||
proc checkFrameStart(self: Debugger, n: int) =
|
||||
for i, e in self.cfiData:
|
||||
if n == e.start:
|
||||
if n == e.start and not (e.started or e.stopped):
|
||||
e.started = true
|
||||
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
|
||||
|
@ -73,7 +75,8 @@ proc checkFrameStart(self: Debugger, n: int) =
|
|||
|
||||
proc checkFrameEnd(self: Debugger, n: int) =
|
||||
for i, e in self.cfiData:
|
||||
if n == e.stop:
|
||||
if n == e.stop and e.started and not e.stopped:
|
||||
e.stopped = true
|
||||
styledEcho fgBlue, "\n==== Peon Bytecode Debugger - End Frame ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ===="
|
||||
|
||||
|
||||
|
@ -94,6 +97,7 @@ proc stackTripleInstruction(self: Debugger, instruction: OpCode) =
|
|||
stdout.styledWriteLine(fgGreen, &", points to index ", fgYellow, $slot)
|
||||
self.current += 4
|
||||
|
||||
|
||||
proc stackDoubleInstruction(self: Debugger, instruction: OpCode) =
|
||||
## Debugs instructions that operate on a single value on the stack using a 16-bit operand
|
||||
var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2]].fromDouble()
|
||||
|
|
Loading…
Reference in New Issue