Bug 1314055 - Part 1: Port async/await implementation from self-hosted JS to C++. r=till

This commit is contained in:
Tooru Fujisawa 2016-11-09 03:27:49 +09:00
Родитель f3e7540d57
Коммит 679dffd17a
11 изменённых файлов: 293 добавлений и 156 удалений

Просмотреть файл

@ -1,47 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Called when creating async function.
// See the comment for js::CreateAsyncFunction what unwrapped, wrapped and the
// return value are.
function AsyncFunction_wrap(unwrapped) {
var wrapper = function() {
// The try block is required to handle throws in default arguments properly.
try {
return AsyncFunction_start(callFunction(std_Function_apply, unwrapped, this, arguments));
} catch (e) {
var promiseCtor = GetBuiltinConstructor('Promise');
return callFunction(Promise_static_reject, promiseCtor, e);
}
};
return CreateAsyncFunction(wrapper, unwrapped);
}
function AsyncFunction_start(generator) {
return AsyncFunction_resume(generator, undefined, generator.next);
}
function AsyncFunction_resume(gen, v, method) {
var promiseCtor = GetBuiltinConstructor('Promise');
let result;
try {
// get back into async function, run to next await point
result = callFunction(method, gen, v);
} catch (exc) {
// The async function itself failed.
return callFunction(Promise_static_reject, promiseCtor, exc);
}
if (result.done)
return callFunction(Promise_static_resolve, promiseCtor, result.value);
// If we get here, `await` occurred. `gen` is paused at a yield point.
return callFunction(Promise_then,
callFunction(Promise_static_resolve, promiseCtor, result.value),
function(val) {
return AsyncFunction_resume(gen, val, gen.next);
}, function (err) {
return AsyncFunction_resume(gen, err, gen.throw);
});
}

Просмотреть файл

@ -93,9 +93,6 @@
#define REGEXP_STICKY_FLAG 0x08
#define REGEXP_UNICODE_FLAG 0x10
#define ASYNC_WRAPPED_SLOT 1
#define ASYNC_UNWRAPPED_SLOT 1
#define MODULE_OBJECT_ENVIRONMENT_SLOT 2
#define MODULE_STATE_FAILED 0

Просмотреть файл

@ -7135,17 +7135,16 @@ BytecodeEmitter::emitAsyncWrapperLambda(unsigned index, bool isArrow) {
}
bool
BytecodeEmitter::emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow) {
BytecodeEmitter::emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow)
{
// needsHomeObject can be true for propertyList for extended class.
// In that case push both unwrapped and wrapped function, in order to
// initialize home object of unwrapped function, and set wrapped function
// as a property.
//
// lambda // unwrapped
// getintrinsic // unwrapped AsyncFunction_wrap
// undefined // unwrapped AsyncFunction_wrap undefined
// dupat 2 // unwrapped AsyncFunction_wrap undefined unwrapped
// call 1 // unwrapped wrapped
// dup // unwrapped unwrapped
// toasync // unwrapped wrapped
//
// Emitted code is surrounded by the following code.
//
@ -7162,22 +7161,13 @@ BytecodeEmitter::emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isA
// pop // classObj classCtor classProto
//
// needsHomeObject is false for other cases, push wrapped function only.
if (needsHomeObject) {
if (!emitAsyncWrapperLambda(index, isArrow))
return false;
}
if (!emitAtomOp(cx->names().AsyncFunction_wrap, JSOP_GETINTRINSIC))
return false;
if (!emit1(JSOP_UNDEFINED))
if (!emitAsyncWrapperLambda(index, isArrow))
return false;
if (needsHomeObject) {
if (!emitDupAt(2))
return false;
} else {
if (!emitAsyncWrapperLambda(index, isArrow))
if (!emit1(JSOP_DUP))
return false;
}
if (!emitCall(JSOP_CALL, 1))
if (!emit1(JSOP_TOASYNC))
return false;
return true;
}

Просмотреть файл

@ -991,7 +991,7 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool lambdaParen)
if (IsAsmJSFunction(fun))
return AsmJSFunctionToString(cx, fun);
if (IsWrappedAsyncFunction(cx, fun)) {
if (IsWrappedAsyncFunction(fun)) {
RootedFunction unwrapped(cx, GetUnwrappedAsyncFunction(fun));
return FunctionToString(cx, unwrapped, lambdaParen);
}
@ -1930,10 +1930,13 @@ js::AsyncFunctionConstructor(JSContext* cx, unsigned argc, Value* vp)
if (!FunctionConstructor(cx, argc, vp, StarGenerator, AsyncFunction))
return false;
FixedInvokeArgs<1> args2(cx);
args2[0].set(args.rval());
return CallSelfHostedFunction(cx, cx->names().AsyncFunction_wrap,
NullHandleValue, args2, args.rval());
RootedFunction unwrapped(cx, &args.rval().toObject().as<JSFunction>());
RootedObject wrapped(cx, WrapAsyncFunction(cx, unwrapped));
if (!wrapped)
return false;
args.rval().setObject(*wrapped);
return true;
}
bool

