Bug 1185106 - Part 4: Implement async functions. (r=efaust, r=jandem)

This commit is contained in:
Mariusz Kierski 2015-10-05 13:24:03 -07:00
parent 9a71bb7002
commit 48c5982418
20 changed files with 436 additions and 53 deletions

View File

@ -284,6 +284,7 @@ selfhosting:: selfhosted.out.h
selfhosting_srcs := \
$(srcdir)/builtin/Utilities.js \
$(srcdir)/builtin/Array.js \
$(srcdir)/builtin/AsyncFunctions.js \
$(srcdir)/builtin/Date.js \
$(srcdir)/builtin/Error.js \
$(srcdir)/builtin/Generator.js \

View File

@ -0,0 +1,46 @@
/* 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/. */
function AsyncFunction_wrap(genFunction) {
var wrapper = function() {
// The try block is required to handle throws in default arguments properly.
try {
return AsyncFunction_start(callFunction(std_Function_apply, genFunction, this, arguments));
} catch (e) {
return Promise_reject(e);
}
};
SetFunctionExtendedSlot(genFunction, 1, wrapper);
var name = GetFunName(genFunction);
if (typeof name !== 'undefined')
SetFunName(wrapper, name);
return wrapper;
}
function AsyncFunction_start(generator) {
return AsyncFunction_resume(generator, undefined, generator.next);
}
function AsyncFunction_resume(gen, v, method) {
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 Promise_reject(exc);
}
if (result.done)
return Promise_resolve(result.value);
// If we get here, `await` occurred. `gen` is paused at a yield point.
return callFunction(Promise_then,
Promise_resolve(result.value),
function(val) {
return AsyncFunction_resume(gen, val, gen.next);
}, function (err) {
return AsyncFunction_resume(gen, err, gen.throw);
});
}

View File

@ -240,7 +240,6 @@ bool
BytecodeEmitter::emit1(JSOp op)
{
MOZ_ASSERT(checkStrictOrSloppy(op));
ptrdiff_t offset;
if (!emitCheck(1, &offset))
return false;
@ -1991,7 +1990,6 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer)
case PNK_PREDECREMENT:
case PNK_POSTDECREMENT:
case PNK_THROW:
case PNK_AWAIT:
MOZ_ASSERT(pn->isArity(PN_UNARY));
*answer = true;
return true;
@ -2013,6 +2011,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer)
case PNK_YIELD_STAR:
case PNK_YIELD:
case PNK_AWAIT:
MOZ_ASSERT(pn->isArity(PN_BINARY));
*answer = true;
return true;
@ -5858,6 +5857,16 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
MOZ_ASSERT(pn->getOp() == JSOP_LAMBDA);
pn->setOp(JSOP_FUNWITHPROTO);
}
if (funbox->isAsync())
return emitAsyncWrapper(index, funbox->needsHomeObject());
if (pn->getOp() == JSOP_DEFFUN) {
if (!emitIndex32(JSOP_LAMBDA, index))
return false;
return emit1(JSOP_DEFFUN);
}
return emitIndex32(pn->getOp(), index);
}
@ -5877,7 +5886,14 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
MOZ_ASSERT(pn->getOp() == JSOP_NOP);
MOZ_ASSERT(atBodyLevel());
switchToPrologue();
if (!emitIndex32(JSOP_DEFFUN, index))
if (funbox->isAsync()) {
if (!emitAsyncWrapper(index, fun->isMethod()))
return false;
} else {
if (!emitIndex32(JSOP_LAMBDA, index))
return false;
}
if (!emit1(JSOP_DEFFUN))
return false;
if (!updateSourceCoordNotes(pn->pn_pos.begin))
return false;
@ -5891,7 +5907,11 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
bi->kind() == Binding::ARGUMENT);
MOZ_ASSERT(bi.argOrLocalIndex() < JS_BIT(20));
#endif
if (!emitIndexOp(JSOP_LAMBDA, index))
if (funbox->isAsync()) {
if (!emitAsyncWrapper(index, false))
return false;
}
else if (!emitIndexOp(JSOP_LAMBDA, index))
return false;
MOZ_ASSERT(pn->getOp() == JSOP_GETLOCAL || pn->getOp() == JSOP_GETARG);
JSOp setOp = pn->getOp() == JSOP_GETLOCAL ? JSOP_SETLOCAL : JSOP_SETARG;
@ -5904,6 +5924,30 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto)
return true;
}
bool
BytecodeEmitter::emitAsyncWrapper(unsigned index, bool needsHomeObject) {
JSAtom* atom = Atomize(cx, "AsyncFunction_wrap", 18);
if (!atom)
return false;
/* TODO Comment */
if (needsHomeObject && !emitIndex32(JSOP_LAMBDA, index))
return false;
if (!emitAtomOp(atom, JSOP_GETINTRINSIC))
return false;
if (!emit1(JSOP_UNDEFINED))
return false;
if (needsHomeObject) {
if (!emitDupAt(2))
return false;
} else {
if (!emitIndex32(JSOP_LAMBDA, index))
return false;
}
if (!emitCall(JSOP_CALL, 1))
return false;
return true;
}
bool
BytecodeEmitter::emitDo(ParseNode* pn)
{
@ -7070,7 +7114,12 @@ BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp,
propdef->pn_right->pn_funbox->needsHomeObject())
{
MOZ_ASSERT(propdef->pn_right->pn_funbox->function()->allowSuperProperty());
if (!emit2(JSOP_INITHOMEOBJECT, isIndex))
bool isAsync = propdef->pn_right->pn_funbox->isAsync();
if (isAsync && !emit1(JSOP_SWAP))
return false;
if (!emit2(JSOP_INITHOMEOBJECT, isIndex + isAsync))
return false;
if (isAsync && !emit1(JSOP_POP))
return false;
}
@ -7689,6 +7738,7 @@ BytecodeEmitter::emitTree(ParseNode* pn)
break;
case PNK_YIELD:
case PNK_AWAIT:
ok = emitYield(pn);
break;
@ -8022,13 +8072,6 @@ BytecodeEmitter::emitTree(ParseNode* pn)
return false;
break;
// PNK_AWAIT handling is not yet implemented (in this part),
// so currently we just return "true" as a placeholder.
case PNK_AWAIT:
if (!emit1(JSOP_TRUE))
return false;
break;
case PNK_POSHOLDER:
MOZ_ASSERT_UNREACHABLE("Should never try to emit PNK_POSHOLDER");

