From e4043d69b4b70480cc6b9fdbc16509ecfd44e2eb Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sun, 1 Dec 2013 14:59:56 -0800 Subject: [PATCH] Unify code for ending compiler. There's now a single code path for the end of a chunk of bytecode, so we can eventually put code there for capturing closures. --- src/wren_compiler.c | 69 ++++++++++++++++++++++++++++++++++----------- src/wren_vm.c | 5 ++-- src/wren_vm.h | 31 +++++++++++++++----- 3 files changed, 78 insertions(+), 27 deletions(-) diff --git a/src/wren_compiler.c b/src/wren_compiler.c index 7577f2f8..f079bc8d 100644 --- a/src/wren_compiler.c +++ b/src/wren_compiler.c @@ -211,6 +211,12 @@ static int initCompiler(Compiler* compiler, Parser* parser, compiler->locals[0].depth = -1; // The initial scope for function or method is a local scope. + // TODO(bob): Need to explicitly pop this scope at end of fn/method so + // that we can correctly close over locals declared at top level of member. + // also, when done compiling fn/method, need to count total number of + // upvalues and store in fnobj. note: have to make sure we include upvalues + // added because a fn within this one closed over something outside of this + // one and we had to add upvalue here to flatten the closure. compiler->scopeDepth = 0; } @@ -227,6 +233,31 @@ static int initCompiler(Compiler* compiler, Parser* parser, return addConstant(parent, OBJ_VAL(compiler->fn)); } +// Emits one bytecode instruction or argument. +static int emit(Compiler* compiler, Code code) +{ + compiler->fn->bytecode[compiler->numCodes++] = code; + return compiler->numCodes - 1; +} + +// Finishes [compiler], which is compiling a function, method, or chunk of top +// level code. If there is a parent compiler, then this emits code in the +// parent compiler to load the resulting function. +static void endCompiler(Compiler* compiler, int constant) +{ + // End the function's code. + emit(compiler, CODE_END); + + // TODO(bob): will need to compile different code to capture upvalues if fn + // has them. + if (compiler->parent != NULL) + { + // In the function that contains this one, load the resulting function object. + emit(compiler->parent, CODE_CONSTANT); + emit(compiler->parent, constant); + } +} + // Outputs a compile or syntax error. static void error(Compiler* compiler, const char* format, ...) { @@ -649,14 +680,7 @@ static void consume(Compiler* compiler, TokenType expected, } } -// Code generation utilities --------------------------------------------------- - -// Emits one bytecode instruction or argument. -static int emit(Compiler* compiler, Code code) -{ - compiler->fn->bytecode[compiler->numCodes++] = code; - return compiler->numCodes - 1; -} +// Variables and scopes -------------------------------------------------------- // Parses a name token and declares a variable in the current scope with that // name. Returns its symbol. @@ -770,6 +794,9 @@ static void popScope(Compiler* compiler) emit(compiler, CODE_POP); } + // TODO(bob): Need to emit code to capture upvalue for any local going out of + // scope now that is closed over. + compiler->scopeDepth--; } @@ -796,6 +823,15 @@ static int resolveName(Compiler* compiler, int* isGlobal) } } + // TODO(bob): Closures! + // look in current upvalues to see if we've already closed over it + // if so, just use that + // walk up parent chain looking in their local scopes for variable + // if we find it, need to close over it here + // add upvalue to fn being compiled + // return index of upvalue + // instead of isGlobal, should be some local/upvalue/global enum + // If we got here, it wasn't in a local scope, so try the global scope. *isGlobal = 1; return findSymbol(&compiler->parser->vm->globalSymbols, start, length); @@ -986,11 +1022,7 @@ static void function(Compiler* compiler, int allowAssignment) expression(&fnCompiler, 0); } - emit(&fnCompiler, CODE_END); - - // Compile the code to load it. - emit(compiler, CODE_CONSTANT); - emit(compiler, constant); + endCompiler(&fnCompiler, constant); } static void field(Compiler* compiler, int allowAssignment) @@ -1047,6 +1079,8 @@ static void name(Compiler* compiler, int allowAssignment) // Compile the right-hand side. statement(compiler); + // TODO(bob): Handle assigning to upvalue. + if (isGlobal) { emit(compiler, CODE_STORE_GLOBAL); @@ -1061,6 +1095,8 @@ static void name(Compiler* compiler, int allowAssignment) return; } + // TODO(bob): Handle reading upvalue. + // Otherwise, it's just a variable access. if (isGlobal) { @@ -1514,12 +1550,11 @@ void method(Compiler* compiler, Code instruction, SignatureFn signature) emit(&methodCompiler, 0); } - emit(&methodCompiler, CODE_END); + endCompiler(&methodCompiler, constant); - // Compile the code to define the method it. + // Compile the code to define the method. emit(compiler, instruction); emit(compiler, symbol); - emit(compiler, constant); } // Compiles a name-binding statement. @@ -1670,7 +1705,7 @@ ObjFn* wrenCompile(WrenVM* vm, const char* source) emit(&compiler, CODE_POP); } - emit(&compiler, CODE_END); + endCompiler(&compiler, -1); unpinObj(vm); diff --git a/src/wren_vm.c b/src/wren_vm.c index 0ec71eeb..9bdefd44 100644 --- a/src/wren_vm.c +++ b/src/wren_vm.c @@ -554,7 +554,7 @@ Value interpret(WrenVM* vm, ObjFn* fn) { int type = instruction; int symbol = READ_ARG(); - int constant = READ_ARG(); + Value method = POP(); ObjClass* classObj = AS_CLASS(PEEK()); switch (type) @@ -576,8 +576,7 @@ Value interpret(WrenVM* vm, ObjFn* fn) break; } - ObjFn* body = AS_FN(frame->fn->constants[constant]); - classObj->methods[symbol].fn = body; + classObj->methods[symbol].fn = AS_FN(method); DISPATCH(); } diff --git a/src/wren_vm.h b/src/wren_vm.h index fc5d11bc..716823a7 100644 --- a/src/wren_vm.h +++ b/src/wren_vm.h @@ -28,17 +28,21 @@ typedef enum // Pop a superclass off the stack, then push a new class that extends it. CODE_SUBCLASS, - // Add a method for symbol [arg1] with body stored in constant [arg2] to the - // class on the top of stack. Does not modify the stack. + // Define a method for symbol [arg] whose body is the function popped off the + // top of the stack. The class receiving the method is on top of the stack + // (after the function is popped off) and remains after this. CODE_METHOD_INSTANCE, - // Add a method for symbol [arg1] with body stored in constant [arg2] to the - // metaclass of the class on the top of stack. Does not modify the stack. + // Define a method for symbol [arg] whose body is the function popped off the + // top of the stack. The class receiving the method is the metaclass of the + // class on top of the stack (after the function is popped off) and remains + // after this. CODE_METHOD_STATIC, - // Add a constructor method for symbol [arg1] with body stored in constant - // [arg2] to the metaclass of the class on the top of stack. Does not modify - // the stack. + // Define a constructor method for symbol [arg] whose body is the function + // popped off the top of the stack. The class receiving the method is the + // metaclass of the class on top of the stack (after the function is popped + // off) and remains after this. CODE_METHOD_CTOR, // Create a new list with [arg] elements. The top [arg] values on the stack @@ -115,6 +119,19 @@ typedef enum CODE_END } Code; + +// TODO(bob): add opcode for closure +// functions with no upvalues can just be loaded as constants like normal +// functions which do have upvalues need a CODE_CLOSURE op. it takes the +// constant index of the fn as an argument. +// it loads that fn, then wraps it in a closure object. +// then it captures all of the upvalues. +// those can either be upvalues for locals, or upvalues that close over an +// outer upvalue. +// +// then add ops for loading and storing an upvalue. take upvalue index as arg. + + typedef struct { // TODO(bob): Make this dynamically sized.