Bug 1316098 - Optimize out result object allocation for await/return in async function.

Tag #1287
This commit is contained in:
Gaming4JC 2019-12-13 20:59:32 -05:00 • committed by Roy Tam
commit c9f1fa2cda
10 changed files with 39 additions and 138 deletions

View file

@ -110,8 +110,8 @@ resumption value has one of the following forms:
the `new` expression returns the frame's `this` value. Similarly, if the `new` expression returns the frame's `this` value. Similarly, if
the function is the constructor for a subclass, then a non-object the function is the constructor for a subclass, then a non-object
value may result in a TypeError. value may result in a TypeError.
If the frame is a generator or async function, then <i>value</i> must If the frame is a generator function, then <i>value</i> must conform to the
conform to the iterator protocol: it must be a non-proxy object of the form iterator protocol: it must be a non-proxy object of the form
<code>{ done: <i>boolean</i>, value: <i>v</i> }</code>, where <code>{ done: <i>boolean</i>, value: <i>v</i> }</code>, where
both `done` and `value` are ordinary properties. both `done` and `value` are ordinary properties.

View file

@ -8552,7 +8552,7 @@ bool
BytecodeEmitter::emitYield(ParseNode* pn) BytecodeEmitter::emitYield(ParseNode* pn)
{ {
MOZ_ASSERT(sc->isFunctionBox()); MOZ_ASSERT(sc->isFunctionBox());
MOZ_ASSERT(pn->getOp() == JSOP_YIELD || pn->getOp() == JSOP_AWAIT); MOZ_ASSERT(pn->getOp() == JSOP_YIELD);
bool needsIteratorResult = sc->asFunctionBox()->needsIteratorResult(); bool needsIteratorResult = sc->asFunctionBox()->needsIteratorResult();
if (needsIteratorResult) { if (needsIteratorResult) {
@ -8574,12 +8574,27 @@ BytecodeEmitter::emitYield(ParseNode* pn)
if (!emitGetDotGenerator()) if (!emitGetDotGenerator())
return false; return false;
if (!emitYieldOp(pn->getOp())) if (!emitYieldOp(JSOP_YIELD))
return false; return false;
return true; return true;
} }
bool
BytecodeEmitter::emitAwait(ParseNode* pn)
{
MOZ_ASSERT(sc->isFunctionBox());
MOZ_ASSERT(pn->getOp() == JSOP_AWAIT);
if (!emitTree(pn->pn_kid))
return false;
if (!emitGetDotGenerator())
return false;
if (!emitYieldOp(JSOP_AWAIT))
return false;
return true;
}
bool bool
BytecodeEmitter::emitYieldStar(ParseNode* iter) BytecodeEmitter::emitYieldStar(ParseNode* iter)
{ {
@ -10631,11 +10646,15 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::
break; break;
case PNK_YIELD: case PNK_YIELD:
case PNK_AWAIT:
if (!emitYield(pn)) if (!emitYield(pn))
return false; return false;
break; break;
case PNK_AWAIT:
if (!emitAwait(pn))
return false;
break;
case PNK_STATEMENTLIST: case PNK_STATEMENTLIST:
if (!emitStatementList(pn)) if (!emitStatementList(pn))
return false; return false;

View file

@ -613,6 +613,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter
MOZ_MUST_USE bool emitYield(ParseNode* pn); MOZ_MUST_USE bool emitYield(ParseNode* pn);
MOZ_MUST_USE bool emitYieldOp(JSOp op); MOZ_MUST_USE bool emitYieldOp(JSOp op);
MOZ_MUST_USE bool emitYieldStar(ParseNode* iter); MOZ_MUST_USE bool emitYieldStar(ParseNode* iter);
MOZ_MUST_USE bool emitAwait(ParseNode* pn);
MOZ_MUST_USE bool emitPropLHS(ParseNode* pn); MOZ_MUST_USE bool emitPropLHS(ParseNode* pn);
MOZ_MUST_USE bool emitPropOp(ParseNode* pn, JSOp op); MOZ_MUST_USE bool emitPropOp(ParseNode* pn, JSOp op);

View file

@ -553,7 +553,7 @@ class FunctionBox : public ObjectBox, public SharedContext
} }
bool needsIteratorResult() const { bool needsIteratorResult() const {
return isStarGenerator() || isAsync(); return isStarGenerator();
} }
bool isAsync() const { return asyncKind() == AsyncFunction; } bool isAsync() const { return asyncKind() == AsyncFunction; }

View file

@ -9,28 +9,6 @@ async function f() {
} }
`); `);
// To continue testing after uncaught exception, remember the exception and
// return normal completeion.
var currentFrame;
var uncaughtException;
dbg.uncaughtExceptionHook = function(e) {
uncaughtException = e;
return {
return: currentFrame.eval("({ done: true, value: 'uncaught' })").return
};
};
function testUncaughtException() {
uncaughtException = undefined;
var val = g.eval(`
var val;
f().then(v => { val = v });
drainJobQueue();
val;
`);
assertEq(val, "uncaught");
assertEq(uncaughtException instanceof TypeError, true);
}
// Just continue // Just continue
dbg.onExceptionUnwind = function(frame) { dbg.onExceptionUnwind = function(frame) {
return undefined; return undefined;
@ -42,83 +20,10 @@ drainJobQueue();
assertEq(exc instanceof ReferenceError, true); assertEq(exc instanceof ReferenceError, true);
`); `);
// Should return object. // Return with resumption value.
dbg.onExceptionUnwind = function(frame) { dbg.onExceptionUnwind = function(frame) {
currentFrame = frame;
return { return {
return: "foo" return: 10
};
};
testUncaughtException();
// The object should have `done` property and `value` property.
dbg.onExceptionUnwind = function(frame) {
currentFrame = frame;
return {
return: frame.eval("({})").return
};
};
testUncaughtException();
// The object should have `done` property.
dbg.onExceptionUnwind = function(frame) {
currentFrame = frame;
return {
return: frame.eval("({ value: 10 })").return
};
};
testUncaughtException();
// The object should have `value` property.
dbg.onExceptionUnwind = function(frame) {
currentFrame = frame;
return {
return: frame.eval("({ done: true })").return
};
};
testUncaughtException();
// `done` property should be a boolean value.
dbg.onExceptionUnwind = function(frame) {
currentFrame = frame;
return {
return: frame.eval("({ done: 10, value: 10 })").return
};
};
testUncaughtException();
// `done` property shouldn't be an accessor.
dbg.onExceptionUnwind = function(frame) {
currentFrame = frame;
return {
return: frame.eval("({ get done() { return true; }, value: 10 })").return
};
};
testUncaughtException();
// `value` property shouldn't be an accessor.
dbg.onExceptionUnwind = function(frame) {
currentFrame = frame;
return {
return: frame.eval("({ done: true, get value() { return 10; } })").return
};
};
testUncaughtException();
// The object shouldn't be a Proxy.
dbg.onExceptionUnwind = function(frame) {
currentFrame = frame;
return {
return: frame.eval("new Proxy({ done: true, value: 10 }, {})").return
};
};
testUncaughtException();
// Correct resumption value.
dbg.onExceptionUnwind = function(frame) {
currentFrame = frame;
return {
return: frame.eval("({ done: true, value: 10 })").return
}; };
}; };
var val = g.eval(` var val = g.eval(`

View file

@ -438,7 +438,6 @@ MSG_DEF(JSMSG_SC_SAB_DISABLED, 0, JSEXN_TYPEERR, "SharedArrayBuffer not
// Debugger // Debugger
MSG_DEF(JSMSG_ASSIGN_FUNCTION_OR_NULL, 1, JSEXN_TYPEERR, "value assigned to {0} must be a function or null") MSG_DEF(JSMSG_ASSIGN_FUNCTION_OR_NULL, 1, JSEXN_TYPEERR, "value assigned to {0} must be a function or null")
MSG_DEF(JSMSG_DEBUG_BAD_AWAIT, 0, JSEXN_TYPEERR, "await expression received invalid value")
MSG_DEF(JSMSG_DEBUG_BAD_LINE, 0, JSEXN_TYPEERR, "invalid line number") MSG_DEF(JSMSG_DEBUG_BAD_LINE, 0, JSEXN_TYPEERR, "invalid line number")
MSG_DEF(JSMSG_DEBUG_BAD_OFFSET, 0, JSEXN_TYPEERR, "invalid script offset") MSG_DEF(JSMSG_DEBUG_BAD_OFFSET, 0, JSEXN_TYPEERR, "invalid script offset")
MSG_DEF(JSMSG_DEBUG_BAD_REFERENT, 2, JSEXN_TYPEERR, "{0} does not refer to {1}") MSG_DEF(JSMSG_DEBUG_BAD_REFERENT, 2, JSEXN_TYPEERR, "{0} does not refer to {1}")

View file

@ -171,22 +171,14 @@ AsyncFunctionResume(JSContext* cx, Handle<PromiseObject*> resultPromise, HandleV
: cx->names().StarGeneratorThrow; : cx->names().StarGeneratorThrow;
FixedInvokeArgs<1> args(cx); FixedInvokeArgs<1> args(cx);
args[0].set(valueOrReason); args[0].set(valueOrReason);
RootedValue result(cx); RootedValue value(cx);
if (!CallSelfHostedFunction(cx, funName, generatorVal, args, &result)) if (!CallSelfHostedFunction(cx, funName, generatorVal, args, &value))
return AsyncFunctionThrown(cx, resultPromise); return AsyncFunctionThrown(cx, resultPromise);
RootedObject resultObj(cx, &result.toObject()); if (generatorVal.toObject().as<GeneratorObject>().isAfterAwait())
RootedValue doneVal(cx);
RootedValue value(cx);
if (!GetProperty(cx, resultObj, resultObj, cx->names().done, &doneVal))
return false;
if (!GetProperty(cx, resultObj, resultObj, cx->names().value, &value))
return false;
if (doneVal.toBoolean())
return AsyncFunctionReturned(cx, resultPromise, value);
return AsyncFunctionAwait(cx, resultPromise, value); return AsyncFunctionAwait(cx, resultPromise, value);
return AsyncFunctionReturned(cx, resultPromise, value);
} }
// Async Functions proposal 2.2 steps 3-8. // Async Functions proposal 2.2 steps 3-8.
@ -242,9 +234,3 @@ js::IsWrappedAsyncFunction(JSFunction* fun)
{ {
return fun->maybeNative() == WrappedAsyncFunction; return fun->maybeNative() == WrappedAsyncFunction;
} }
MOZ_MUST_USE bool
js::CheckAsyncResumptionValue(JSContext* cx, HandleValue v)
{
return CheckStarGeneratorResumptionValue(cx, v);
}

View file

@ -35,9 +35,6 @@ MOZ_MUST_USE bool
AsyncFunctionAwaitedRejected(JSContext* cx, Handle<PromiseObject*> resultPromise, AsyncFunctionAwaitedRejected(JSContext* cx, Handle<PromiseObject*> resultPromise,
HandleValue generatorVal, HandleValue reason); HandleValue generatorVal, HandleValue reason);
MOZ_MUST_USE bool
CheckAsyncResumptionValue(JSContext* cx, HandleValue v);
} // namespace js } // namespace js
#endif /* vm_AsyncFunction_h */ #endif /* vm_AsyncFunction_h */

View file

@ -32,7 +32,6 @@
#include "js/Vector.h" #include "js/Vector.h"
#include "proxy/ScriptedProxyHandler.h" #include "proxy/ScriptedProxyHandler.h"
#include "vm/ArgumentsObject.h" #include "vm/ArgumentsObject.h"
#include "vm/AsyncFunction.h"
#include "vm/DebuggerMemory.h" #include "vm/DebuggerMemory.h"
#include "vm/GeneratorObject.h" #include "vm/GeneratorObject.h"
#include "vm/SPSProfiler.h" #include "vm/SPSProfiler.h"
@ -1560,16 +1559,11 @@ CheckResumptionValue(JSContext* cx, AbstractFramePtr frame, const Maybe<HandleVa
JSTrapStatus status, MutableHandleValue vp) JSTrapStatus status, MutableHandleValue vp)
{ {
if (status == JSTRAP_RETURN && frame && frame.isFunctionFrame()) { if (status == JSTRAP_RETURN && frame && frame.isFunctionFrame()) {
// Don't let a { return: ... } resumption value make a generator or // Don't let a { return: ... } resumption value make a generator
// async function violate the iterator protocol. The return value from // function violate the iterator protocol. The return value from
// such a frame must have the form { done: <bool>, value: <anything> }. // such a frame must have the form { done: <bool>, value: <anything> }.
RootedFunction callee(cx, frame.callee()); RootedFunction callee(cx, frame.callee());
if (callee->isAsync()) { if (callee->isStarGenerator()) {
if (!CheckAsyncResumptionValue(cx, vp)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_AWAIT);
return false;
}
} else if (callee->isStarGenerator() || callee->isAsync()) {
if (!CheckStarGeneratorResumptionValue(cx, vp)) { if (!CheckStarGeneratorResumptionValue(cx, vp)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_YIELD); JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_YIELD);
return false; return false;

View file

@ -2130,12 +2130,12 @@
*/ \ */ \
macro(JSOP_DEBUGAFTERYIELD, 208, "debugafteryield", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_DEBUGAFTERYIELD, 208, "debugafteryield", NULL, 1, 0, 0, JOF_BYTE) \
/* /*
* Pops the generator and the return value 'result', stops interpretation * Pops the generator and the return value 'promise', stops interpretation
* and returns 'result'. Pushes resolved value onto the stack. * and returns 'promise'. Pushes resolved value onto the stack.
* Category: Statements * Category: Statements
* Type: Generator * Type: Generator
* Operands: uint24_t yieldAndAwaitIndex * Operands: uint24_t yieldAndAwaitIndex
* Stack: result, gen => resolved * Stack: promise, gen => resolved
*/ \ */ \
macro(JSOP_AWAIT, 209, "await", NULL, 4, 2, 1, JOF_UINT24) \ macro(JSOP_AWAIT, 209, "await", NULL, 4, 2, 1, JOF_UINT24) \
macro(JSOP_UNUSED210, 210, "unused210", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED210, 210, "unused210", NULL, 1, 0, 0, JOF_BYTE) \