Bug 1185106 - Part 4: Implement async functions. (r=efaust, r=jandem)
☠☠ backed out by 2384726c541f ☠ ☠
authorMariusz Kierski <mkierski@mozilla.com>
Mon, 05 Oct 2015 13:24:03 -0700
changeset 266729 e0552bf93a5aa223caa866b070d2d9df0dfe21d3
parent 266728 50d74e6e89379ee381979901af5c8fffc73efe00
child 266730 2829205d2c81494061c7ba87d94b65e00940ff8d
push id29497
push usercbook@mozilla.com
push dateThu, 08 Oct 2015 13:27:17 +0000
treeherdermozilla-central@1f4cf75c8948 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersefaust, jandem
bugs1185106
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1185106 - Part 4: Implement async functions. (r=efaust, r=jandem)
js/src/Makefile.in
js/src/builtin/AsyncFunctions.js
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/jit/BaselineCompiler.cpp
js/src/jit/CodeGenerator.cpp
js/src/jit/IonBuilder.cpp
js/src/jit/Lowering.cpp
js/src/jit/MIR.h
js/src/jit/shared/LIR-shared.h
js/src/jsfun.h
js/src/tests/ecma_7/AsyncFunctions/1.1.1.js
js/src/tests/ecma_7/AsyncFunctions/1.1.2.js
js/src/tests/ecma_7/AsyncFunctions/methods.js
js/src/tests/ecma_7/AsyncFunctions/semantics.js
js/src/tests/ecma_7/AsyncFunctions/shell.js
js/src/tests/ecma_7/AsyncFunctions/syntax-modules.js
js/src/vm/Interpreter.cpp
js/src/vm/Opcodes.h
js/src/vm/ScopeObject.cpp
--- a/js/src/Makefile.in
+++ b/js/src/Makefile.in
@@ -279,16 +279,17 @@ endif
 
 # Prepare self-hosted JS code for embedding
 export:: selfhosting
 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 \
   $(srcdir)/builtin/Intl.js \
   $(srcdir)/builtin/IntlData.js \
   $(srcdir)/builtin/Iterator.js \
   $(srcdir)/builtin/Map.js \
   $(srcdir)/builtin/Module.js \