Просмотреть файл

@ -750,7 +750,6 @@ selfhosted.inputs = [
'builtin/SelfHostingDefines.h',
'builtin/Utilities.js',
'builtin/Array.js',
'builtin/AsyncFunctions.js',
'builtin/Classes.js',
'builtin/Date.js',
'builtin/Error.js',

Просмотреть файл

@ -8,8 +8,9 @@
#include "jscompartment.h"
#include "builtin/SelfHostingDefines.h"
#include "builtin/Promise.h"
#include "vm/GlobalObject.h"
#include "vm/Interpreter.h"
#include "vm/SelfHosting.h"
using namespace js;
@ -46,73 +47,267 @@ GlobalObject::initAsyncFunction(JSContext* cx, Handle<GlobalObject*> global)
return true;
}
static bool AsyncFunctionStart(JSContext* cx, HandleValue generatorVal, MutableHandleValue rval);
#define UNWRAPPED_ASYNC_WRAPPED_SLOT 1
#define WRAPPED_ASYNC_UNWRAPPED_SLOT 0
// Async Functions proposal 1.1.8 and 1.2.14.
static bool
WrappedAsyncFunction(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedFunction wrapped(cx, &args.callee().as<JSFunction>());
RootedValue unwrappedVal(cx, wrapped->getExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT));
RootedFunction unwrapped(cx, &unwrappedVal.toObject().as<JSFunction>());
RootedValue thisValue(cx, args.thisv());
// Step 2.
// Also does a part of 2.2 steps 1-2.
RootedValue generatorVal(cx);
InvokeArgs args2(cx);
if (!args2.init(cx, argc))
return false;
for (size_t i = 0, len = argc; i < len; i++)
args2[i].set(args[i]);
if (Call(cx, unwrappedVal, thisValue, args2, &generatorVal)) {
// Steps 3, 5.
return AsyncFunctionStart(cx, generatorVal, args.rval());
}
// Step 4.
RootedValue exc(cx);
if (!GetAndClearException(cx, &exc))
return false;
RootedObject rejectPromise(cx, PromiseObject::unforgeableReject(cx, exc));
if (!rejectPromise)
return false;
// Step 5.
args.rval().setObject(*rejectPromise);
return true;
}
// Async Functions proposal 2.1 steps 1, 3 (partially).
// In the spec it creates a function, but we create 2 functions `unwrapped` and
// `wrapped`. `unwrapped` is a generator that corresponds to
// the async function's body, replacing `await` with `yield`. `wrapped` is a
// function that is visible to the outside, and handles yielded values.
JSObject*
js::WrapAsyncFunction(JSContext* cx, HandleFunction unwrapped)
{
MOZ_ASSERT(unwrapped->isStarGenerator());
// Create a new function with AsyncFunctionPrototype, reusing the name and
// the length of `unwrapped`.
// Step 1.
RootedObject proto(cx, GlobalObject::getOrCreateAsyncFunctionPrototype(cx, cx->global()));
if (!proto)
return nullptr;
RootedAtom funName(cx, unwrapped->name());
uint16_t length;
if (!unwrapped->getLength(cx, &length))
return nullptr;
// Steps 3 (partially).
RootedFunction wrapped(cx, NewFunctionWithProto(cx, WrappedAsyncFunction, length,
JSFunction::NATIVE_FUN, nullptr,
funName, proto,
AllocKind::FUNCTION_EXTENDED,
TenuredObject));
if (!wrapped)
return nullptr;
// Link them to each other to make GetWrappedAsyncFunction and
// GetUnwrappedAsyncFunction work.
unwrapped->setExtendedSlot(UNWRAPPED_ASYNC_WRAPPED_SLOT, ObjectValue(*wrapped));
wrapped->setExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT, ObjectValue(*unwrapped));
return wrapped;
}
// Async Functions proposal 2.2 steps 3.f, 3.g.
static bool
AsyncFunctionThrown(JSContext* cx, MutableHandleValue rval)
{
// Step 3.f.
RootedValue exc(cx);
if (!GetAndClearException(cx, &exc))
return false;
RootedObject rejectPromise(cx, PromiseObject::unforgeableReject(cx, exc));
if (!rejectPromise)
return false;
// Step 3.g.
rval.setObject(*rejectPromise);
return true;
}
// Async Functions proposal 2.2 steps 3.d-e, 3.g.
static bool
AsyncFunctionReturned(JSContext* cx, HandleValue value, MutableHandleValue rval)
{
// Steps 3.d-e.
RootedObject resolveObj(cx, PromiseObject::unforgeableResolve(cx, value));
if (!resolveObj)
return false;
// Step 3.g.
rval.setObject(*resolveObj);
return true;
}
static bool AsyncFunctionAwait(JSContext* cx, HandleValue generatorVal, HandleValue value,
MutableHandleValue rval);
enum class ResumeKind {
Normal,
Throw
};
// Async Functions proposal 2.2 steps 3-8, 2.4 steps 2-7, 2.5 steps 2-7.
static bool
AsyncFunctionResume(JSContext* cx, HandleValue generatorVal, ResumeKind kind,
HandleValue valueOrReason, MutableHandleValue rval)
{
// Execution context switching is handled in generator.
HandlePropertyName funName = kind == ResumeKind::Normal
? cx->names().StarGeneratorNext
: cx->names().StarGeneratorThrow;
FixedInvokeArgs<1> args(cx);
args[0].set(valueOrReason);
RootedValue result(cx);
if (!CallSelfHostedFunction(cx, funName, generatorVal, args, &result))
return AsyncFunctionThrown(cx, rval);
RootedObject resultObj(cx, &result.toObject());
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, value, rval);
return AsyncFunctionAwait(cx, generatorVal, value, rval);
}
// Async Functions proposal 2.2 steps 3-8.
static bool
AsyncFunctionStart(JSContext* cx, HandleValue generatorVal, MutableHandleValue rval)
{
return AsyncFunctionResume(cx, generatorVal, ResumeKind::Normal, UndefinedHandleValue, rval);
}
#define AWAITED_FUNC_GENERATOR_SLOT 0
static bool AsyncFunctionAwaitedFulfilled(JSContext* cx, unsigned argc, Value* vp);
static bool AsyncFunctionAwaitedRejected(JSContext* cx, unsigned argc, Value* vp);
// Async Functions proposal 2.3 steps 1-8.
static bool
AsyncFunctionAwait(JSContext* cx, HandleValue generatorVal, HandleValue value,
MutableHandleValue rval)
{
// Step 1 (implicit).
// Steps 2-3.
RootedObject resolveObj(cx, PromiseObject::unforgeableResolve(cx, value));
if (!resolveObj)
return false;
Rooted<PromiseObject*> resolvePromise(cx, resolveObj.as<PromiseObject>());
// Step 4.
RootedAtom funName(cx, cx->names().empty);
RootedFunction onFulfilled(cx, NewNativeFunction(cx, AsyncFunctionAwaitedFulfilled, 1,
funName, gc::AllocKind::FUNCTION_EXTENDED,
GenericObject));
if (!onFulfilled)
return false;
// Step 5.
RootedFunction onRejected(cx, NewNativeFunction(cx, AsyncFunctionAwaitedRejected, 1,
funName, gc::AllocKind::FUNCTION_EXTENDED,
GenericObject));
if (!onRejected)
return false;
// Step 6.
onFulfilled->setExtendedSlot(AWAITED_FUNC_GENERATOR_SLOT, generatorVal);
onRejected->setExtendedSlot(AWAITED_FUNC_GENERATOR_SLOT, generatorVal);
// Step 8.
RootedValue onFulfilledVal(cx, ObjectValue(*onFulfilled));
RootedValue onRejectedVal(cx, ObjectValue(*onRejected));
RootedObject resultPromise(cx, OriginalPromiseThen(cx, resolvePromise, onFulfilledVal,
onRejectedVal));
if (!resultPromise)
return false;
rval.setObject(*resultPromise);
return true;
}
// Async Functions proposal 2.4.
static bool
AsyncFunctionAwaitedFulfilled(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
RootedFunction F(cx, &args.callee().as<JSFunction>());
RootedValue value(cx, args[0]);
// Step 1.
RootedValue generatorVal(cx, F->getExtendedSlot(AWAITED_FUNC_GENERATOR_SLOT));
// Steps 2-7.
return AsyncFunctionResume(cx, generatorVal, ResumeKind::Normal, value, args.rval());
}
// Async Functions proposal 2.5.
static bool
AsyncFunctionAwaitedRejected(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
RootedFunction F(cx, &args.callee().as<JSFunction>());
RootedValue reason(cx, args[0]);
// Step 1.
RootedValue generatorVal(cx, F->getExtendedSlot(AWAITED_FUNC_GENERATOR_SLOT));
// Step 2-7.
return AsyncFunctionResume(cx, generatorVal, ResumeKind::Throw, reason, args.rval());
}
JSFunction*
js::GetWrappedAsyncFunction(JSFunction* unwrapped)
{
MOZ_ASSERT(unwrapped->isAsync());
return &unwrapped->getExtendedSlot(ASYNC_WRAPPED_SLOT).toObject().as<JSFunction>();
return &unwrapped->getExtendedSlot(UNWRAPPED_ASYNC_WRAPPED_SLOT).toObject().as<JSFunction>();
}
JSFunction*
js::GetUnwrappedAsyncFunction(JSFunction* wrapper)
js::GetUnwrappedAsyncFunction(JSFunction* wrapped)
{
JSFunction* unwrapped = &wrapper->getExtendedSlot(ASYNC_UNWRAPPED_SLOT).toObject().as<JSFunction>();
MOZ_ASSERT(IsWrappedAsyncFunction(wrapped));
JSFunction* unwrapped = &wrapped->getExtendedSlot(WRAPPED_ASYNC_UNWRAPPED_SLOT).toObject().as<JSFunction>();
MOZ_ASSERT(unwrapped->isAsync());
return unwrapped;
}
bool
js::IsWrappedAsyncFunction(JSContext* cx, JSFunction* wrapper)
js::IsWrappedAsyncFunction(JSFunction* fun)
{
return IsSelfHostedFunctionWithName(wrapper, cx->names().AsyncWrapped);
return fun->maybeNative() == WrappedAsyncFunction;
}
bool
js::CreateAsyncFunction(JSContext* cx, HandleFunction wrapper, HandleFunction unwrapped,
MutableHandleFunction result)
{
// Create a new function with AsyncFunctionPrototype, reusing the script
// and the environment of `wrapper` function, and the name and the length
// of `unwrapped` function.
RootedObject proto(cx, GlobalObject::getOrCreateAsyncFunctionPrototype(cx, cx->global()));
RootedObject scope(cx, wrapper->environment());
RootedAtom atom(cx, unwrapped->name());
RootedFunction wrapped(cx, NewFunctionWithProto(cx, nullptr, 0,
JSFunction::INTERPRETED_LAMBDA,
scope, atom, proto,
AllocKind::FUNCTION_EXTENDED, TenuredObject));
if (!wrapped)
return false;
wrapped->initScript(wrapper->nonLazyScript());
// Link them each other to make GetWrappedAsyncFunction and
// GetUnwrappedAsyncFunction work.
unwrapped->setExtendedSlot(ASYNC_WRAPPED_SLOT, ObjectValue(*wrapped));
wrapped->setExtendedSlot(ASYNC_UNWRAPPED_SLOT, ObjectValue(*unwrapped));
// The script of `wrapper` is self-hosted, so `wrapped` should also be
// set as self-hosted function.
wrapped->setIsSelfHostedBuiltin();
// Set LAZY_FUNCTION_NAME_SLOT to "AsyncWrapped" to make it detectable in
// IsWrappedAsyncFunction.
wrapped->setExtendedSlot(LAZY_FUNCTION_NAME_SLOT, StringValue(cx->names().AsyncWrapped));
// The length of the script of `wrapper` is different than the length of
// `unwrapped`. We should set actual length as resolved length, to avoid
// using the length of the script.
uint16_t length;
if (!unwrapped->getLength(cx, &length))
return false;
RootedValue lengthValue(cx, NumberValue(length));
if (!DefineProperty(cx, wrapped, cx->names().length, lengthValue,
nullptr, nullptr, JSPROP_READONLY))
{
return false;
}
result.set(wrapped);
return true;
}

