Some work for heap vars, wip fixes for higher-order functions

This commit is contained in:
Mattia Giambirtone 2022-05-27 14:01:57 +02:00
parent 71c05ec1bf
commit bf9b9389ce
4 changed files with 136 additions and 48 deletions

View File

@ -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:

View File

@ -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):

View File

@ -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,

View File

@ -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()