View File

@ -482,6 +482,8 @@ struct BytecodeEmitter
bool emitPropOp(ParseNode* pn, JSOp op);
bool emitPropIncDec(ParseNode* pn);
bool emitAsyncWrapper(unsigned index, bool isMethod);
bool emitComputedPropertyName(ParseNode* computedPropName);
// Emit bytecode to put operands for a JSOP_GETELEM/CALLELEM/SETELEM/DELELEM

View File

@ -2496,15 +2496,15 @@ static const VMFunction DefFunOperationInfo = FunctionInfo<DefFunOperationFn>(De
bool
BaselineCompiler::emit_JSOP_DEFFUN()
{
RootedFunction fun(cx, script->getFunction(GET_UINT32_INDEX(pc)));
frame.syncStack(0);
masm.loadPtr(frame.addressOfScopeChain(), R0.scratchReg());
frame.popRegsAndSync(1);
masm.unboxObject(R0, R0.scratchReg());
masm.loadPtr(frame.addressOfScopeChain(), R1.scratchReg());
prepareVMCall();
pushArg(ImmGCPtr(fun));
pushArg(R0.scratchReg());
pushArg(R1.scratchReg());
pushArg(ImmGCPtr(script));
return callVM(DefFunOperationInfo);

View File

@ -3737,8 +3737,9 @@ void
CodeGenerator::visitDefFun(LDefFun* lir)
{
Register scopeChain = ToRegister(lir->scopeChain());
Register fun = ToRegister(lir->fun());
pushArg(ImmGCPtr(lir->mir()->fun()));
pushArg(fun);
pushArg(scopeChain);
pushArg(ImmGCPtr(current->mir()->info().script()));

View File

@ -12585,13 +12585,11 @@ IonBuilder::jsop_defvar(uint32_t index)
bool
IonBuilder::jsop_deffun(uint32_t index)
{
JSFunction* fun = script()->getFunction(index);
if (fun->isNative() && IsAsmJSModuleNative(fun->native()))
return abort("asm.js module function");
MOZ_ASSERT(analysis().usesScopeChain());
MDefFun* deffun = MDefFun::New(alloc(), fun, current->scopeChain());
MDefinition *funDef = current->pop();
MDefFun* deffun = MDefFun::New(alloc(), funDef, current->scopeChain());
current->add(deffun);
return resumeAfter(deffun);

View File

@ -156,7 +156,11 @@ LIRGenerator::visitDefVar(MDefVar* ins)
void
LIRGenerator::visitDefFun(MDefFun* ins)
{
LDefFun* lir = new(alloc()) LDefFun(useRegisterAtStart(ins->scopeChain()));
MDefinition* fun = ins->fun();
MOZ_ASSERT(fun->type() == MIRType_Object);
LDefFun* lir = new(alloc()) LDefFun(useRegisterAtStart(fun),
useRegisterAtStart(ins->scopeChain()));
add(lir, ins);
assignSafepoint(lir, ins);
}

View File

@ -7319,29 +7319,26 @@ class MDefVar
};
class MDefFun
: public MUnaryInstruction,
public NoTypePolicy::Data
: public MBinaryInstruction,
public ObjectPolicy<0>::Data
{
CompilerFunction fun_;
private:
MDefFun(JSFunction* fun, MDefinition* scopeChain)
: MUnaryInstruction(scopeChain),
fun_(fun)
MDefFun(MDefinition* fun, MDefinition* scopeChain)
: MBinaryInstruction(fun, scopeChain)
{}
public:
INSTRUCTION_HEADER(DefFun)
static MDefFun* New(TempAllocator& alloc, JSFunction* fun, MDefinition* scopeChain) {
static MDefFun* New(TempAllocator& alloc, MDefinition* fun, MDefinition* scopeChain) {
return new(alloc) MDefFun(fun, scopeChain);
}
JSFunction* fun() const {
return fun_;
MDefinition* fun() const {
return getOperand(0);
}
MDefinition* scopeChain() const {
return getOperand(0);
return getOperand(1);
}
bool possiblyCalls() const override {
return true;

View File

@ -1228,19 +1228,23 @@ class LDefVar : public LCallInstructionHelper<0, 1, 0>
}
};
class LDefFun : public LCallInstructionHelper<0, 1, 0>
class LDefFun : public LCallInstructionHelper<0, 2, 0>
{
public:
LIR_HEADER(DefFun)
explicit LDefFun(const LAllocation& scopeChain)
LDefFun(const LAllocation& fun, const LAllocation& scopeChain)
{
setOperand(0, scopeChain);
setOperand(0, fun);
setOperand(1, scopeChain);
}
const LAllocation* scopeChain() {
const LAllocation* fun() {
return getOperand(0);
}
const LAllocation* scopeChain() {
return getOperand(1);
}
MDefFun* mir() const {
return mir_->toDefFun();
}

View File

@ -467,7 +467,7 @@ class JSFunction : public js::NativeObject
}
js::FunctionAsyncKind asyncKind() const {
return isInterpretedLazy() ? lazyScript()->asyncKind() : nonLazyScript()->asyncKind();
return isInterpretedLazy() ? lazyScript()->asyncKind() : nonLazyScript()->asyncKind();
}
bool isGenerator() const { return generatorKind() != js::NotGenerator; }

View File

@ -0,0 +1,18 @@
/* 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/. */
function assertThrowsSE(code) {
assertThrows(() => Reflect.parse(code), SyntaxError);
}
assertThrowsSE("'use strict'; async function eval() {}");
assertThrowsSE("'use strict'; async function arguments() {}");
assertThrowsSE("async function a(k = super.prop) { }");
assertThrowsSE("async function a() { super.prop(); }");
assertThrowsSE("async function a() { super(); }");
assertThrowsSE("async function a(k = await 3) {}");
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -0,0 +1,13 @@
/* 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/. */
async function test() { }
var anon = async function() { }
assertEq(test.name, "test");
assertEq(anon.name, "");
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -0,0 +1,56 @@
/* 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/. */
var Promise = ShellPromise;
class X {
constructor() {
this.value = 42;
}
async getValue() {
return this.value;
}
setValue(value) {
this.value = value;
}
async increment() {
var value = await this.getValue();
this.setValue(value + 1);
return this.getValue();
}
async getBaseClassName() {
return 'X';
}
static async getStaticValue() {
return 44;
}
}
class Y extends X {
constructor() { }
async getBaseClassName() {
return super.getBaseClassName();
}
}
var objLiteral = {
async get() {
return 45;
},
someStuff: 5
};
var x = new X();
var y = new Y();
Promise.all([
assertEventuallyEq(x.getValue(), 42),
assertEventuallyEq(x.increment(), 43),
assertEventuallyEq(X.getStaticValue(), 44),
assertEventuallyEq(objLiteral.get(), 45),
assertEventuallyEq(y.getBaseClassName(), 'X'),
]).then(() => {
if (typeof reportCompare === "function")
reportCompare(true, true);
});

View File

@ -0,0 +1,168 @@
/* 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/. */
var Promise = ShellPromise;
async function empty() {
}
async function simpleReturn() {
return 1;
}
async function simpleAwait() {
var result = await 2;
return result;
}
async function simpleAwaitAsync() {
var result = await simpleReturn();
return 2 + result;
}
async function returnOtherAsync() {
return 1 + await simpleAwaitAsync();
}
async function simpleThrower() {
throw new Error();
}
async function delegatedThrower() {
var val = await simpleThrower();
return val;
}
async function tryCatch() {
try {
await delegatedThrower();
return 'FAILED';
} catch (_) {
return 5;
}
}
async function tryCatchThrow() {
try {
await delegatedThrower();
return 'FAILED';
} catch (_) {
return delegatedThrower();
}
}
async function wellFinally() {
try {
await delegatedThrower();
} catch (_) {
return 'FAILED';
} finally {
return 6;
}
}
async function finallyMayFail() {
try {
await delegatedThrower();
} catch (_) {
return 5;
} finally {
return delegatedThrower();
}
}
async function embedded() {
async function inner() {
return 7;
}
return await inner();
}
// recursion, it works!
async function fib(n) {
return (n == 0 || n == 1) ? n : await fib(n - 1) + await fib(n - 2);
}
// mutual recursion
async function isOdd(n) {
async function isEven(n) {
return n === 0 || await isOdd(n - 1);
}
return n !== 0 && await isEven(n - 1);
}
// recursion, take three!
var hardcoreFib = async function fib2(n) {
return (n == 0 || n == 1) ? n : await fib2(n - 1) + await fib2(n - 2);
}
var asyncExpr = async function() {
return 10;
}
var namedAsyncExpr = async function simple() {
return 11;
}
async function executionOrder() {
var value = 0;
async function first() {
return (value = value === 0 ? 1 : value);
}
async function second() {
return (value = value === 0 ? 2 : value);
}
async function third() {
return (value = value === 0 ? 3 : value);
}
return await first() + await second() + await third() + 6;
}
async function miscellaneous() {
if (arguments.length === 3 &&
arguments.callee.name === "miscellaneous")
return 14;
}
function thrower() {
throw 15;
}
async function defaultArgs(arg = thrower()) {
}
// Async functions are not constructible
assertThrows(() => {
async function Person() {
}
new Person();
}, TypeError);
Promise.all([
assertEventuallyEq(empty(), undefined),
assertEventuallyEq(simpleReturn(), 1),
assertEventuallyEq(simpleAwait(), 2),
assertEventuallyEq(simpleAwaitAsync(), 3),
assertEventuallyEq(returnOtherAsync(), 4),
assertEventuallyThrows(simpleThrower(), Error),
assertEventuallyEq(tryCatch(), 5),
assertEventuallyThrows(tryCatchThrow(), Error),
assertEventuallyEq(wellFinally(), 6),
assertEventuallyThrows(finallyMayFail(), Error),
assertEventuallyEq(embedded(), 7),
assertEventuallyEq(fib(6), 8),
assertEventuallyEq(executionOrder(), 9),
assertEventuallyEq(asyncExpr(), 10),
assertEventuallyEq(namedAsyncExpr(), 11),
assertEventuallyEq(isOdd(12).then(v => v ? "oops" : 12), 12),
assertEventuallyEq(hardcoreFib(7), 13),
assertEventuallyEq(miscellaneous(1, 2, 3), 14),
assertEventuallyEq(defaultArgs().catch(e => e), 15)
]).then(() => {
if (typeof reportCompare === "function")
reportCompare(true, true);
});

View File

@ -0,0 +1,27 @@
/* 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/. */
/**
* These methods are inspired by chai-as-promised library for promise testing
* in JS applications. They check if promises eventually resolve to a given value
* or are rejected with a specified error type.
*/
if (typeof assertEventuallyEq === 'undefined') {
assertEventuallyEq = function(promise, expected) {
return promise.then(actual => assertEq(actual, expected));
};
}
if (typeof assertEventuallyThrows === 'undefined') {
assertEventuallyThrows = function(promise, expectedErrorType) {
return promise.catch(actualE => assertEq(actualE instanceof expectedErrorType, true));
};
}
if (typeof assertEventuallyDeepEq === 'undefined') {
assertEventuallyDeepEq = function(promise, expected) {
return promise.then(actual => assertDeepEq(actual, expected));
};
}

View File

@ -0,0 +1,8 @@
parseModule("async function f() { await 3; }");
parseModule("async function f() { await 3; }");
assertThrows(() => parseModule("var await = 5;"), SyntaxError);
assertThrows(() => parseModule("export var await;"), SyntaxError);
assertThrows(() => parseModule("async function f() { function g() { await 3; } }"), SyntaxError);
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -3507,17 +3507,18 @@ CASE(JSOP_DEFVAR)
}
END_CASE(JSOP_DEFVAR)
CASE(JSOP_DEFFUN)
{
CASE(JSOP_DEFFUN) {
/*
* A top-level function defined in Global or Eval code (see ECMA-262
* Ed. 3), or else a SpiderMonkey extension: a named function statement in
* a compound statement (not at the top statement level of global code, or
* at the top level of a function body).
*/
ReservedRooted<JSFunction*> fun(&rootFunction0, script->getFunction(GET_UINT32_INDEX(REGS.pc)));
MOZ_ASSERT(REGS.sp[-1].isObject());
ReservedRooted<JSFunction*> fun(&rootFunction0, &REGS.sp[-1].toObject().as<JSFunction>());
if (!DefFunOperation(cx, script, REGS.fp()->scopeChain(), fun))
goto error;
REGS.sp--;
}
END_CASE(JSOP_DEFFUN)
@ -4321,14 +4322,6 @@ js::DefFunOperation(JSContext* cx, HandleScript script, HandleObject scopeChain,
* requests in server-side JS.
*/
RootedFunction fun(cx, funArg);
if (fun->isNative() || fun->environment() != scopeChain) {
fun = CloneFunctionObjectIfNotSingleton(cx, fun, scopeChain, nullptr, TenuredObject);
if (!fun)
return false;
} else {
MOZ_ASSERT(script->treatAsRunOnce());
MOZ_ASSERT(!script->functionNonDelazifying());
}
/*
* We define the function as a property of the variable object and not the

View File

@ -1294,7 +1294,7 @@
* Operands: uint32_t funcIndex
* Stack: =>
*/ \
macro(JSOP_DEFFUN, 127,"deffun", NULL, 5, 0, 0, JOF_OBJECT) \
macro(JSOP_DEFFUN, 127,"deffun", NULL, 1, 1, 0, JOF_BYTE) \
/*
* Defines the new binding on the frame's current variables-object (the
* scope object on the scope chain designated to receive new variables) with
@ -1983,7 +1983,6 @@
* Stack: val => ToString(val)
*/ \
macro(JSOP_TOSTRING, 228, "tostring", NULL, 1, 1, 1, JOF_BYTE)
/*
* In certain circumstances it may be useful to "pad out" the opcode space to
* a power of two. Use this macro to do so.

View File

@ -237,7 +237,12 @@ CallObject::createForFunction(JSContext* cx, HandleObject enclosing, HandleFunct
* object holding function's name.
*/
if (callee->isNamedLambda()) {
scopeChain = DeclEnvObject::create(cx, scopeChain, callee);
if (callee->isAsync()) {
RootedFunction fun(cx, &callee->getExtendedSlot(1).toObject().as<JSFunction>());
scopeChain = DeclEnvObject::create(cx, scopeChain, fun);
} else {
scopeChain = DeclEnvObject::create(cx, scopeChain, callee);
}
if (!scopeChain)
return nullptr;
}