Don't allow calling the root fiber.
The VM used to not detect this case. It meant you could get into a situation where another fiber's caller had completed. Then, when it tried to resume that fiber, the VM would crash because there was nothing to resume to. This is part of thinking through all the cases around re-entrancy. Added some notes for that too.
This commit is contained in:
+9
-3
@@ -88,8 +88,14 @@ static bool runFiber(WrenVM* vm, ObjFiber* fiber, Value* args, bool isCall,
|
||||
|
||||
if (isCall)
|
||||
{
|
||||
// You can't call a called fiber, but you can transfer directly to it,
|
||||
// which is why this check is gated on `isCall`. This way, after resuming a
|
||||
// suspended fiber, it will run and then return to the fiber that called it
|
||||
// and so on.
|
||||
if (fiber->caller != NULL) RETURN_ERROR("Fiber has already been called.");
|
||||
|
||||
if (fiber->state == FIBER_ROOT) RETURN_ERROR("Cannot call root fiber.");
|
||||
|
||||
// Remember who ran it.
|
||||
fiber->caller = vm->fiber;
|
||||
}
|
||||
@@ -181,7 +187,7 @@ DEF_PRIMITIVE(fiber_try)
|
||||
runFiber(vm, AS_FIBER(args[0]), args, true, false, "try");
|
||||
|
||||
// If we're switching to a valid fiber to try, remember that we're trying it.
|
||||
if (IS_NULL(vm->fiber->error)) vm->fiber->callerIsTrying = true;
|
||||
if (IS_NULL(vm->fiber->error)) vm->fiber->state = FIBER_TRY;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -192,7 +198,7 @@ DEF_PRIMITIVE(fiber_yield)
|
||||
|
||||
// Unhook this fiber from the one that called it.
|
||||
current->caller = NULL;
|
||||
current->callerIsTrying = false;
|
||||
current->state = FIBER_OTHER;
|
||||
|
||||
if (vm->fiber != NULL)
|
||||
{
|
||||
@@ -210,7 +216,7 @@ DEF_PRIMITIVE(fiber_yield1)
|
||||
|
||||
// Unhook this fiber from the one that called it.
|
||||
current->caller = NULL;
|
||||
current->callerIsTrying = false;
|
||||
current->state = FIBER_OTHER;
|
||||
|
||||
if (vm->fiber != NULL)
|
||||
{
|
||||
|
||||
+1
-1
@@ -171,7 +171,7 @@ ObjFiber* wrenNewFiber(WrenVM* vm, ObjClosure* closure)
|
||||
fiber->openUpvalues = NULL;
|
||||
fiber->caller = NULL;
|
||||
fiber->error = NULL_VAL;
|
||||
fiber->callerIsTrying = false;
|
||||
fiber->state = FIBER_OTHER;
|
||||
|
||||
if (closure != NULL)
|
||||
{
|
||||
|
||||
+19
-4
@@ -293,6 +293,24 @@ typedef struct
|
||||
Value* stackStart;
|
||||
} CallFrame;
|
||||
|
||||
// Tracks how this fiber has been invoked, aside from the ways that can be
|
||||
// detected from the state of other fields in the fiber.
|
||||
typedef enum
|
||||
{
|
||||
// The fiber is being run from another fiber using a call to `try()`.
|
||||
FIBER_TRY,
|
||||
|
||||
// The fiber was directly invoked by `runInterpreter()`. This means it's the
|
||||
// initial fiber used by a call to `wrenCall()` or `wrenInterpret()`.
|
||||
FIBER_ROOT,
|
||||
|
||||
// The fiber is invoked some other way. If [caller] is `NULL` then the fiber
|
||||
// was invoked using `call()`. If [numFrames] is zero, then the fiber has
|
||||
// finished running and is done. If [numFrames] is one and that frame's `ip`
|
||||
// points to the first byte of code, the fiber has not been started yet.
|
||||
FIBER_OTHER,
|
||||
} FiberState;
|
||||
|
||||
typedef struct sObjFiber
|
||||
{
|
||||
Obj obj;
|
||||
@@ -331,10 +349,7 @@ typedef struct sObjFiber
|
||||
// error object. Otherwise, it will be null.
|
||||
Value error;
|
||||
|
||||
// This will be true if the caller that called this fiber did so using "try".
|
||||
// In that case, if this fiber fails with an error, the error will be given
|
||||
// to the caller.
|
||||
bool callerIsTrying;
|
||||
FiberState state;
|
||||
} ObjFiber;
|
||||
|
||||
typedef enum
|
||||
|
||||
+3
-3
@@ -399,7 +399,7 @@ static void runtimeError(WrenVM* vm)
|
||||
current->error = error;
|
||||
|
||||
// If the caller ran this fiber using "try", give it the error and stop.
|
||||
if (current->callerIsTrying)
|
||||
if (current->state == FIBER_TRY)
|
||||
{
|
||||
// Make the caller's try method return the error message.
|
||||
current->caller->stackTop[-1] = vm->fiber->error;
|
||||
@@ -768,12 +768,12 @@ static Value getModuleVariable(WrenVM* vm, ObjModule* module,
|
||||
}
|
||||
|
||||
// The main bytecode interpreter loop. This is where the magic happens. It is
|
||||
// also, as you can imagine, highly performance critical. Returns `true` if the
|
||||
// fiber completed without error.
|
||||
// also, as you can imagine, highly performance critical.
|
||||
static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
|
||||
{
|
||||
// Remember the current fiber so we can find it if a GC happens.
|
||||
vm->fiber = fiber;
|
||||
fiber->state = FIBER_ROOT;
|
||||
|
||||
// Hoist these into local variables. They are accessed frequently in the loop
|
||||
// but assigned less frequently. Keeping them in locals and updating them when
|
||||
|
||||
Reference in New Issue
Block a user