new file mode 100644
--- /dev/null
+++ b/js/src/builtin/AsyncFunctions.js
@@ -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);
+                        });
+}
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -235,17 +235,16 @@ BytecodeEmitter::checkStrictOrSloppy(JSO
     return true;
 }
 #endif
 
 bool
 BytecodeEmitter::emit1(JSOp op)
 {
     MOZ_ASSERT(checkStrictOrSloppy(op));
-
     ptrdiff_t offset;
     if (!emitCheck(1, &offset))
         return false;
 
     jsbytecode* code = this->code(offset);
     code[0] = jsbytecode(op);
     updateDepth(offset);
     return true;
@@ -1991,17 +1990,16 @@ BytecodeEmitter::checkSideEffects(ParseN
         return checkSideEffects(pn->pn_kid, answer);
 
       // Unary cases with obvious side effects.
       case PNK_PREINCREMENT:
       case PNK_POSTINCREMENT:
       case PNK_PREDECREMENT:
       case PNK_POSTDECREMENT:
       case PNK_THROW:
-      case PNK_AWAIT:
         MOZ_ASSERT(pn->isArity(PN_UNARY));
         *answer = true;
         return true;
 
       // These might invoke valueOf/toString, even with a subexpression without
       // side effects!  Consider |+{ valueOf: null, toString: null }|.
       case PNK_BITNOT:
       case PNK_POS:
@@ -2013,16 +2011,17 @@ BytecodeEmitter::checkSideEffects(ParseN
       // This invokes the (user-controllable) iterator protocol.
       case PNK_SPREAD:
         MOZ_ASSERT(pn->isArity(PN_UNARY));
         *answer = true;
         return true;
 
       case PNK_YIELD_STAR:
       case PNK_YIELD:
+      case PNK_AWAIT:
         MOZ_ASSERT(pn->isArity(PN_BINARY));
         *answer = true;
         return true;
 
       // Deletion generally has side effects, even if isolated cases have none.
       case PNK_DELETENAME:
       case PNK_DELETEPROP:
       case PNK_DELETEELEM:
@@ -5858,16 +5857,26 @@ BytecodeEmitter::emitFunction(ParseNode*
                     return false;
             }
         }
 
         if (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);
     }
 
     MOZ_ASSERT(!needsProto);
 
     /*
      * For a script we emit the code as we parse. Thus the bytecode for
      * top-level functions should go in the prologue to predefine their
@@ -5877,44 +5886,79 @@ BytecodeEmitter::emitFunction(ParseNode*
      * invocation of the emitter and calls to emitTree for function
      * definitions can be scheduled before generating the rest of code.
      */
     if (sc->isGlobalContext()) {
         MOZ_ASSERT(pn->pn_scopecoord.isFree());
         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;
         switchToMain();
     } else {
 #ifdef DEBUG
         BindingIter bi(script);
         while (bi->name() != fun->atom())
             bi++;
         MOZ_ASSERT(bi->kind() == Binding::VARIABLE || bi->kind() == Binding::CONSTANT ||
                    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;
         if (!emitVarOp(pn, setOp))
             return false;
         if (!emit1(JSOP_POP))
             return false;
     }
 
     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)
 {
     /* Emit an annotated nop so IonBuilder can recognize the 'do' loop. */
     unsigned noteIndex;
     if (!newSrcNote(SRC_WHILE, &noteIndex))
         return false;
     if (!emit1(JSOP_NOP))
         return false;
@@ -7070,17 +7114,22 @@ BytecodeEmitter::emitPropertyList(ParseN
 
         if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER)
             objp.set(nullptr);
 
         if (propdef->pn_right->isKind(PNK_FUNCTION) &&
             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;
         }
 
         if (isIndex) {
             objp.set(nullptr);
             switch (op) {
               case JSOP_INITPROP:        op = JSOP_INITELEM;        break;
               case JSOP_INITPROP_GETTER: op = JSOP_INITELEM_GETTER; break;
@@ -7673,16 +7722,17 @@ BytecodeEmitter::emitTree(ParseNode* pn)
         break;
 
       case PNK_GENERATOR:
         if (!emit1(JSOP_GENERATOR))
             return false;
         break;
 
       case PNK_YIELD:
+      case PNK_AWAIT:
         ok = emitYield(pn);
         break;
 
       case PNK_STATEMENTLIST:
         ok = emitStatementList(pn, top);
         break;
 
       case PNK_SEMI:
@@ -8006,23 +8056,16 @@ BytecodeEmitter::emitTree(ParseNode* pn)
         ok = emitClass(pn);
         break;
 
       case PNK_NEWTARGET:
         if (!emit1(JSOP_NEWTARGET))
             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");
 
       default:
         MOZ_ASSERT(0);
     }
 
     /* bce->emitLevel == 1 means we're last on the stack, so finish up. */
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -475,16 +475,18 @@ struct BytecodeEmitter
     bool emitYield(ParseNode* pn);
     bool emitYieldOp(JSOp op);
     bool emitYieldStar(ParseNode* iter, ParseNode* gen);
 
     bool emitPropLHS(ParseNode* pn);
     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
     // opcode onto the stack in the right order. In the case of SETELEM, the
     // value to be assigned must already be pushed.
     bool emitElemOperands(ParseNode* pn, JSOp op);
 
     bool emitElemOpBase(JSOp op);
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -2511,25 +2511,25 @@ BaselineCompiler::emit_JSOP_DEFLET()
 }
 
 typedef bool (*DefFunOperationFn)(JSContext*, HandleScript, HandleObject, HandleFunction);
 static const VMFunction DefFunOperationInfo = FunctionInfo<DefFunOperationFn>(DefFunOperation);
 
 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);
 }
 
 typedef bool (*InitPropGetterSetterFn)(JSContext*, jsbytecode*, HandleObject, HandlePropertyName,
                                        HandleObject);
 static const VMFunction InitPropGetterSetterInfo =
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -3742,18 +3742,19 @@ CodeGenerator::visitDefLexical(LDefLexic
 
 typedef bool (*DefFunOperationFn)(JSContext*, HandleScript, HandleObject, HandleFunction);
 static const VMFunction DefFunOperationInfo = FunctionInfo<DefFunOperationFn>(DefFunOperation);
 
 void
 CodeGenerator::visitDefFun(LDefFun* lir)
 {
     Register scopeChain = ToRegister(lir->scopeChain());
-
-    pushArg(ImmGCPtr(lir->mir()->fun()));
+    Register fun = ToRegister(lir->fun());
+
+    pushArg(fun);
     pushArg(scopeChain);
     pushArg(ImmGCPtr(current->mir()->info().script()));
 
     callVM(DefFunOperationInfo, lir);
 }
 
 typedef bool (*CheckOverRecursedFn)(JSContext*);
 static const VMFunction CheckOverRecursedInfo =
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -12684,23 +12684,19 @@ IonBuilder::jsop_deflexical(uint32_t ind
     current->add(deflex);
 
     return resumeAfter(deflex);
 }
 
 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());
+    MDefFun* deffun = MDefFun::New(alloc(), current->pop(), current->scopeChain());
     current->add(deffun);
 
     return resumeAfter(deffun);
 }
 
 bool
 IonBuilder::jsop_checklexical()
 {
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -159,17 +159,21 @@ LIRGenerator::visitDefLexical(MDefLexica
     LDefLexical* lir = new(alloc()) LDefLexical();
     add(lir, ins);
     assignSafepoint(lir, 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);
 }
 
 void
 LIRGenerator::visitNewArray(MNewArray* ins)
 {
     LNewArray* lir = new(alloc()) LNewArray(temp());
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -7356,39 +7356,36 @@ class MDefLexical
         return name_;
     }
     unsigned attrs() const {
         return attrs_;
     }
 };
 
 class MDefFun
