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():
|
if i < self.frames.high():
|
||||||
stdout.styledWrite(fgYellow, ", ")
|
stdout.styledWrite(fgYellow, ", ")
|
||||||
styledEcho fgMagenta, "]"
|
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:
|
if self.envs.len() !> 0:
|
||||||
stdout.styledWrite(fgGreen, "Environments: ", fgMagenta, "[")
|
stdout.styledWrite(fgGreen, "Environments: ", fgMagenta, "[")
|
||||||
for i, e in self.envs:
|
for i, e in self.envs:
|
||||||
|
@ -655,13 +662,6 @@ when debugVM: # So nim shuts up
|
||||||
if i < self.envs.high():
|
if i < self.envs.high():
|
||||||
stdout.styledWrite(fgYellow, ", ")
|
stdout.styledWrite(fgYellow, ", ")
|
||||||
styledEcho fgMagenta, "]"
|
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:
|
if self.results.len() !> 0:
|
||||||
stdout.styledWrite(fgYellow, "Function Results: ", fgMagenta, "[")
|
stdout.styledWrite(fgYellow, "Function Results: ", fgMagenta, "[")
|
||||||
for i, e in self.results:
|
for i, e in self.results:
|
||||||
|
@ -669,13 +669,6 @@ when debugVM: # So nim shuts up
|
||||||
if i < self.results.high():
|
if i < self.results.high():
|
||||||
stdout.styledWrite(fgYellow, ", ")
|
stdout.styledWrite(fgYellow, ", ")
|
||||||
styledEcho fgMagenta, "]"
|
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
|
discard readLine stdin
|
||||||
|
|
||||||
|
|
||||||
|
@ -771,7 +764,7 @@ proc dispatch*(self: PeonVM) =
|
||||||
self.results.add(self.getNil())
|
self.results.add(self.getNil())
|
||||||
# Creates a new call frame
|
# Creates a new call frame
|
||||||
self.frames.add(uint64(self.calls.len() - 2))
|
self.frames.add(uint64(self.calls.len() - 2))
|
||||||
self.closures.add(offset)
|
self.closures.add(offset - 1)
|
||||||
# Loads the arguments onto the stack
|
# Loads the arguments onto the stack
|
||||||
for _ in 0..<argc:
|
for _ in 0..<argc:
|
||||||
self.pushc(self.pop())
|
self.pushc(self.pop())
|
||||||
|
|
|
@ -476,6 +476,7 @@ proc getStackPos(self: Compiler, name: Name): int =
|
||||||
if name == variable:
|
if name == variable:
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
|
inc(result)
|
||||||
if not found:
|
if not found:
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
|
@ -954,6 +955,32 @@ proc emitFunction(self: Compiler, name: Name) =
|
||||||
self.emitBytes(self.getStackPos(name).toTriple(), name.line)
|
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) =
|
proc generateCall(self: Compiler, fn: Name, args: seq[Expression], onStack: bool = false) =
|
||||||
## Small wrapper that abstracts emitting a call instruction
|
## Small wrapper that abstracts emitting a call instruction
|
||||||
## for a given function
|
## for a given function
|
||||||
|
@ -1008,6 +1035,8 @@ proc beginScope(self: Compiler) =
|
||||||
proc `$`(self: Type): string = $self[]
|
proc `$`(self: Type): string = $self[]
|
||||||
|
|
||||||
|
|
||||||
|
# Flattens our weird function tree into a linear
|
||||||
|
# list
|
||||||
proc flattenImpl(self: Type, to: var seq[Type]) =
|
proc flattenImpl(self: Type, to: var seq[Type]) =
|
||||||
to.add(self)
|
to.add(self)
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
|
@ -1028,24 +1057,41 @@ proc endScope(self: Compiler) =
|
||||||
for name in self.names:
|
for name in self.names:
|
||||||
if name.depth > self.scopeDepth:
|
if name.depth > self.scopeDepth:
|
||||||
names.add(name)
|
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
|
# We don't increase the pop count for these kinds of objects
|
||||||
# because they're not stored the same way as regular variables
|
# 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)
|
inc(popCount)
|
||||||
if name.isFunDecl and not name.valueType.isClosure and name.valueType.children.len() > 0 and name.depth == 0:
|
if name.isFunDecl and name.valueType.children.len() > 0 and name.depth == 0:
|
||||||
# If a function at the top level contains any closures,
|
# When a closure goes out of scope, its environment is reclaimed.
|
||||||
# when it goes out of scope all of the environments that
|
# This includes the environments of every other closure that may
|
||||||
# belong to its inner functions also go out of
|
# have been contained within it, too
|
||||||
# scope
|
|
||||||
var i = 0
|
var i = 0
|
||||||
let f = name.valueType
|
var all = flatten(name.valueType)
|
||||||
for fn in flatten(f):
|
# Why this? Well, it's simple: if a function returns
|
||||||
if fn.isClosure:
|
# a closure, that function becomes a closure too. The
|
||||||
for y in 0..f.envLen:
|
# environments of closures are aligned one after the
|
||||||
self.closedOver.delete(y + i)
|
# other, so if a and b are both closures, but only b
|
||||||
self.emitByte(PopClosure, self.peek().token.line)
|
# closes over a value, both a and b will have an envLen
|
||||||
self.emitBytes((y + i).toTriple(), self.peek().token.line)
|
# of 1, which would cause us to emit one extra PopClosure
|
||||||
inc(i)
|
# 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 popCount > 1:
|
||||||
# If we're popping less than 65535 variables, then
|
# If we're popping less than 65535 variables, then
|
||||||
# we can emit a PopN instruction. This is true for
|
# we can emit a PopN instruction. This is true for
|
||||||
|
@ -1434,8 +1480,8 @@ proc identifier(self: Compiler, node: IdentExpr) =
|
||||||
else:
|
else:
|
||||||
if s.valueType.kind == Function and s.isFunDecl:
|
if s.valueType.kind == Function and s.isFunDecl:
|
||||||
# Functions have no runtime
|
# Functions have no runtime
|
||||||
# representation, so we need
|
# representation: they're just
|
||||||
# to create one on the fly
|
# a location to jump to
|
||||||
self.emitByte(LoadInt64, node.token.line)
|
self.emitByte(LoadInt64, node.token.line)
|
||||||
self.emitBytes(self.chunk.writeConstant(s.codePos.toLong()), 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:
|
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
|
var fn = self.currentFunction.valueType
|
||||||
while true:
|
while true:
|
||||||
fn.isClosure = true
|
fn.isClosure = true
|
||||||
|
fn.envLen += 1
|
||||||
if fn.parent.isNil():
|
if fn.parent.isNil():
|
||||||
break
|
break
|
||||||
fn = fn.parent
|
fn = fn.parent
|
||||||
s.isClosedOver = true
|
s.isClosedOver = true
|
||||||
self.currentFunction.valueType.envLen += 1
|
|
||||||
self.closedOver.add(s)
|
self.closedOver.add(s)
|
||||||
let stackIdx = self.getStackPos(s).toTriple()
|
let stackIdx = self.getStackPos(s).toTriple()
|
||||||
let closeIdx = self.getClosurePos(s).toTriple()
|
let closeIdx = self.getClosurePos(s).toTriple()
|
||||||
let oldLen = self.chunk.code.len()
|
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(StoreClosure.uint8, s.belongsTo.codePos)
|
||||||
self.chunk.code.insert(stackIdx[0], s.belongsTo.codePos + 1)
|
self.chunk.code.insert(stackIdx[0], s.belongsTo.codePos + 1)
|
||||||
self.chunk.code.insert(stackIdx[1], s.belongsTo.codePos + 2)
|
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):
|
for exp in reversed(all):
|
||||||
self.callExpr(exp)
|
self.callExpr(exp)
|
||||||
# TODO
|
# 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)
|
# TODO: Calling lambdas on-the-fly (i.e. on the same line)
|
||||||
else:
|
else:
|
||||||
let typ = self.inferType(node)
|
let typ = self.inferType(node)
|
||||||
|
|
|
@ -150,6 +150,17 @@ proc callInstruction(self: Debugger, instruction: OpCode) =
|
||||||
self.current += 1
|
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) =
|
proc constantInstruction(self: Debugger, instruction: OpCode) =
|
||||||
## Debugs instructions that operate on the constant table
|
## Debugs instructions that operate on the constant table
|
||||||
var size: uint
|
var size: uint
|
||||||
|
@ -203,6 +214,8 @@ proc disassembleInstruction*(self: Debugger) =
|
||||||
self.storeClosureInstruction(opcode)
|
self.storeClosureInstruction(opcode)
|
||||||
of Call:
|
of Call:
|
||||||
self.callInstruction(opcode)
|
self.callInstruction(opcode)
|
||||||
|
of CallClosure:
|
||||||
|
self.callClosureInstruction(opcode)
|
||||||
of jumpInstructions:
|
of jumpInstructions:
|
||||||
self.jumpInstruction(opcode)
|
self.jumpInstruction(opcode)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -5,13 +5,13 @@ fn first(a, b, c: int): int {
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn second(a, b: int): int {
|
fn second(a, b, c: int): int {
|
||||||
return first(b, a, 0);
|
return first(b, a, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn last(a, b, c: int): int {
|
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);
|
print(first(1, 2, 3) == 1);
|
||||||
var x = first(second(1, 2), 3, 4);
|
print(second(1, 2, 3) == 2);
|
||||||
print(x == 2);
|
print(last(1, 2, 3) == 3);
|
||||||
print(middle(3, 1, 2) == 1);
|
print(first(second(1, 2, 3), 2, 3) == 2);
|
||||||
print(first(last(second(2, 1), 3, 0), 4, 5) == 0);
|
print(last(1, 2, second(3, 4, 5)) == 4);
|
|
@ -11,19 +11,6 @@ fn outer: fn (n: int): int {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var x = outer();
|
||||||
fn getAdder(a, b: int): fn (): int64 {
|
print(x(50)); # 50
|
||||||
var x = a;
|
|
||||||
var y = b;
|
|
||||||
fn adder: int {
|
|
||||||
return x + y;
|
|
||||||
}
|
|
||||||
return adder;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
print(outer()(1)); # 1
|
print(outer()(1)); # 1
|
||||||
var a = 1;
|
|
||||||
var b = 2;
|
|
||||||
var adder = getAdder(a, b);
|
|
||||||
print(adder()); # 3
|
|
Loading…
Reference in New Issue