Fix local variable declarations in the REPL.

A statement like:

  for (i in 1..2)

When run in the REPL declares a local variable ("i"), but not inside
a function or method body. This hit a corner case in the compiler
where it didn't have the correct slot indexes set up.

That corner case is because sometimes when you compile a chunk, local
slot zero is pre-allocated -- either to refer to "this" or to hold the
closure for a function so that it doesn't get GCed while running. But
if you're compiling top-level code, that slot isn't allocated. But top
level code for the REPL *should* be, because that gets invoked like a
function.

To simplify things, *every* compiled chunk now pre-allocates slot zero.
That way, there are fewer cases to keep in mind.

Also fixed an issue where a GC during an import could collected the
imported module body's closure.

Fix #456.
This commit is contained in:
Bob Nystrom 2018-04-27 08:20:49 -07:00
parent 64f8bf7ae3
commit c5ce6fac46
9 changed files with 66 additions and 73 deletions

View File

@ -498,7 +498,7 @@ static int addConstant(Compiler* compiler, Value constant)
// Initializes [compiler].
static void initCompiler(Compiler* compiler, Parser* parser, Compiler* parent,
bool isFunction)
bool isMethod)
{
compiler->parser = parser;
compiler->parent = parent;
@ -512,41 +512,41 @@ static void initCompiler(Compiler* compiler, Parser* parser, Compiler* parent,
parser->vm->compiler = compiler;
// Declare a local slot for either the closure or method receiver so that we
// don't try to reuse that slot for a user-defined local variable. For
// methods, we name it "this", so that we can resolve references to that like
// a normal variable. For functions, they have no explicit "this", so we use
// an empty name. That way references to "this" inside a function walks up
// the parent chain to find a method enclosing the function whose "this" we
// can close over.
compiler->numLocals = 1;
compiler->numSlots = compiler->numLocals;
if (isMethod)
{
compiler->locals[0].name = "this";
compiler->locals[0].length = 4;
}
else
{
compiler->locals[0].name = NULL;
compiler->locals[0].length = 0;
}
compiler->locals[0].depth = -1;
compiler->locals[0].isUpvalue = false;
if (parent == NULL)
{
compiler->numLocals = 0;
// Compiling top-level code, so the initial scope is module-level.
compiler->scopeDepth = -1;
}
else
{
// Declare a fake local variable for the receiver so that it's slot in the
// stack is taken. For methods, we call this "this", so that we can resolve
// references to that like a normal variable. For functions, they have no
// explicit "this". So we pick a bogus name. That way references to "this"
// inside a function will try to walk up the parent chain to find a method
// enclosing the function whose "this" we can close over.
compiler->numLocals = 1;
if (isFunction)
{
compiler->locals[0].name = NULL;
compiler->locals[0].length = 0;
}
else
{
compiler->locals[0].name = "this";
compiler->locals[0].length = 4;
}
compiler->locals[0].depth = -1;
compiler->locals[0].isUpvalue = false;
// The initial scope for function or method is a local scope.
// The initial scope for functions and methods is local scope.
compiler->scopeDepth = 0;
}
compiler->numSlots = compiler->numLocals;
compiler->fn = wrenNewFunction(parser->vm, parser->module,
compiler->numLocals);
}
@ -1867,7 +1867,7 @@ static void methodCall(Compiler* compiler, Code instruction,
called.arity++;
Compiler fnCompiler;
initCompiler(&fnCompiler, compiler->parser, compiler, true);
initCompiler(&fnCompiler, compiler->parser, compiler, false);
// Make a dummy signature to track the arity.
Signature fnSignature = { "", 0, SIG_METHOD, 0 };
@ -3080,7 +3080,7 @@ static void createConstructor(Compiler* compiler, Signature* signature,
int initializerSymbol)
{
Compiler methodCompiler;
initCompiler(&methodCompiler, compiler->parser, compiler, false);
initCompiler(&methodCompiler, compiler->parser, compiler, true);
// Allocate the instance.
emitOp(&methodCompiler, compiler->enclosingClass->isForeign
@ -3166,7 +3166,7 @@ static bool method(Compiler* compiler, Variable classVariable)
compiler->enclosingClass->signature = &signature;
Compiler methodCompiler;
initCompiler(&methodCompiler, compiler->parser, compiler, false);
initCompiler(&methodCompiler, compiler->parser, compiler, true);
// Compile the method signature.
signatureFn(&methodCompiler, &signature);
@ -3445,7 +3445,7 @@ ObjFn* wrenCompile(WrenVM* vm, ObjModule* module, const char* source,
int numExistingVariables = module->variables.count;
Compiler compiler;
initCompiler(&compiler, &parser, NULL, true);
initCompiler(&compiler, &parser, NULL, false);
ignoreNewlines(&compiler);
if (isExpression)

View File

@ -59,15 +59,7 @@ DEF_PRIMITIVE(fiber_new)
RETURN_ERROR("Function cannot take more than one parameter.");
}
ObjFiber* newFiber = wrenNewFiber(vm, closure);
// The compiler expects the first slot of a function to hold the receiver.
// Since a fiber's stack is invoked directly, it doesn't have one, so put it
// in here.
newFiber->stack[0] = NULL_VAL;
newFiber->stackTop++;
RETURN_OBJ(newFiber);
RETURN_OBJ(wrenNewFiber(vm, closure));
}
DEF_PRIMITIVE(fiber_abort)

View File

@ -159,27 +159,31 @@ ObjFiber* wrenNewFiber(WrenVM* vm, ObjClosure* closure)
ObjFiber* fiber = ALLOCATE(vm, ObjFiber);
initObj(vm, &fiber->obj, OBJ_FIBER, vm->fiberClass);
fiber->stack = stack;
fiber->stackTop = fiber->stack;
fiber->stackCapacity = stackCapacity;
fiber->frames = frames;
fiber->frameCapacity = INITIAL_CALL_FRAMES;
fiber->stack = stack;
fiber->stackCapacity = stackCapacity;
wrenResetFiber(vm, fiber, closure);
fiber->numFrames = 0;
return fiber;
}
void wrenResetFiber(WrenVM* vm, ObjFiber* fiber, ObjClosure* closure)
{
// Reset everything.
fiber->stackTop = fiber->stack;
fiber->openUpvalues = NULL;
fiber->caller = NULL;
fiber->error = NULL_VAL;
fiber->callerIsTrying = false;
fiber->numFrames = 0;
if (closure != NULL)
{
// Initialize the first call frame.
wrenAppendCallFrame(vm, fiber, closure, fiber->stack);
// Initialize the first call frame.
if (closure != NULL) wrenAppendCallFrame(vm, fiber, closure, fiber->stack);
// The first slot always holds the closure.
fiber->stackTop[0] = OBJ_VAL(closure);
fiber->stackTop++;
}
return fiber;
}
void wrenEnsureStack(WrenVM* vm, ObjFiber* fiber, int needed)

View File

@ -628,10 +628,6 @@ ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn);
// Creates a new fiber object that will invoke [closure].
ObjFiber* wrenNewFiber(WrenVM* vm, ObjClosure* closure);
// Resets [fiber] back to an initial state where it is ready to invoke
// [closure].
void wrenResetFiber(WrenVM* vm, ObjFiber* fiber, ObjClosure* closure);
// Adds a new [CallFrame] to [fiber] invoking [closure] whose stack starts at
// [stackStart].
static inline void wrenAppendCallFrame(WrenVM* vm, ObjFiber* fiber,

View File

@ -1249,17 +1249,18 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
Value name = fn->constants.data[READ_SHORT()];
// Make a slot on the stack for the module's fiber to place the return
// value. It will be popped after this fiber is resumed.
PUSH(NULL_VAL);
// value. It will be popped after this fiber is resumed. Store the
// imported module's closure in the slot in case a GC happens when
// invoking the closure.
PUSH(wrenImportModule(vm, name));
Value result = wrenImportModule(vm, name);
if (!IS_NULL(fiber->error)) RUNTIME_ERROR();
// If we get a closure, call it to execute the module body.
if (IS_CLOSURE(result))
if (IS_CLOSURE(PEEK()))
{
STORE_FRAME();
ObjClosure* closure = AS_CLOSURE(result);
ObjClosure* closure = AS_CLOSURE(PEEK());
callFunction(vm, fiber, closure, 1);
LOAD_FRAME();
}
@ -1417,7 +1418,7 @@ WrenInterpretResult wrenInterpretInModule(WrenVM* vm, const char* module,
wrenPushRoot(vm, (Obj*)closure);
ObjFiber* fiber = wrenNewFiber(vm, closure);
wrenPopRoot(vm); // closure.
return runInterpreter(vm, fiber);
}

View File

@ -1,6 +1,6 @@
{
var a0 = "value"
var a1 = a0
// Slot zero is always taken to hold the closure or receiver.
var a1 = "value"
var a2 = a1
var a3 = a2
var a4 = a3

View File

@ -1,10 +1,10 @@
// Can have more than 256 local variables in a local scope, as long as they
// Can have more than 255 local variables in a local scope, as long as they
// aren't all in scope at the same time.
{
{
var a0 = "value a"
var a1 = a0
// Slot zero is always taken to hold the closure or receiver.
var a1 = "value a"
var a2 = a1
var a3 = a2
var a4 = a3
@ -263,8 +263,8 @@
}
{
var b0 = "value b"
var b1 = b0
// Slot zero is always taken to hold the closure or receiver.
var b1 = "value b"
var b2 = b1
var b3 = b2
var b4 = b3

View File

@ -1,6 +1,6 @@
{
var a0 = "value"
var a1 = a0
// Slot zero is always taken to hold the closure or receiver.
var a1 = "value"
var a2 = a1
var a3 = a2
var a4 = a3

View File

@ -1,6 +1,6 @@
{
var a0 = "value"
var a1 = a0
// Slot zero is always taken to hold the closure or receiver.
var a1 = "value"
var a2 = a1
var a3 = a2
var a4 = a3