From 3214496c8d642ee4a53a5d0d36962888078cfb07 Mon Sep 17 00:00:00 2001 From: "igor.bukanov%gmail.com" Date: Tue, 29 Aug 2006 06:39:21 +0000 Subject: [PATCH] Bug 349320: generator cleanup, r=brendan a=schrep,beltnzer git-svn-id: svn://10.0.0.236/branches/MOZILLA_1_8_BRANCH@208696 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/js/src/jsgc.c | 13 +- mozilla/js/src/jsgc.h | 2 +- mozilla/js/src/jsinterp.c | 2 +- mozilla/js/src/jsiter.c | 348 ++++++++++++++++++++++---------------- mozilla/js/src/jsiter.h | 13 +- 5 files changed, 221 insertions(+), 157 deletions(-) diff --git a/mozilla/js/src/jsgc.c b/mozilla/js/src/jsgc.c index 1f604aa72be..1c22fe443fc 100644 --- a/mozilla/js/src/jsgc.c +++ b/mozilla/js/src/jsgc.c @@ -889,13 +889,14 @@ CloseIteratorStates(JSContext *cx) #if JS_HAS_GENERATORS void -js_RegisterGeneratorObject(JSContext *cx, JSGenerator *gen) +js_RegisterOpenGenerator(JSContext *cx, JSGenerator *gen) { JSRuntime *rt; rt = cx->runtime; JS_ASSERT(!rt->gcRunning); JS_ASSERT(rt->state != JSRTS_LANDING); + JS_ASSERT(gen->state == JSGEN_OPEN); JS_LOCK_GC(rt); gen->next = rt->gcCloseState.reachableList; @@ -935,8 +936,14 @@ FindAndMarkObjectsToClose(JSContext *cx, JSGCInvocationKind gckind) *genp = gen->next; gen->next = NULL; if (gen->state != JSGEN_CLOSED) { - /* Generator cannot be nesting, i.e., running or closing. */ - JS_ASSERT(gen->state <= JSGEN_OPEN); + /* + * Generator cannot be nesting, i.e., running or closing, and + * newborn generator is never registered with GC. + * + * XXX: we do need to run the close hook if the last yield + * happened outside a try block. + */ + JS_ASSERT(gen->state == JSGEN_OPEN); *rt->gcCloseState.todoTail = gen; rt->gcCloseState.todoTail = &gen->next; rt->gcCloseState.todoCount++; diff --git a/mozilla/js/src/jsgc.h b/mozilla/js/src/jsgc.h index d10d61e1d3b..2b337371090 100644 --- a/mozilla/js/src/jsgc.h +++ b/mozilla/js/src/jsgc.h @@ -170,7 +170,7 @@ typedef struct JSGCCloseState { } JSGCCloseState; extern void -js_RegisterGeneratorObject(JSContext *cx, JSGenerator *gen); +js_RegisterOpenGenerator(JSContext *cx, JSGenerator *gen); JSBool js_RunCloseHooks(JSContext *cx); diff --git a/mozilla/js/src/jsinterp.c b/mozilla/js/src/jsinterp.c index 07a8d81bd41..f5b41d7949c 100644 --- a/mozilla/js/src/jsinterp.c +++ b/mozilla/js/src/jsinterp.c @@ -6078,7 +6078,7 @@ interrupt: BEGIN_CASE(JSOP_YIELD) ASSERT_NOT_THROWING(cx); - if (FRAME_TO_GENERATOR(fp)->state & JSGEN_CLOSING) { + if (FRAME_TO_GENERATOR(fp)->state == JSGEN_CLOSING) { str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, fp->argv[-2], NULL); if (str) { diff --git a/mozilla/js/src/jsiter.c b/mozilla/js/src/jsiter.c index 28771161ddc..45e5c54a5dd 100644 --- a/mozilla/js/src/jsiter.c +++ b/mozilla/js/src/jsiter.c @@ -594,43 +594,21 @@ js_ThrowStopIteration(JSContext *cx, JSObject *obj) #if JS_HAS_GENERATORS -/* - * Execute generator's close hook after GC detects that the object has become - * unreachable. - */ -JSBool -js_CloseGeneratorObject(JSContext *cx, JSGenerator *gen) -{ - JSObject *obj, *proto; - jsval fval, rval; - const jsid id = ATOM_TO_JSID(cx->runtime->atomState.closeAtom); - - /* JSGenerator.closeLink must be already unlinked from all lists. */ - JS_ASSERT(!gen->next); - JS_ASSERT(gen != cx->runtime->gcCloseState.reachableList); - JS_ASSERT(gen != cx->runtime->gcCloseState.todoHead); - - /* - * Get generator_close from cached class object for gen's scope chain, so - * as not to depend on gen->obj's mutable prototype chain. - */ - obj = gen->obj; - if (!js_GetClassPrototype(cx, obj, INT_TO_JSID(JSProto_Generator), &proto)) - return JS_FALSE; - if (!JS_GetMethodById(cx, proto, id, &proto, &fval)) - return JS_FALSE; - - return js_InternalCall(cx, obj, fval, 0, NULL, &rval); -} - static void generator_finalize(JSContext *cx, JSObject *obj) { JSGenerator *gen; gen = (JSGenerator *) JS_GetPrivate(cx, obj); - if (gen) + if (gen) { + /* + * gen can be open on shutdown when close hooks are ignored or when + * the embedding cancels scheduled close hooks. + */ + JS_ASSERT(gen->state == JSGEN_NEWBORN || gen->state == JSGEN_CLOSED || + gen->state == JSGEN_OPEN); JS_free(cx, gen); + } } static uint32 @@ -746,8 +724,6 @@ js_NewGenerator(JSContext *cx, JSStackFrame *fp) goto bad; } - /* Register after we have properly initialized the private slot. */ - js_RegisterGeneratorObject(cx, gen); return obj; bad: @@ -755,43 +731,58 @@ js_NewGenerator(JSContext *cx, JSStackFrame *fp) return NULL; } +typedef enum JSGeneratorOp { + JSGENOP_NEXT, + JSGENOP_SEND, + JSGENOP_THROW, + JSGENOP_CLOSE +} JSGeneratorOp; + /* - * Common subroutine of generator_send and generator_close. + * Start newborn or restart yielding generator and perform the requested + * operation inside its frame. */ static JSBool -generator_send_sub(JSContext *cx, JSObject *obj, JSGenerator *gen, - uintN argc, jsval *argv, jsval *rval) +SendToGenerator(JSContext *cx, JSGeneratorOp op, JSObject *obj, + JSGenerator *gen, jsval arg, jsval *rval) { - JSString *str; + jsval genexit; JSStackFrame *fp; + jsval junk; JSArena *arena; JSBool ok; - jsval junk; + jsval exn; + JSClass *clasp; + JSString *str; - if (gen->state & JSGEN_RUNNING) { - str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, argv[-1], - NULL); - if (str) { - JS_ReportErrorNumberUC(cx, js_GetErrorMessage, NULL, - JSMSG_NESTING_GENERATOR, - JSSTRING_CHARS(str)); + JS_ASSERT(gen->state == JSGEN_NEWBORN || gen->state == JSGEN_OPEN); + switch (op) { + case JSGENOP_NEXT: + case JSGENOP_SEND: + if (gen->state == JSGEN_OPEN) { + /* + * Store the argument to send as the result of the yield + * expression. + */ + gen->frame.sp[-1] = arg; } - return JS_FALSE; - } + gen->state = JSGEN_RUNNING; + break; - if (gen->state == JSGEN_NEWBORN) { - if (argc != 0 && !JSVAL_IS_VOID(argv[0])) { - str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, argv[0], - NULL); - if (str) { - JS_ReportErrorNumberUC(cx, js_GetErrorMessage, NULL, - JSMSG_BAD_GENERATOR_SEND, - JSSTRING_CHARS(str)); - } + case JSGENOP_THROW: + JS_SetPendingException(cx, arg); + gen->state = JSGEN_RUNNING; + break; + + default: + JS_ASSERT(op == JSGENOP_CLOSE); + if (!js_FindClassObject(cx, NULL, INT_TO_JSID(JSProto_GeneratorExit), + &genexit)) { return JS_FALSE; } - - gen->state = JSGEN_OPEN; + JS_SetPendingException(cx, genexit); + gen->state = JSGEN_CLOSING; + break; } fp = cx->fp; @@ -799,100 +790,36 @@ generator_send_sub(JSContext *cx, JSObject *obj, JSGenerator *gen, cx->stackPool.current = &gen->arena; cx->fp = &gen->frame; gen->frame.down = fp; - - /* Store the argument to send as the result of the yield expression. */ - gen->frame.sp[-1] = (argc != 0) ? argv[0] : JSVAL_VOID; - gen->state |= JSGEN_RUNNING; ok = js_Interpret(cx, gen->frame.pc, &junk); - gen->state &= ~JSGEN_RUNNING; cx->fp = fp; cx->stackPool.current = arena; - if (!ok) { - /* An error, exception, or silent termination by branch callback. */ - JS_ASSERT(!(gen->frame.flags & JSFRAME_YIELDING)); - gen->state = JSGEN_CLOSED; - return JS_FALSE; + if (gen->frame.flags & JSFRAME_YIELDING) { + /* Yield cannot fail, throw or be called on closing. */ + JS_ASSERT(ok); + JS_ASSERT(!cx->throwing); + JS_ASSERT(gen->state == JSGEN_RUNNING); + JS_ASSERT(op != JSGENOP_CLOSE); + gen->frame.flags &= ~JSFRAME_YIELDING; + gen->state = JSGEN_OPEN; + *rval = gen->frame.rval; + return JS_TRUE; } - if (!(gen->frame.flags & JSFRAME_YIELDING)) { + gen->state = JSGEN_CLOSED; + + if (ok) { /* Returned, explicitly or by falling off the end. */ - gen->state = JSGEN_CLOSED; + if (op == JSGENOP_CLOSE) + return JS_TRUE; return js_ThrowStopIteration(cx, obj); } - gen->frame.flags &= ~JSFRAME_YIELDING; - *rval = gen->frame.rval; - return JS_TRUE; -} - -static JSBool -generator_send(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, - jsval *rval) -{ - JSGenerator *gen; - - if (!JS_InstanceOf(cx, obj, &js_GeneratorClass, argv)) - return JS_FALSE; - - gen = (JSGenerator *) JS_GetPrivate(cx, obj); - if (!gen || gen->state >= JSGEN_CLOSING) - return !JS_IsExceptionPending(cx) && js_ThrowStopIteration(cx, obj); - - return generator_send_sub(cx, obj, gen, argc, argv, rval); -} - -static JSBool -generator_next(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, - jsval *rval) -{ - return generator_send(cx, obj, 0, argv, rval); -} - -static JSBool -generator_throw(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, - jsval *rval) -{ - JS_SetPendingException(cx, argv[0]); - return generator_send(cx, obj, 0, argv, rval); -} - -static JSBool -generator_close(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, - jsval *rval) -{ - JSGenerator *gen; - jsval genexit, exn; - JSBool ok; - JSClass *clasp; - JSString *str; - - if (!JS_InstanceOf(cx, obj, &js_GeneratorClass, argv)) - return JS_FALSE; - - gen = (JSGenerator *) JS_GetPrivate(cx, obj); - if (!gen || gen->state == JSGEN_CLOSED) - return JS_TRUE; - - if (!js_FindClassObject(cx, NULL, INT_TO_JSID(JSProto_GeneratorExit), - &genexit)) { - return JS_FALSE; - } - - /* Throw GeneratorExit at the generator and ignore the returned status. */ - JS_SetPendingException(cx, genexit); - gen->state |= JSGEN_CLOSING; - ok = generator_send_sub(cx, obj, gen, 0, argv, rval); - gen->state = JSGEN_CLOSED; - - if (!cx->throwing) { + if (op == JSGENOP_CLOSE && cx->throwing) { /* - * If out-of-memory was reported or the branch callback canceled the - * generator, fail immediately. + * Generator terminated with an exception. Clear if it is a normal + * exit signal. */ - if (!ok) - return JS_FALSE; - } else { exn = cx->exception; if (!JSVAL_IS_PRIMITIVE(exn)) { clasp = OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(exn)); @@ -902,17 +829,148 @@ generator_close(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, return JS_TRUE; } } + str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, + OBJECT_TO_JSVAL(obj), NULL); + if (str) { + JS_ReportErrorNumberUC(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_GENERATOR_EXIT, + JSSTRING_CHARS(str)); + } + return JS_FALSE; } - str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, argv[-1], NULL); - if (str) { - JS_ReportErrorNumberUC(cx, js_GetErrorMessage, NULL, - JSMSG_BAD_GENERATOR_EXIT, - JSSTRING_CHARS(str)); - } + /* + * An error, silent termination by branch callback or an exception thrown + * in resposnse to next|send|throw. Propagate the condition to the caller. + */ return JS_FALSE; } +/* + * Execute gen's close hook after the GC detects that the object has become + * unreachable. + */ +JSBool +js_CloseGeneratorObject(JSContext *cx, JSGenerator *gen) +{ + /* JSGenerator.closeLink must be already unlinked from all lists. */ + JS_ASSERT(!gen->next); + JS_ASSERT(gen != cx->runtime->gcCloseState.reachableList); + JS_ASSERT(gen != cx->runtime->gcCloseState.todoHead); + + /* We pass null as rval since SendToGenerator never uses it with CLOSE. */ + return SendToGenerator(cx, JSGENOP_CLOSE, gen->obj, gen, JSVAL_VOID, NULL); +} + +/* + * Common subroutine of generator_(next|send|throw|close) methods. + */ +static JSBool +generator_op(JSContext *cx, JSGeneratorOp op, + JSObject *obj, uintN argc, jsval *argv, jsval *rval) +{ + JSGenerator *gen; + JSString *str; + jsval arg; + JSBool wasNewborn; + + if (!JS_InstanceOf(cx, obj, &js_GeneratorClass, argv)) + return JS_FALSE; + + gen = (JSGenerator *) JS_GetPrivate(cx, obj); + switch (gen->state) { + case JSGEN_NEWBORN: + switch (op) { + case JSGENOP_NEXT: + case JSGENOP_THROW: + break; + + case JSGENOP_SEND: + if (!JSVAL_IS_VOID(argv[0])) { + str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, + argv[0], NULL); + if (str) { + JS_ReportErrorNumberUC(cx, js_GetErrorMessage, NULL, + JSMSG_BAD_GENERATOR_SEND, + JSSTRING_CHARS(str)); + } + return JS_FALSE; + } + break; + + default: + JS_ASSERT(op == JSGENOP_CLOSE); + gen->state = JSGEN_CLOSED; + return JS_TRUE; + } + wasNewborn = JS_TRUE; + break; + + case JSGEN_OPEN: + wasNewborn = JS_FALSE; + break; + + case JSGEN_RUNNING: + case JSGEN_CLOSING: + str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, argv[-1], + NULL); + if (str) { + JS_ReportErrorNumberUC(cx, js_GetErrorMessage, NULL, + JSMSG_NESTING_GENERATOR, + JSSTRING_CHARS(str)); + } + return JS_FALSE; + + default: + JS_ASSERT(gen->state == JSGEN_CLOSED); + if (op == JSGENOP_CLOSE) + return JS_TRUE; + return js_ThrowStopIteration(cx, obj); + } + + arg = (op == JSGENOP_SEND || op == JSGENOP_THROW) + ? argv[0] + : JSVAL_VOID; + if (!SendToGenerator(cx, op, obj, gen, arg, rval)) + return JS_FALSE; + if (wasNewborn && gen->state == JSGEN_OPEN) { + /* + * The generator yielded the first time. Register it with GC to ensure + * that suspended finally blocks will be executed. + */ + js_RegisterOpenGenerator(cx, gen); + } + return JS_TRUE; +} + +static JSBool +generator_send(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + return generator_op(cx, JSGENOP_SEND, obj, argc, argv, rval); +} + +static JSBool +generator_next(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + return generator_op(cx, JSGENOP_NEXT, obj, argc, argv, rval); +} + +static JSBool +generator_throw(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + return generator_op(cx, JSGENOP_THROW, obj, argc, argv, rval); +} + +static JSBool +generator_close(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, + jsval *rval) +{ + return generator_op(cx, JSGENOP_CLOSE, obj, argc, argv, rval); +} + /* * NB: we pass (0, NULL) as (argc, argv) to js_fun_toString in both of these * native methods, which tells js_fun_toString to use its obj parameter as the diff --git a/mozilla/js/src/jsiter.h b/mozilla/js/src/jsiter.h index 2f860828b4a..274818b1815 100644 --- a/mozilla/js/src/jsiter.h +++ b/mozilla/js/src/jsiter.h @@ -95,15 +95,14 @@ js_ThrowStopIteration(JSContext *cx, JSObject *obj); #if JS_HAS_GENERATORS /* - * Generator state codes are actually flag bits, to allow RUNNING to be added - * to OPEN, and CLOSING to be added to OPEN and RUNNING. + * Generator state codes. */ typedef enum JSGeneratorState { - JSGEN_NEWBORN = 0, /* not yet started */ - JSGEN_OPEN = 1, /* started by a .next() or .send(undefined) call */ - JSGEN_RUNNING = 2, /* currently executing via .next(), etc., call */ - JSGEN_CLOSING = 4, /* close method is doing .send(GeneratorExit) */ - JSGEN_CLOSED = 8 /* closed, cannot be started or closed again */ + JSGEN_NEWBORN, /* not yet started */ + JSGEN_OPEN, /* started by a .next() or .send(undefined) call */ + JSGEN_RUNNING, /* currently executing via .next(), etc., call */ + JSGEN_CLOSING, /* close method is doing .send(GeneratorExit) */ + JSGEN_CLOSED /* closed, cannot be started or closed again */ } JSGeneratorState; struct JSGenerator {