at compile time, at the end of a functions block compilation we now emit a new instruction OP_CLOSURE
that the VM will use at runtime to wrap our function objects within a new closure object. the idea is that we’re going to use this closure object to store references to closed over variables (upvalues).
as a refresher, each time we create a compiler instance per function declaration, we also create a new function object via newFunction
.
static void function(FunctionType type) {
Compiler compiler;
initCompiler(&compiler, type);
beginScope();
...
ObjFunction* function = endCompiler();
// emit a closure instruction!
emitBytes(OP_CLOSURE, makeConstant(OBJ_VAL(function)));
}
...
static void initCompiler(Compiler* compiler, FunctionType type) {
compiler->enclosing = current;
compiler->function = NULL;
compiler->type = type;
compiler->localCount = 0;
compiler->scopeDepth = 0;
compiler->function = newFunction();
current = compiler;
if (type != TYPE_SCRIPT) {
current->function->name = copyString(parser.previous.start,
parser.previous.length);
}
now at the end of the function compilation we make sure to emit an OP_CLOSURE so that at runtime, we use that opcode to wrap the raw ObjFunction in a closure and push it onto the stack.
below is the disassembly of fun foo() { fun bar(){} }
> fun foo() { fun bar(){} }
== bar ==
0000 1 OP_NIL
0001 | OP_RETURN
== foo ==
0000 1 OP_CLOSURE 0 <fn bar>
0002 | OP_NIL
0003 | OP_RETURN
== <script> ==
0000 1 OP_CLOSURE 1 <fn foo>
0002 | OP_DEFINE_GLOBAL 0 'foo'
0004 2 OP_NIL
0005 | OP_RETURN
[ <script> ]
0000 1 OP_CLOSURE 1 <fn foo>
[ <script> ][ <fn foo> ]
0002 | OP_DEFINE_GLOBAL 0 'foo'
[ <script> ]
0004 2 OP_NIL
[ <script> ][ nil ]
0005 | OP_RETURN
there’s a couple of interesting things about this design choice
- every function, regardless of whether they close over variables, will be treated like a closure at runtime. this adds both overhead through the creation of each closure function and indirection
- closed over values are stored on the clojure instead of the function, which nicely reflects the reality that we made have multiple different closures of the same function!