Просмотреть файл

@ -16,14 +16,13 @@ JSFunction*
GetWrappedAsyncFunction(JSFunction* unwrapped);
JSFunction*
GetUnwrappedAsyncFunction(JSFunction* wrapper);
GetUnwrappedAsyncFunction(JSFunction* wrapped);
bool
IsWrappedAsyncFunction(JSContext* cx, JSFunction* wrapper);
IsWrappedAsyncFunction(JSFunction* fun);
bool
CreateAsyncFunction(JSContext* cx, HandleFunction wrapper, HandleFunction unwrapped,
MutableHandleFunction result);
JSObject*
WrapAsyncFunction(JSContext* cx, HandleFunction unwrapped);
} // namespace js

Просмотреть файл

@ -30,7 +30,6 @@
macro(as, as, "as") \
macro(Async, Async, "Async") \
macro(AsyncFunction, AsyncFunction, "AsyncFunction") \
macro(AsyncFunction_wrap, AsyncFunction_wrap, "AsyncFunction_wrap") \
macro(AsyncWrapped, AsyncWrapped, "AsyncWrapped") \
macro(async, async, "async") \
macro(await, await, "await") \
@ -280,6 +279,8 @@
macro(stack, stack, "stack") \
macro(star, star, "*") \
macro(starDefaultStar, starDefaultStar, "*default*") \
macro(StarGeneratorNext, StarGeneratorNext, "StarGeneratorNext") \
macro(StarGeneratorThrow, StarGeneratorThrow, "StarGeneratorThrow") \
macro(startTimestamp, startTimestamp, "startTimestamp") \
macro(state, state, "state") \
macro(static, static_, "static") \

