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
|
# 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:
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue