Various fixes to closures. Fixed chained calls and local scopes
This commit is contained in:
parent
a4bccba6cd
commit
e759e6cbb2
|
@ -648,6 +648,13 @@ when debugVM: # So nim shuts up
|
|||
if i < self.frames.high():
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
if self.closures.len() !> 0:
|
||||
stdout.styledWrite(fgBlue, "Closure offsets: ", fgMagenta, "[")
|
||||
for i, e in self.closures:
|
||||
stdout.styledWrite(fgYellow, $e)
|
||||
if i < self.closures.high():
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
if self.envs.len() !> 0:
|
||||
stdout.styledWrite(fgGreen, "Environments: ", fgMagenta, "[")
|
||||
for i, e in self.envs:
|
||||
|
@ -655,13 +662,6 @@ when debugVM: # So nim shuts up
|
|||
if i < self.envs.high():
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
if self.closures.len() !> 0:
|
||||
stdout.styledWrite(fgGreen, "Environment offsets: ", fgMagenta, "[")
|
||||
for i, e in self.closures:
|
||||
stdout.styledWrite(fgYellow, $e)
|
||||
if i < self.closures.high():
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
if self.results.len() !> 0:
|
||||
stdout.styledWrite(fgYellow, "Function Results: ", fgMagenta, "[")
|
||||
for i, e in self.results:
|
||||
|
@ -669,13 +669,6 @@ when debugVM: # So nim shuts up
|
|||
if i < self.results.high():
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
if self.closures.len() !> 0:
|
||||
stdout.styledWrite(fgBlue, "Closure offsets: ", fgMagenta, "[")
|
||||
for i, e in self.closures:
|
||||
stdout.styledWrite(fgYellow, $e)
|
||||
if i < self.closures.high():
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
discard readLine stdin
|
||||
|
||||
|
||||
|
@ -771,7 +764,7 @@ proc dispatch*(self: PeonVM) =
|
|||
self.results.add(self.getNil())
|
||||
# Creates a new call frame
|
||||
self.frames.add(uint64(self.calls.len() - 2))
|
||||
self.closures.add(offset)
|
||||
self.closures.add(offset - 1)
|
||||
# Loads the arguments onto the stack
|
||||
for _ in 0..<argc:
|
||||
self.pushc(self.pop())
|
||||
|
|
|
@ -476,6 +476,7 @@ proc getStackPos(self: Compiler, name: Name): int =
|
|||
if name == variable:
|
||||
found = true
|
||||
break
|
||||
inc(result)
|
||||
if not found:
|
||||
return -1
|
||||
|
||||
|
@ -954,6 +955,32 @@ proc emitFunction(self: Compiler, name: Name) =
|
|||
self.emitBytes(self.getStackPos(name).toTriple(), name.line)
|
||||
|
||||
|
||||
proc generateCall(self: Compiler, fn: Type, args: seq[Expression], line: int) =
|
||||
## Version of generateCall that takes Type objects
|
||||
## instead of Name objects. The function is assumed
|
||||
## to be on the stack
|
||||
self.emitByte(LoadUInt32, line)
|
||||
self.emitBytes(self.chunk.writeConstant(0.toQuad()), line)
|
||||
let pos = self.chunk.consts.len() - 4
|
||||
for i, argument in reversed(args):
|
||||
# We pass the arguments in reverse
|
||||
# because of how stacks work. They'll
|
||||
# be reversed again at runtime
|
||||
self.check(argument, fn.args[^(i + 1)].kind)
|
||||
self.expression(argument)
|
||||
# Creates a new call frame and jumps
|
||||
# to the function's first instruction
|
||||
# in the code
|
||||
if not fn.isClosure:
|
||||
self.emitByte(Call, line)
|
||||
else:
|
||||
self.emitByte(CallClosure,line)
|
||||
self.emitBytes(fn.args.len().toTriple(), line)
|
||||
if fn.isClosure:
|
||||
self.emitBytes(fn.envLen.toTriple(), line)
|
||||
self.patchReturnAddress(pos)
|
||||
|
||||
|
||||
proc generateCall(self: Compiler, fn: Name, args: seq[Expression], onStack: bool = false) =
|
||||
## Small wrapper that abstracts emitting a call instruction
|
||||
## for a given function
|
||||
|
@ -1008,6 +1035,8 @@ proc beginScope(self: Compiler) =
|
|||
proc `$`(self: Type): string = $self[]
|
||||
|
||||
|
||||
# Flattens our weird function tree into a linear
|
||||
# list
|
||||
proc flattenImpl(self: Type, to: var seq[Type]) =
|
||||
to.add(self)
|
||||
for child in self.children:
|
||||
|
@ -1028,24 +1057,41 @@ proc endScope(self: Compiler) =
|
|||
for name in self.names:
|
||||
if name.depth > self.scopeDepth:
|
||||
names.add(name)
|
||||
if name.valueType.kind notin {Generic, CustomType} and not name.isFunDecl:
|
||||
if name.valueType.kind notin {Generic, CustomType} and not name.isFunDecl and name.depth > 0:
|
||||
# We don't increase the pop count for these kinds of objects
|
||||
# because they're not stored the same way as regular variables
|
||||
# (for types, generics and function declarations). We also don't
|
||||
# pop for local scopes because the VM takes care of closing a function's
|
||||
# stack frame
|
||||
inc(popCount)
|
||||
if name.isFunDecl and not name.valueType.isClosure and name.valueType.children.len() > 0 and name.depth == 0:
|
||||
# If a function at the top level contains any closures,
|
||||
# when it goes out of scope all of the environments that
|
||||
# belong to its inner functions also go out of
|
||||
# scope
|
||||
if name.isFunDecl and name.valueType.children.len() > 0 and name.depth == 0:
|
||||
# When a closure goes out of scope, its environment is reclaimed.
|
||||
# This includes the environments of every other closure that may
|
||||
# have been contained within it, too
|
||||
var i = 0
|
||||
let f = name.valueType
|
||||
for fn in flatten(f):
|
||||
if fn.isClosure:
|
||||
for y in 0..f.envLen:
|
||||
self.closedOver.delete(y + i)
|
||||
self.emitByte(PopClosure, self.peek().token.line)
|
||||
self.emitBytes((y + i).toTriple(), self.peek().token.line)
|
||||
inc(i)
|
||||
var all = flatten(name.valueType)
|
||||
# Why this? Well, it's simple: if a function returns
|
||||
# a closure, that function becomes a closure too. The
|
||||
# environments of closures are aligned one after the
|
||||
# other, so if a and b are both closures, but only b
|
||||
# closes over a value, both a and b will have an envLen
|
||||
# of 1, which would cause us to emit one extra PopClosure
|
||||
# instruction than what's actually needed. We can account
|
||||
# for this easily by checking if the contained function's
|
||||
# environment is larger than the contained one, which will
|
||||
# guarantee there actually is some value that the contained
|
||||
# function is closing over
|
||||
var envLen = 0
|
||||
var lastEnvLen = 0
|
||||
for fn in all:
|
||||
if fn.isClosure and fn.envLen > lastEnvLen:
|
||||
envLen += fn.envLen
|
||||
lastEnvLen = fn.envLen
|
||||
for y in 0..<envLen:
|
||||
self.closedOver.delete(y + i)
|
||||
self.emitByte(PopClosure, self.peek().token.line)
|
||||
self.emitBytes((y + i).toTriple(), self.peek().token.line)
|
||||
inc(i)
|
||||
if popCount > 1:
|
||||
# If we're popping less than 65535 variables, then
|
||||
# we can emit a PopN instruction. This is true for
|
||||
|
@ -1434,8 +1480,8 @@ proc identifier(self: Compiler, node: IdentExpr) =
|
|||
else:
|
||||
if s.valueType.kind == Function and s.isFunDecl:
|
||||
# Functions have no runtime
|
||||
# representation, so we need
|
||||
# to create one on the fly
|
||||
# representation: they're just
|
||||
# a location to jump to
|
||||
self.emitByte(LoadInt64, node.token.line)
|
||||
self.emitBytes(self.chunk.writeConstant(s.codePos.toLong()), node.token.line)
|
||||
elif self.scopeDepth > 0 and not self.currentFunction.isNil() and s.depth != self.scopeDepth:
|
||||
|
@ -1446,15 +1492,18 @@ proc identifier(self: Compiler, node: IdentExpr) =
|
|||
var fn = self.currentFunction.valueType
|
||||
while true:
|
||||
fn.isClosure = true
|
||||
fn.envLen += 1
|
||||
if fn.parent.isNil():
|
||||
break
|
||||
fn = fn.parent
|
||||
s.isClosedOver = true
|
||||
self.currentFunction.valueType.envLen += 1
|
||||
self.closedOver.add(s)
|
||||
let stackIdx = self.getStackPos(s).toTriple()
|
||||
let closeIdx = self.getClosurePos(s).toTriple()
|
||||
let oldLen = self.chunk.code.len()
|
||||
# This madness makes it so that we can insert bytecode
|
||||
# at arbitrary offsets into our alredy compiled code and
|
||||
# have our metadata be up to date
|
||||
self.chunk.code.insert(StoreClosure.uint8, s.belongsTo.codePos)
|
||||
self.chunk.code.insert(stackIdx[0], s.belongsTo.codePos + 1)
|
||||
self.chunk.code.insert(stackIdx[1], s.belongsTo.codePos + 2)
|
||||
|
@ -1581,7 +1630,12 @@ proc callExpr(self: Compiler, node: CallExpr): Name {.discardable.} =
|
|||
for exp in reversed(all):
|
||||
self.callExpr(exp)
|
||||
# TODO
|
||||
self.generateCall(result, argExpr, onStack=true)
|
||||
case all[^1].callee.kind:
|
||||
of identExpr:
|
||||
let fn = self.resolve(IdentExpr(all[^1].callee))
|
||||
self.generateCall(fn.valueType.returnType, argExpr, fn.line)
|
||||
else:
|
||||
discard # TODO: Lambdas
|
||||
# TODO: Calling lambdas on-the-fly (i.e. on the same line)
|
||||
else:
|
||||
let typ = self.inferType(node)
|
||||
|
|
|
@ -150,6 +150,17 @@ proc callInstruction(self: Debugger, instruction: OpCode) =
|
|||
self.current += 1
|
||||
|
||||
|
||||
proc callClosureInstruction(self: Debugger, instruction: OpCode) =
|
||||
## Debugs closure calls
|
||||
var size = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
|
||||
self.current += 3
|
||||
var envSize = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
|
||||
self.current += 3
|
||||
printInstruction(instruction)
|
||||
styledEcho fgGreen, &", creates frame of size ", fgYellow, $(size + 2), fgGreen, " with environment of size ", fgYellow, $envSize, fgGreen
|
||||
self.current += 1
|
||||
|
||||
|
||||
proc constantInstruction(self: Debugger, instruction: OpCode) =
|
||||
## Debugs instructions that operate on the constant table
|
||||
var size: uint
|
||||
|
@ -203,6 +214,8 @@ proc disassembleInstruction*(self: Debugger) =
|
|||
self.storeClosureInstruction(opcode)
|
||||
of Call:
|
||||
self.callInstruction(opcode)
|
||||
of CallClosure:
|
||||
self.callClosureInstruction(opcode)
|
||||
of jumpInstructions:
|
||||
self.jumpInstruction(opcode)
|
||||
else:
|
||||
|
|
|
@ -5,13 +5,13 @@ fn first(a, b, c: int): int {
|
|||
return a;
|
||||
}
|
||||
|
||||
fn second(a, b: int): int {
|
||||
return first(b, a, 0);
|
||||
fn second(a, b, c: int): int {
|
||||
return first(b, a, c);
|
||||
}
|
||||
|
||||
|
||||
fn last(a, b, c: int): int {
|
||||
return second(a, c);
|
||||
return second(a, c, b);
|
||||
}
|
||||
|
||||
|
||||
|
@ -20,8 +20,9 @@ fn middle(a, b, c: int): int {
|
|||
}
|
||||
|
||||
|
||||
# These should all print true!
|
||||
print(first(1, 2, 3) == 1);
|
||||
var x = first(second(1, 2), 3, 4);
|
||||
print(x == 2);
|
||||
print(middle(3, 1, 2) == 1);
|
||||
print(first(last(second(2, 1), 3, 0), 4, 5) == 0);
|
||||
print(second(1, 2, 3) == 2);
|
||||
print(last(1, 2, 3) == 3);
|
||||
print(first(second(1, 2, 3), 2, 3) == 2);
|
||||
print(last(1, 2, second(3, 4, 5)) == 4);
|
|
@ -11,19 +11,6 @@ fn outer: fn (n: int): int {
|
|||
}
|
||||
|
||||
|
||||
|
||||
fn getAdder(a, b: int): fn (): int64 {
|
||||
var x = a;
|
||||
var y = b;
|
||||
fn adder: int {
|
||||
return x + y;
|
||||
}
|
||||
return adder;
|
||||
}
|
||||
|
||||
|
||||
var x = outer();
|
||||
print(x(50)); # 50
|
||||
print(outer()(1)); # 1
|
||||
var a = 1;
|
||||
var b = 2;
|
||||
var adder = getAdder(a, b);
|
||||
print(adder()); # 3
|
Loading…
Reference in New Issue