Просмотреть файл

@ -38,6 +38,7 @@
#include "jit/BaselineJIT.h"
#include "jit/Ion.h"
#include "jit/IonAnalysis.h"
#include "vm/AsyncFunction.h"
#include "vm/Debugger.h"
#include "vm/GeneratorObject.h"
#include "vm/Opcodes.h"
@ -1869,7 +1870,6 @@ CASE(EnableInterruptsPseudoOpcode)
/* Various 1-byte no-ops. */
CASE(JSOP_NOP)
CASE(JSOP_NOP_DESTRUCTURING)
CASE(JSOP_UNUSED149)
CASE(JSOP_UNUSED182)
CASE(JSOP_UNUSED183)
CASE(JSOP_UNUSED187)
@ -3480,6 +3480,18 @@ CASE(JSOP_LAMBDA_ARROW)
}
END_CASE(JSOP_LAMBDA_ARROW)
CASE(JSOP_TOASYNC)
{
ReservedRooted<JSFunction*> unwrapped(&rootFunction0,
&REGS.sp[-1].toObject().as<JSFunction>());
JSObject* wrapped = WrapAsyncFunction(cx, unwrapped);
if (!wrapped)
goto error;
REGS.sp[-1].setObject(*wrapped);
}
END_CASE(JSOP_TOASYNC)
CASE(JSOP_CALLEE)
MOZ_ASSERT(REGS.fp()->isFunctionFrame());
PUSH_COPY(REGS.fp()->calleev());

