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 # into the given stack index
self.set(int(self.readLong()), self.pop()) self.set(int(self.readLong()), self.pop())
of StoreHeap: 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: of LoadHeap:
self.push(self.heapVars[self.readLong()]) self.push(self.heapVars[self.readLong()])
of LoadVar: of LoadVar:

View File

@ -50,6 +50,7 @@ type
node*: ASTNode node*: ASTNode
case kind*: TypeKind: case kind*: TypeKind:
of Function: of Function:
isLambda: bool
args*: seq[Type] args*: seq[Type]
returnType*: Type returnType*: Type
else: else:
@ -83,6 +84,7 @@ type
# code begins. For variables, this stores their # code begins. For variables, this stores their
# position in the stack (used for closures) # position in the stack (used for closures)
codePos: int codePos: int
isClosedOver: bool
Loop = object Loop = object
## A "loop object" used ## A "loop object" used
## by the compiler to emit ## 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, # emits LoadHeap if it detects the variable is closed over,
# whether or not this function is called # whether or not this function is called
self.closedOver.add(name) self.closedOver.add(name)
let idx = self.closedOver.high().toTriple()
if self.closedOver.len() >= 16777216: if self.closedOver.len() >= 16777216:
self.error("too many consecutive closed-over variables (max is 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] = StoreHeap.uint8
self.chunk.code[name.codePos + 1] = idx[0] self.chunk.code[name.codePos + 1] = idx[0]
self.chunk.code[name.codePos + 2] = idx[1] self.chunk.code[name.codePos + 2] = idx[1]
self.chunk.code[name.codePos + 3] = idx[2] self.chunk.code[name.codePos + 3] = idx[2]
name.isClosedOver = true
proc compareTypesWithNullNode(self: Compiler, a, b: Type): bool = proc compareTypesWithNullNode(self: Compiler, a, b: Type): bool =
@ -452,28 +455,92 @@ proc compareTypes(self: Compiler, a, b: Type): bool =
of Function: of Function:
if a.node == nil or b.node == nil: if a.node == nil or b.node == nil:
return self.compareTypesWithNullNode(a, b) return self.compareTypesWithNullNode(a, b)
let if not a.isLambda and not b.isLambda:
a = FunDecl(a.node) let
b = FunDecl(b.node) a = FunDecl(a.node)
typeOfA = self.inferType(a.returnType) b = FunDecl(b.node)
typeOfB = self.inferType(b.returnType) typeOfA = self.inferType(a.returnType)
if a.name.token.lexeme != b.name.token.lexeme: typeOfB = self.inferType(b.returnType)
return false if a.name.token.lexeme != b.name.token.lexeme:
elif a.arguments.len() != b.arguments.len():
return false
elif not self.compareTypes(typeOfA, typeOfB):
if typeOfA.kind != Any and typeOfB.kind != Any:
return false return false
for (argA, argB) in zip(a.arguments, b.arguments): elif a.arguments.len() != b.arguments.len():
if argA.mutable != argB.mutable:
return false 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 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 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 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: else:
discard discard
@ -607,6 +674,13 @@ proc inferType(self: Compiler, node: Expression): Type =
nanExpr, floatExpr, nilExpr nanExpr, floatExpr, nilExpr
}: }:
return self.inferType(LiteralExpr(node)) 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: else:
discard # Unreachable discard # Unreachable
@ -639,7 +713,8 @@ proc typeToStr(self: Compiler, typ: Type): string =
result &= ")" result &= ")"
else: else:
discard # Unreachable discard # Unreachable
result &= &": {self.typeToStr(typ.returnType)}" if typ.returnType != nil:
result &= &": {self.typeToStr(typ.returnType)}"
else: else:
discard discard
@ -807,7 +882,7 @@ proc callBinaryOp(self: Compiler, fn: Name, op: BinaryExpr) =
self.expression(op.b) self.expression(op.b)
self.emitByte(Call) # Creates a stack frame self.emitByte(Call) # Creates a stack frame
self.emitBytes(fn.codePos.toTriple()) self.emitBytes(fn.codePos.toTriple())
self.emitBytes(1.toTriple()) self.emitBytes(2.toTriple())
self.patchReturnAddress(idx) self.patchReturnAddress(idx)
@ -876,7 +951,11 @@ proc declareName(self: Compiler, node: Declaration) =
isConst: node.isConst, isConst: node.isConst,
valueType: Type(kind: self.inferType(node.value).kind, node: node), valueType: Type(kind: self.inferType(node.value).kind, node: node),
codePos: self.chunk.code.len(), 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]) self.emitBytes([NoOp, NoOp, NoOp, NoOp])
of NodeKind.funDecl: of NodeKind.funDecl:
var node = FunDecl(node) var node = FunDecl(node)
@ -894,7 +973,8 @@ proc declareName(self: Compiler, node: Declaration) =
args: @[]), args: @[]),
codePos: self.chunk.code.high(), codePos: self.chunk.code.high(),
name: node.name, name: node.name,
isLet: false)) isLet: false,
isClosedOver: false))
let fn = self.names[^1] let fn = self.names[^1]
for argument in node.arguments: for argument in node.arguments:
if self.names.high() > 16777215: if self.names.high() > 16777215:
@ -909,7 +989,8 @@ proc declareName(self: Compiler, node: Declaration) =
name: argument.name, name: argument.name,
valueType: nil, valueType: nil,
codePos: self.chunk.code.len(), codePos: self.chunk.code.len(),
isLet: false)) isLet: false,
isClosedOver: false))
self.names[^1].valueType = self.inferType(argument.valueType) self.names[^1].valueType = self.inferType(argument.valueType)
# We check if the argument's type is a generic # We check if the argument's type is a generic
if self.names[^1].valueType == nil and argument.valueType.kind == identExpr: 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 self.names[^1].valueType.node = argument.name
fn.valueType.args.add(self.names[^1].valueType) fn.valueType.args.add(self.names[^1].valueType)
else: else:
discard # Unreachable discard # TODO: Types, enums
proc identifier(self: Compiler, node: IdentExpr) = proc identifier(self: Compiler, node: IdentExpr) =
@ -949,7 +1030,7 @@ proc identifier(self: Compiler, node: IdentExpr) =
self.emitBytes((index - self.frames[self.scopeDepth]).toTriple()) self.emitBytes((index - self.frames[self.scopeDepth]).toTriple())
else: else:
# Heap-allocated closure variable. Stored in a separate "closure array" in the VM that does not have stack semantics. # 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) # dynamic arrays at runtime anyway)
self.emitByte(LoadHeap) self.emitByte(LoadHeap)
self.emitBytes(self.closedOver.high().toTriple()) 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" var msg = &"multiple matching implementations of '{node.name.token.lexeme}' found:\n"
for fn in reversed(impl): for fn in reversed(impl):
var node = FunDecl(fn.valueType.node) 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" msg &= &"- '{node.name.token.lexeme}' at line {node.token.line} of type {self.typeToStr(fn.valueType)}\n"
self.error(msg) self.error(msg)
# We store the current function # We store the current function
@ -1369,19 +1449,6 @@ proc funDecl(self: Compiler, node: FunDecl) =
# of boilerplate code to make closures work, but # of boilerplate code to make closures work, but
# that's about it # 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: case self.currentFunction.kind:
of NodeKind.funDecl: of NodeKind.funDecl:
if not self.currentFunction.hasExplicitReturn: if not self.currentFunction.hasExplicitReturn:
@ -1396,6 +1463,19 @@ proc funDecl(self: Compiler, node: FunDecl) =
self.emitByte(OpCode.Return) self.emitByte(OpCode.Return)
else: else:
discard # Unreachable 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 # Currently defer is not functional so we
# just pop the instructions # just pop the instructions
for i in countup(deferStart, self.deferred.len() - 1, 1): 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 # 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.) # 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, LoadTrue, LoadFalse,
LoadNan, LoadInf, LoadNan, LoadInf,
Pop, OpCode.Raise, Pop, Raise, BeginTry,
BeginTry, FinishTry, FinishTry, Yield,
OpCode.Yield, OpCode.Await, Await, NoOp, ReturnValue}
OpCode.NoOp, OpCode.Return,
OpCode.ReturnValue}
# 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,

View File

@ -25,6 +25,7 @@ type
CFIElement = ref object CFIElement = ref object
start, stop, bottom, argc: int start, stop, bottom, argc: int
name: string name: string
started, stopped: bool
Debugger* = ref object Debugger* = ref object
chunk: Chunk chunk: Chunk
cfiData: seq[CFIElement] cfiData: seq[CFIElement]
@ -63,7 +64,8 @@ proc printInstruction(instruction: OpCode, newline: bool = false) =
proc checkFrameStart(self: Debugger, n: int) = proc checkFrameStart(self: Debugger, n: int) =
for i, e in self.cfiData: 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 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- Start offset: ", fgYellow, $e.start
styledEcho fgGreen, "\t- End offset: ", fgYellow, $e.stop styledEcho fgGreen, "\t- End offset: ", fgYellow, $e.stop
@ -73,7 +75,8 @@ proc checkFrameStart(self: Debugger, n: int) =
proc checkFrameEnd(self: Debugger, n: int) = proc checkFrameEnd(self: Debugger, n: int) =
for i, e in self.cfiData: 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, ") ====" 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) stdout.styledWriteLine(fgGreen, &", points to index ", fgYellow, $slot)
self.current += 4 self.current += 4
proc stackDoubleInstruction(self: Debugger, instruction: OpCode) = proc stackDoubleInstruction(self: Debugger, instruction: OpCode) =
## Debugs instructions that operate on a single value on the stack using a 16-bit operand ## 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() var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2]].fromDouble()