-  : public MUnaryInstruction,
-    public NoTypePolicy::Data
-{
-    CompilerFunction fun_;
-
+  : public MBinaryInstruction,
+    public ObjectPolicy<0>::Data
+{
   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;
     }
 };
 
 class MRegExp : public MNullaryInstruction
 {
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -1233,28 +1233,32 @@ class LDefLexical : public LCallInstruct
   public:
     LIR_HEADER(DefLexical)
 
     MDefLexical* mir() const {
         return mir_->toDefLexical();
     }
 };
 
-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* fun() {
+        return getOperand(0);
+    }
     const LAllocation* scopeChain() {
-        return getOperand(0);
+        return getOperand(1);
     }
     MDefFun* mir() const {
         return mir_->toDefFun();
     }
 };
 
 class LTypeOfV : public LInstructionHelper<1, BOX_PIECES, 1>
 {
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -462,17 +462,17 @@ class JSFunction : public js::NativeObje
             return nonLazyScript()->generatorKind();
         if (js::LazyScript* lazy = lazyScriptOrNull())
             return lazy->generatorKind();
         MOZ_ASSERT(isSelfHostedBuiltin());
         return js::NotGenerator;
     }
 
     js::FunctionAsyncKind asyncKind() const {
-      return isInterpretedLazy() ? lazyScript()->asyncKind() : nonLazyScript()->asyncKind();
+        return isInterpretedLazy() ? lazyScript()->asyncKind() : nonLazyScript()->asyncKind();
     }
 
     bool isGenerator() const { return generatorKind() != js::NotGenerator; }
 
     bool isLegacyGenerator() const { return generatorKind() == js::LegacyGenerator; }
 
     bool isStarGenerator() const { return generatorKind() == js::StarGenerator; }
 
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/AsyncFunctions/1.1.1.js
@@ -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);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/AsyncFunctions/1.1.2.js
@@ -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);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/AsyncFunctions/methods.js
@@ -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);
+});
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/AsyncFunctions/semantics.js
@@ -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);
+});
--- a/js/src/tests/ecma_7/AsyncFunctions/shell.js
+++ b/js/src/tests/ecma_7/AsyncFunctions/shell.js
@@ -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));
+  };
+}
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_7/AsyncFunctions/syntax-modules.js
@@ -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);
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -3524,19 +3524,20 @@ END_CASE(JSOP_DEFLET)
 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)));
+    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)
 
 CASE(JSOP_LAMBDA)
 {
     /* Load the specified function object literal. */
     ReservedRooted<JSFunction*> fun(&rootFunction0, script->getFunction(GET_UINT32_INDEX(REGS.pc)));
     JSObject* obj = Lambda(cx, fun, REGS.fp()->scopeChain());
@@ -4330,24 +4331,16 @@ js::DefFunOperation(JSContext* cx, Handl
      * current scope via parent. We do this to enable sharing of compiled
      * functions among multiple equivalent scopes, amortizing the cost of
      * compilation over a number of executions.  Examples include XUL scripts
      * and event handlers shared among Firefox or other Mozilla app chrome
      * windows, and user-defined JS functions precompiled and then shared among
      * 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
      * current scope chain even for the case of function expression statements
      * and functions defined by eval inside let or with blocks.
      */
     RootedObject parent(cx, scopeChain);
     while (!parent->isQualifiedVarObj())
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -1280,20 +1280,20 @@ 1234567890123456789012345678901234567890
     \
     /*
      * Defines the given function on the current scope.
      *
      * This is used for global scripts and also in some cases for function
      * scripts where use of dynamic scoping inhibits optimization.
      *   Category: Variables and Scopes
      *   Type: Variables
-     *   Operands: uint32_t funcIndex
-     *   Stack: =>
+     *   Operands:
+     *   Stack: fun =>
      */ \
-    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 constant binding on global lexical scope.
      *
      * Throws if a binding with the same name already exists on the scope, or
      * if a var binding with the same name exists on the global.
      *   Category: Variables and Scopes
      *   Type: Variables
      *   Operands: uint32_t nameIndex
      *   Stack: =>
@@ -1990,17 +1990,16 @@ 1234567890123456789012345678901234567890
     macro(JSOP_LOOPENTRY,     227, "loopentry",    NULL,  2,  0,  0,  JOF_UINT8) \
     /*
      * Converts the value on the top of the stack to a String
      *   Category: Other
      *   Operands:
      *   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.
  */
 #define FOR_EACH_TRAILING_UNUSED_OPCODE(macro) \
     macro(229) \
     macro(230) \
     macro(231) \
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -232,17 +232,22 @@ CallObject::createForFunction(JSContext*
     RootedObject scopeChain(cx, enclosing);
     MOZ_ASSERT(scopeChain);
 
     /*
      * For a named function expression Call's parent points to an environment
      * 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;
     }
 
     RootedScript script(cx, callee->nonLazyScript());
     return create(cx, script, scopeChain, callee);
 }