Просмотреть файл

@ -1529,7 +1529,15 @@
* Stack: => new.target
*/ \
macro(JSOP_NEWTARGET, 148, "newtarget", NULL, 1, 0, 1, JOF_BYTE) \
macro(JSOP_UNUSED149, 149, "unused149", NULL, 1, 0, 0, JOF_BYTE) \
/*
* Pops the top of stack value as 'unwrapped', converts it to async
* function 'wrapped', and pushes 'wrapped' back on the stack.
* Category: Statements
* Type: Function
* Operands:
* Stack: unwrapped => wrapped
*/ \
macro(JSOP_TOASYNC, 149, "toasync", NULL, 1, 1, 1, JOF_BYTE) \
/*
* Pops the top two values 'lval' and 'rval' from the stack, then pushes
* the result of 'Math.pow(lval, rval)'.

Просмотреть файл

@ -38,7 +38,6 @@
#include "jit/InlinableNatives.h"
#include "js/CharacterEncoding.h"
#include "js/Date.h"
#include "vm/AsyncFunction.h"
#include "vm/Compression.h"
#include "vm/GeneratorObject.h"
#include "vm/Interpreter.h"
@ -1865,23 +1864,6 @@ js::ReportIncompatibleSelfHostedMethod(JSContext* cx, const CallArgs& args)
return false;
}
bool
intrinsic_CreateAsyncFunction(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
RootedFunction wrapper(cx, &args[0].toObject().as<JSFunction>());
RootedFunction unwrapped(cx, &args[1].toObject().as<JSFunction>());
RootedFunction wrapped(cx);
if (!CreateAsyncFunction(cx, wrapper, unwrapped, &wrapped))
return false;
args.rval().setObject(*wrapped);
return true;
}
/**
* Returns the default locale as a well-formed, but not necessarily canonicalized,
* BCP-47 language tag.
@ -2281,8 +2263,6 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("RuntimeDefaultLocale", intrinsic_RuntimeDefaultLocale, 0,0),
JS_FN("AddContentTelemetry", intrinsic_AddContentTelemetry, 2,0),
JS_FN("CreateAsyncFunction", intrinsic_CreateAsyncFunction, 1,0),
JS_INLINABLE_FN("_IsConstructing", intrinsic_IsConstructing, 0,0,
IntrinsicIsConstructing),
JS_INLINABLE_FN("SubstringKernel", intrinsic_SubstringKernel, 3,0,