Bug 1289318 - Part 5: Port most Promise functions directly involved in Promise resolution from JS to C++. r?efaust draft
authorTill Schneidereit <till@tillschneidereit.net>
Fri, 05 Aug 2016 18:40:46 +0200
changeset 400698 2bf87b24ee110575403ed5496f9eeb6d4262b4ca
parent 400697 6429d48ca8d8547f7a2ecb737723c5d304a8e779
child 400699 2d88eea27b4b4e6575a5be0e5f53913de55bfa15
push id26245
push userbmo:till@tillschneidereit.net
push dateMon, 15 Aug 2016 14:02:13 +0000
reviewersefaust
bugs1289318
milestone51.0a1
Bug 1289318 - Part 5: Port most Promise functions directly involved in Promise resolution from JS to C++. r?efaust Importantly, CreateResolvingFunctions, ResolvePromise, and TriggerPromiseReactions have been ported. This reduces memory usage because before, the `resolve` and `reject` functions stored on a pending Promise kept track of each other and the Promise they belong to using a closure. Now, that state is stored in the functions' extended slots - which they have anyway. It should also improve performance, as fewer switches between JS and C++ code occur during processing of Promise reaction job lists. MozReview-Commit-ID: 9Wp0sDFayTy
js/src/builtin/Promise.cpp
js/src/builtin/Promise.js
js/src/builtin/Utilities.js
js/src/tests/ecma_6/Promise/promise-basics.js
js/src/vm/CommonPropertyNames.h
js/src/vm/SelfHosting.cpp
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -44,38 +44,358 @@ static const JSPropertySpec promise_stat
 static double
 MillisecondsSinceStartup()
 {
     auto now = mozilla::TimeStamp::Now();
     bool ignored;
     return (now - mozilla::TimeStamp::ProcessCreation(ignored)).ToMilliseconds();
 }
 
+enum ResolutionFunctionSlots {
+    ResolutionFunctionSlot_Promise = 0,
+    ResolutionFunctionSlot_OtherFunction,
+};
+
+// ES2016, 25.4.1.8.
+static MOZ_MUST_USE bool
+TriggerPromiseReactions(JSContext* cx, HandleValue reactionsVal, JS::PromiseState state,
+                        HandleValue valueOrReason)
+{
+    RootedObject reactions(cx, &reactionsVal.toObject());
+
+    AutoIdVector keys(cx);
+    if (!GetPropertyKeys(cx, reactions, JSITER_OWNONLY, &keys))
+        return false;
+    MOZ_ASSERT(keys.length() > 0, "Reactions list should be created lazily");
+
+    RootedPropertyName handlerName(cx);
+    handlerName = state == JS::PromiseState::Fulfilled
+                  ? cx->names().fulfillHandler
+                  : cx->names().rejectHandler;
+
+    // Each reaction is an internally-created object with the structure:
+    // {
+    //   promise: [the promise this reaction resolves],
+    //   resolve: [the `resolve` callback content code provided],
+    //   reject:  [the `reject` callback content code provided],
+    //   fulfillHandler: [the internal handler that fulfills the promise]
+    //   rejectHandler: [the internal handler that rejects the promise]
+    //   incumbentGlobal: [an object from the global that was incumbent when
+    //                     the reaction was created]
+    // }
+    RootedValue val(cx);
+    RootedObject reaction(cx);
+    RootedValue handler(cx);
+    RootedObject promise(cx);
+    RootedObject resolve(cx);
+    RootedObject reject(cx);
+    RootedObject objectFromIncumbentGlobal(cx);
+
+    for (size_t i = 0; i < keys.length(); i++) {
+        if (!GetProperty(cx, reactions, reactions, keys[i], &val))
+            return false;
+        reaction = &val.toObject();
+
+        if (!GetProperty(cx, reaction, reaction, cx->names().promise, &val))
+            return false;
+
+        // The jsapi function AddPromiseReactions can add reaction records
+        // without a `promise` object. See comment for EnqueuePromiseReactions
+        // in Promise.js.
+        promise = val.toObjectOrNull();
+
+        if (!GetProperty(cx, reaction, reaction, handlerName, &handler))
+            return false;
+        MOZ_ASSERT((handler.isNumber() &&
+                    (handler.toNumber() == PROMISE_HANDLER_IDENTITY ||
+                     handler.toNumber() == PROMISE_HANDLER_THROWER)) ||
+                   handler.toObject().isCallable());
+
+        if (!GetProperty(cx, reaction, reaction, cx->names().resolve, &val))
+            return false;
+        resolve = &val.toObject();
+        MOZ_ASSERT(IsCallable(resolve));
+
+        if (!GetProperty(cx, reaction, reaction, cx->names().reject, &val))
+            return false;
+        reject = &val.toObject();
+        MOZ_ASSERT(IsCallable(reject));
+
+        if (!GetProperty(cx, reaction, reaction, cx->names().incumbentGlobal, &val))
+            return false;
+        objectFromIncumbentGlobal = val.toObjectOrNull();
+
+        if (!EnqueuePromiseReactionJob(cx, handler, valueOrReason, resolve, reject, promise,
+                                       objectFromIncumbentGlobal))
+        {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+// ES2016, Commoned-out implementation of 25.4.1.4. and 25.4.1.7.
+static MOZ_MUST_USE bool
+ResolvePromise(JSContext* cx, Handle<PromiseObject*> promise, HandleValue valueOrReason,
+               JS::PromiseState state)
+{
+    // Step 1.
+    MOZ_ASSERT(promise->state() == JS::PromiseState::Pending);
+    MOZ_ASSERT(state == JS::PromiseState::Fulfilled || state == JS::PromiseState::Rejected);
+
+    // Step 2.
+    // We only have one list of reactions for both resolution types. So
+    // instead of getting the right list of reactions, we determine the
+    // resolution type to retrieve the right information from the
+    // reaction records.
+    RootedValue reactionsVal(cx, promise->getFixedSlot(PROMISE_REACTIONS_SLOT));
+
+    // Step 3.
+    promise->setFixedSlot(PROMISE_RESULT_SLOT, valueOrReason);
+
+    // Steps 4-5.
+    promise->setFixedSlot(PROMISE_REACTIONS_SLOT, UndefinedValue());
+
+    // Step 6.
+    promise->setFixedSlot(PROMISE_STATE_SLOT, Int32Value(int32_t(state)));
+
+    // Also null out the resolve/reject functions so they can be GC'd.
+    promise->setFixedSlot(PROMISE_RESOLVE_FUNCTION_SLOT, UndefinedValue());
+    promise->setFixedSlot(PROMISE_REJECT_FUNCTION_SLOT, UndefinedValue());
+
+    // Now that everything else is done, do the things the debugger needs.
+    // Step 7 of RejectPromise implemented in onSettled.
+    promise->onSettled(cx);
+
+    // Step 7 of FulfillPromise.
+    // Step 8 of RejectPromise.
+    if (reactionsVal.isObject())
+        return TriggerPromiseReactions(cx, reactionsVal, state, valueOrReason);
+
+    return true;
+}
+
+// ES2016, 25.4.1.4.
+static MOZ_MUST_USE bool
+FulfillMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue value_) {
+    Rooted<PromiseObject*> promise(cx);
+    RootedValue value(cx, value_);
+
+    mozilla::Maybe<AutoCompartment> ac;
+    if (!IsWrapper(promiseObj)) {
+        promise = &promiseObj->as<PromiseObject>();
+    } else {
+        promise = &UncheckedUnwrap(promiseObj)->as<PromiseObject>();
+        ac.emplace(cx, promise);
+        if (!promise->compartment()->wrap(cx, &value))
+            return false;
+    }
+
+    MOZ_ASSERT(promise->state() == JS::PromiseState::Pending);
+
+    return ResolvePromise(cx, promise, value, JS::PromiseState::Fulfilled);
+}
+
+// ES2016, 25.4.1.7.
+static MOZ_MUST_USE bool
+RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue reason_) {
+    Rooted<PromiseObject*> promise(cx);
+    RootedValue reason(cx, reason_);
+
+    mozilla::Maybe<AutoCompartment> ac;
+    if (!IsWrapper(promiseObj)) {
+        promise = &promiseObj->as<PromiseObject>();
+    } else {
+        promise = &UncheckedUnwrap(promiseObj)->as<PromiseObject>();
+        ac.emplace(cx, promise);
+
+        // The rejection reason might've been created in a compartment with higher
+        // privileges than the Promise's. In that case, object-type rejection
+        // values might be wrapped into a wrapper that throws whenever the
+        // Promise's reaction handler wants to do anything useful with it. To
+        // avoid that situation, we synthesize a generic error that doesn't
+        // expose any privileged information but can safely be used in the
+        // rejection handler.
+        if (!promise->compartment()->wrap(cx, &reason))
+            return false;
+        if (reason.isObject() && !CheckedUnwrap(&reason.toObject())) {
+            // Async stacks are only properly adopted if there's at least one
+            // interpreter frame active right now. If a thenable job with a
+            // throwing `then` function got us here, that'll not be the case,
+            // so we add one by throwing the error from self-hosted code.
+            FixedInvokeArgs<1> getErrorArgs(cx);
+            getErrorArgs[0].set(Int32Value(JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON));
+            if (!CallSelfHostedFunction(cx, "GetInternalError", reason, getErrorArgs, &reason))
+                return false;
+        }
+    }
+
+    MOZ_ASSERT(promise->state() == JS::PromiseState::Pending);
+
+    return ResolvePromise(cx, promise, reason, JS::PromiseState::Rejected);
+}
+
+static void
+ClearResolutionFunctionSlots(JSFunction* resolutionFun)
+{
+    JSFunction* otherFun = &resolutionFun->getExtendedSlot(ResolutionFunctionSlot_OtherFunction)
+                           .toObject().as<JSFunction>();
+    resolutionFun->setExtendedSlot(ResolutionFunctionSlot_Promise, UndefinedValue());
+    otherFun->setExtendedSlot(ResolutionFunctionSlot_Promise, UndefinedValue());
+
+    // Also reset the reference to the resolve function so it can be collected.
+    resolutionFun->setExtendedSlot(ResolutionFunctionSlot_OtherFunction, UndefinedValue());
+    otherFun->setExtendedSlot(ResolutionFunctionSlot_OtherFunction, UndefinedValue());
+}
+// ES2016, 25.4.1.3.1.
 static bool
+RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    RootedFunction reject(cx, &args.callee().as<JSFunction>());
+    RootedValue reasonVal(cx, args.get(0));
+
+    // Steps 1-2.
+    RootedValue promiseVal(cx, reject->getExtendedSlot(ResolutionFunctionSlot_Promise));
+
+    // Steps 3-4.
+    // We use the existence of the Promise a a signal for whether it was
+    // already resolved. Upon resolution, it's reset to `undefined`.
+    if (promiseVal.isUndefined()) {
+        args.rval().setUndefined();
+        return true;
+    }
+
+    RootedObject promise(cx, &promiseVal.toObject());
+
+    // Step 5.
+    ClearResolutionFunctionSlots(reject);
+
+    // Step 6.
+    bool result = RejectMaybeWrappedPromise(cx, promise, reasonVal);
+    if (result)
+        args.rval().setUndefined();
+    return result;
+}
+
+// ES2016, 25.4.1.3.2.
+static bool
+ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    RootedFunction resolve(cx, &args.callee().as<JSFunction>());
+    RootedValue resolutionVal(cx, args.get(0));
+
+    // Steps 1-2.
+    RootedValue promiseVal(cx, resolve->getExtendedSlot(ResolutionFunctionSlot_Promise));
+
+    // Steps 3-4.
+    // We use the existence of the Promise a a signal for whether it was
+    // already resolved. Upon resolution, it's reset to `undefined`.
+    if (promiseVal.isUndefined()) {
+        args.rval().setUndefined();
+        return true;
+    }
+
+    RootedObject promise(cx, &promiseVal.toObject());
+
+    // Step 5.
+    ClearResolutionFunctionSlots(resolve);
+
+    // Step 6.
+    if (resolutionVal == promiseVal) {
+        // Step 6.a.
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
+                             JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF);
+        RootedValue selfResolutionError(cx);
+        bool status = GetAndClearException(cx, &selfResolutionError);
+        MOZ_ASSERT(status);
+
+        // Step 6.b.
+        status = RejectMaybeWrappedPromise(cx, promise, selfResolutionError);
+        if (status)
+            args.rval().setUndefined();
+        return status;
+    }
+
+    // Step 7.
+    if (!resolutionVal.isObject()) {
+        bool status = FulfillMaybeWrappedPromise(cx, promise, resolutionVal);
+        if (status)
+            args.rval().setUndefined();
+        return status;
+    }
+
+    RootedObject resolution(cx, &resolutionVal.toObject());
+
+    // Step 8.
+    RootedValue thenVal(cx);
+    bool status = GetProperty(cx, resolution, resolution, cx->names().then, &thenVal);
+
+    // Step 9.
+    if (!status) {
+        RootedValue error(cx);
+        if (!GetAndClearException(cx, &error))
+            return false;
+
+        return RejectMaybeWrappedPromise(cx, promise, error);
+    }
+
+    // Step 10 (implicit).
+
+    // Step 11.
+    if (!IsCallable(thenVal)) {
+        bool status = FulfillMaybeWrappedPromise(cx, promise, resolutionVal);
+        if (status)
+            args.rval().setUndefined();
+        return status;
+    }
+
+    // Step 12.
+    if (!EnqueuePromiseResolveThenableJob(cx, promiseVal, resolutionVal, thenVal))
+        return false;
+
+    // Step 13.
+    args.rval().setUndefined();
+    return true;
+}
+
+// ES2016, 25.4.1.3.
+static MOZ_MUST_USE bool
 CreateResolvingFunctions(JSContext* cx, HandleValue promise,
                          MutableHandleValue resolveVal,
                          MutableHandleValue rejectVal)
 {
-    FixedInvokeArgs<1> args(cx);
-    args[0].set(promise);
+    RootedAtom funName(cx, cx->names().empty);
+    RootedFunction resolve(cx, NewNativeFunction(cx, ResolvePromiseFunction, 1, funName,
+                                                 gc::AllocKind::FUNCTION_EXTENDED));
+    if (!resolve)
+        return false;
 
-    RootedValue rval(cx);
-
-    if (!CallSelfHostedFunction(cx, cx->names().CreateResolvingFunctions, UndefinedHandleValue,
-                                args, &rval))
-    {
+    RootedFunction reject(cx, NewNativeFunction(cx, RejectPromiseFunction, 1, funName,
+                                                 gc::AllocKind::FUNCTION_EXTENDED));
+    if (!reject)
         return false;
-    }
+
+    // The resolving functions are trusted, so self-hosted code should be able
+    // to call them using callFunction instead of callContentFunction.
+    resolve->setFlags(resolve->flags() | JSFunction::SELF_HOSTED);
+    reject->setFlags(reject->flags() | JSFunction::SELF_HOSTED);
 
-    RootedArrayObject resolvingFunctions(cx, &args.rval().toObject().as<ArrayObject>());
-    resolveVal.set(resolvingFunctions->getDenseElement(0));
-    rejectVal.set(resolvingFunctions->getDenseElement(1));
+    resolve->setExtendedSlot(ResolutionFunctionSlot_Promise, promise);
+    resolve->setExtendedSlot(ResolutionFunctionSlot_OtherFunction, ObjectValue(*reject));
 
-    MOZ_ASSERT(IsCallable(resolveVal));
-    MOZ_ASSERT(IsCallable(rejectVal));
+    reject->setExtendedSlot(ResolutionFunctionSlot_Promise, promise);
+    reject->setExtendedSlot(ResolutionFunctionSlot_OtherFunction, ObjectValue(*resolve));
+
+    resolveVal.setObject(*resolve);
+    rejectVal.setObject(*reject);
 
     return true;
 }
 
 // ES2016, February 12 draft, 25.4.3.1. steps 3-11.
 PromiseObject*
 PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto /* = nullptr */)
 {
@@ -232,46 +552,49 @@ PromiseObject::getID()
  * fulfill and reject cases. As an optimization, we have only one of those,
  * containing the required data for both cases. So we just walk that list
  * and extract the dependent promises from all reaction records.
  */
 bool
 PromiseObject::dependentPromises(JSContext* cx, MutableHandle<GCVector<Value>> values)
 {
     RootedValue reactionsVal(cx, getReservedSlot(PROMISE_REACTIONS_SLOT));
-    RootedObject reactions(cx, reactionsVal.toObjectOrNull());
-    if (!reactions)
+    if (reactionsVal.isNullOrUndefined())
         return true;
+    RootedObject reactions(cx, &reactionsVal.toObject());
 
     AutoIdVector keys(cx);
     if (!GetPropertyKeys(cx, reactions, JSITER_OWNONLY, &keys))
         return false;
 
     if (keys.length() == 0)
         return true;
 
     if (!values.growBy(keys.length()))
         return false;
 
     // Each reaction is an internally-created object with the structure:
     // {
     //   promise: [the promise this reaction resolves],
     //   resolve: [the `resolve` callback content code provided],
     //   reject:  [the `reject` callback content code provided],
-    //   handler: [the internal handler that fulfills/rejects the promise]
+    //   fulfillHandler: [the internal handler that fulfills the promise]
+    //   rejectHandler: [the internal handler that rejects the promise]
+    //   incumbentGlobal: [an object from the global that was incumbent when
+    //                     the reaction was created]
     // }
     //
     // In the following loop we collect the `capabilities.promise` values for
     // each reaction.
     for (size_t i = 0; i < keys.length(); i++) {
         MutableHandleValue val = values[i];
         if (!GetProperty(cx, reactions, reactions, keys[i], val))
             return false;
         RootedObject reaction(cx, &val.toObject());
-        if (!GetProperty(cx, reaction, reaction, cx->runtime()->commonNames->promise, val))
+        if (!GetProperty(cx, reaction, reaction, cx->names().promise, val))
             return false;
     }
 
     return true;
 }
 
 namespace js {
 
@@ -732,18 +1055,16 @@ EnqueuePromiseResolveThenableJob(JSConte
     // created in that compartment.
     // That guarantees that the embedding ends up with the right entry global.
     // This is relevant for some html APIs like fetch that derive information
     // from said global.
     RootedObject then(cx, CheckedUnwrap(&thenVal.toObject()));
     AutoCompartment ac(cx, then);
 
     RootedAtom funName(cx, cx->names().empty);
-    if (!funName)
-        return false;
     RootedFunction job(cx, NewNativeFunction(cx, PromiseResolveThenableJob, 0, funName,
                                              gc::AllocKind::FUNCTION_EXTENDED));
     if (!job)
         return false;
 
     // Store the `then` function on the callback.
     job->setExtendedSlot(ThenableJobSlot_Handler, ObjectValue(*then));
 
--- a/js/src/builtin/Promise.js
+++ b/js/src/builtin/Promise.js
@@ -12,178 +12,24 @@ function PromiseReactionRecord(promise, 
     this.reject = reject;
     this.fulfillHandler = fulfillHandler;
     this.rejectHandler = rejectHandler;
     this.incumbentGlobal = incumbentGlobal;
 }
 
 MakeConstructible(PromiseReactionRecord, PromiseReactionRecordProto);
 
-// ES6, 25.4.1.3.
-function CreateResolvingFunctions(promise) {
-    // The callbacks created here can deal with Promises wrapped in cross-
-    // compartment wrappers. That's required because in some circumstances,
-    // they're created in a higher-privileged compartment from the Promise,
-    // so they can be invoked seamlessly by code in that compartment.
-    //
-    // See the comment in PromiseConstructor (in builtin/Promise.cpp) for more
-    // details.
-    let unwrap = false;
-    if (!IsPromise(promise)) {
-        assert(IsWrappedPromise(promise),
-               "CreateResolvingFunctions expects arg0 to be a - maybe wrapped - promise");
-        unwrap = true;
-    }
-
-    // Step 1.
-    let alreadyResolved = false;
-
-    // Steps 2-4.
-    // ES6, 25.4.1.3.2. Inlined here so we can use an upvar instead of a slot to
-    // store promise and alreadyResolved, and share the latter with reject below.
-    function resolve(resolution) {
-        // Steps 1-3 (implicit).
-
-        // Step 4.
-        if (alreadyResolved)
-            return undefined;
-
-        // Step 5.
-        alreadyResolved = true;
-
-        // Step 6.
-        // We know |promise| is an object, so using strict equality instead of
-        // SameValue is fine.
-        if (resolution === promise) {
-            // Step 6.a.
-            let selfResolutionError = GetTypeError(JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF);
-
-            // Step 6.b.
-            if (unwrap) {
-                return RejectUnwrappedPromise(promise, selfResolutionError);
-            }
-            return RejectPromise(promise, selfResolutionError);
-        }
-
-        // Step 7.
-        if (!IsObject(resolution)) {
-            if (unwrap) {
-                return callFunction(CallPromiseMethodIfWrapped, promise, resolution,
-                                    "FulfillUnwrappedPromise");
-            }
-            return FulfillPromise(promise, resolution);
-        }
-
-        // Steps 8-9.
-        let then;
-        try {
-            then = resolution.then;
-        } catch (e) {
-            if (unwrap) {
-                return RejectUnwrappedPromise(promise, e);
-            }
-            return RejectPromise(promise, e);
-        }
-
-        // Step 10 (implicit).
-
-        // Step 11.
-        if (!IsCallable(then)) {
-            if (unwrap) {
-                return callFunction(CallPromiseMethodIfWrapped, promise, resolution,
-                                    "FulfillUnwrappedPromise");
-            }
-            return FulfillPromise(promise, resolution);
-        }
-
-        // Step 12.
-        _EnqueuePromiseResolveThenableJob(promise, resolution, then);
-
-        // Step 13.
-        return undefined;
-    }
-
-    // Steps 5-7.
-    // ES6, 25.4.1.3.2.
-    function reject(reason) {
-        // Steps 1-3 (implicit).
-
-        // Step 4.
-        if (alreadyResolved)
-            return undefined;
-
-        // Step 5.
-        alreadyResolved = true;
-
-        // Step 6.
-        if (unwrap) {
-            return RejectUnwrappedPromise(promise, reason);
-        }
-        return RejectPromise(promise, reason);
-    }
-
-    // Return an array instead of an object with resolve/reject properties
-    // to make value extraction from C++ easier.
-    return [resolve, reject];
-}
-
-// ES6, 25.4.1.4.
-function FulfillPromise(promise, value) {
-    return ResolvePromise(promise, value, PROMISE_STATE_FULFILLED);
-}
-function FulfillUnwrappedPromise(value) {
-    return ResolvePromise(this, value, PROMISE_STATE_FULFILLED);
-}
-
-// Commoned-out implementation of 25.4.1.4. and 25.4.1.7.
-// ES2016 February 12 draft.
-function ResolvePromise(promise, valueOrReason, state) {
-    // Step 1.
-    assert(GetPromiseState(promise) === PROMISE_STATE_PENDING,
-           "Can't resolve non-pending promise");
-    assert(state >= PROMISE_STATE_FULFILLED && state <= PROMISE_STATE_REJECTED,
-           `Invalid Promise resolution state <${state}>`);
-
-    // Step 2.
-    // We only have one list of reactions for both resolution types. So
-    // instead of getting the right list of reactions, we determine the
-    // resolution type to retrieve the right information from the
-    // reaction records.
-    var reactions = UnsafeGetReservedSlot(promise, PROMISE_REACTIONS_SLOT);
-    let jobType = state === PROMISE_STATE_FULFILLED
-                  ? PROMISE_JOB_TYPE_FULFILL
-                  : PROMISE_JOB_TYPE_REJECT;
-
-    // Step 3.
-    UnsafeSetReservedSlot(promise, PROMISE_RESULT_SLOT, valueOrReason);
-
-    // Steps 4-5.
-    UnsafeSetReservedSlot(promise, PROMISE_REACTIONS_SLOT, null);
-
-    // Step 6.
-    UnsafeSetReservedSlot(promise, PROMISE_STATE_SLOT, state);
-
-    // Also null out the resolve/reject functions so they can be GC'd.
-    UnsafeSetReservedSlot(promise, PROMISE_RESOLVE_FUNCTION_SLOT, null);
-    UnsafeSetReservedSlot(promise, PROMISE_REJECT_FUNCTION_SLOT, null);
-
-    // Now that everything else is done, do the things the debugger needs.
-    // Step 7 of RejectPromise implemented in the debugger intrinsic.
-    _dbg_onPromiseSettled(promise);
-
-    // Step 7 of FulfillPromise.
-    // Step 8 of RejectPromise.
-    if (reactions)
-        TriggerPromiseReactions(reactions, jobType, valueOrReason);
-}
-
 // Used to verify that an object is a PromiseCapability record.
 var PromiseCapabilityRecordProto = {__proto__: null};
 
-// ES6, 25.4.1.5.
+// ES2016, 25.4.1.3, implemented in Promise.cpp.
+
+// ES2016, 25.4.1.4, implemented in Promise.cpp.
+
+// ES2016, 25.4.1.5.
 // Creates PromiseCapability records, see 25.4.1.1.
 function NewPromiseCapability(C) {
     // Steps 1-2.
     if (!IsConstructor(C))
         ThrowTypeError(JSMSG_NOT_CONSTRUCTOR, 0);
 
     // Step 3. Replaced by individual fields, combined in step 11.
     let resolve;
@@ -217,32 +63,23 @@ function NewPromiseCapability(C) {
     return {
         __proto__: PromiseCapabilityRecordProto,
         promise,
         resolve,
         reject
     };
 }
 
-// ES6, 25.4.1.6. is implemented as an intrinsic in SelfHosting.cpp.
+// ES2016, 25.4.1.6, implemented in SelfHosting.cpp.
 
-// ES2016, February 12 draft, 25.4.1.7.
-function RejectPromise(promise, reason) {
-    return ResolvePromise(promise, reason, PROMISE_STATE_REJECTED);
-}
+// ES2016, 25.4.1.7, implemented in Promise.cpp.
 
-// ES6, 25.4.1.8.
-function TriggerPromiseReactions(reactions, jobType, argument) {
-    // Step 1.
-    for (var i = 0, len = reactions.length; i < len; i++)
-        EnqueuePromiseReactionJob(reactions[i], jobType, argument);
-    // Step 2 (implicit).
-}
+// ES2016, 25.4.1.8, implemented in Promise.cpp.
 
-// ES2016, February 12 draft 25.4.1.9, implemented in SelfHosting.cpp.
+// ES2016, 25.4.1.9, implemented in SelfHosting.cpp.
 
 // ES6, 25.4.2.1.
 function EnqueuePromiseReactionJob(reaction, jobType, argument) {
     // Reaction records contain handlers for both fulfillment and rejection.
     // The `jobType` parameter allows us to retrieves the right one.
     assert(jobType === PROMISE_JOB_TYPE_FULFILL || jobType === PROMISE_JOB_TYPE_REJECT,
            "Invalid job type");
     _EnqueuePromiseReactionJob(reaction[jobType],
@@ -252,17 +89,17 @@ function EnqueuePromiseReactionJob(react
                                reaction.promise,
                                reaction.incumbentGlobal || null);
 }
 
 // ES6, 25.4.2.2. (Implemented in C++).
 
 // ES6, 25.4.3.1. (Implemented in C++).
 
-// ES7 2016-01-21 draft, 25.4.4.1.
+// ES2016, 25.4.4.1.
 function Promise_static_all(iterable) {
     // Step 1.
     let C = this;
 
     // Step 2.
     if (!IsObject(C))
         ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.all call");
 
@@ -472,17 +309,17 @@ function CreatePromiseAllResolveElementF
             // Step 10.b.
             return callContentFunction(promiseCapability.resolve, undefined, values);
         }
 
         // Step 11 (implicit).
     };
 }
 
-// ES7, 2016-01-21 draft, 25.4.4.3.
+// ES2016, 25.4.4.3.
 function Promise_static_race(iterable) {
     // Step 1.
     let C = this;
 
     // Step 2.
     if (!IsObject(C))
         ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.race call");
 
@@ -510,17 +347,17 @@ function Promise_static_race(iterable) {
         // TODO: implement iterator closing.
 
         // Step 8.b.
         callContentFunction(promiseCapability.reject, undefined, e);
         return promiseCapability.promise;
     }
 }
 
-// ES7, 2016-01-21 draft, 25.4.4.3.1.
+// ES2016, 25.4.4.3.1.
 function PerformPromiseRace(iteratorRecord, resultCapability, C) {
     assert(IsConstructor(C), "PerformPromiseRace called with non-constructor");
     assert(IsPromiseCapability(resultCapability), "Invalid promise capability record");
 
     // Step 1.
     let iterator = iteratorRecord.iterator;
     let racePromise = resultCapability.promise;
     let next;
@@ -695,17 +532,17 @@ function Promise_static_resolve(x) {
 
     // Steps 6-7.
     callContentFunction(promiseCapability.resolve, undefined, x);
 
     // Step 8.
     return promiseCapability.promise;
 }
 
-//ES6, 25.4.4.6.
+// ES6, 25.4.4.6.
 function Promise_static_get_species() {
     // Step 1.
     return this;
 }
 _SetCanonicalName(Promise_static_get_species, "get [Symbol.species]");
 
 // ES6, 25.4.5.1.
 function Promise_catch(onRejected) {
@@ -864,17 +701,17 @@ function UnwrappedPerformPromiseThen(ful
     function onRejected(argument) {
         return UnsafeCallWrappedFunction(rejectedHandler, undefined, argument);
     }
     return PerformPromiseThen(this, IsCallable(fulfilledHandler) ? onFulfilled : fulfilledHandler,
                               IsCallable(rejectedHandler) ? onRejected : rejectedHandler,
                               resultCapability);
 }
 
-// ES2016, March 1, 2016 draft, 25.4.5.3.1.
+// ES2016, 25.4.5.3.1.
 function PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability) {
     // Step 1.
     assert(IsPromise(promise), "Can't call PerformPromiseThen on non-Promise objects");
 
     // Step 2.
     assert(IsPromiseCapability(resultCapability), "Invalid promise capability record");
 
     // Step 3.
--- a/js/src/builtin/Utilities.js
+++ b/js/src/builtin/Utilities.js
@@ -202,16 +202,25 @@ function GetTypeError(msg) {
     try {
         FUN_APPLY(ThrowTypeError, undefined, arguments);
     } catch (e) {
         return e;
     }
     assert(false, "the catch block should've returned from this function.");
 }
 
+function GetInternalError(msg) {
+    try {
+        FUN_APPLY(ThrowInternalError, undefined, arguments);
+    } catch (e) {
+        return e;
+    }
+    assert(false, "the catch block should've returned from this function.");
+}
+
 // To be used when a function is required but calling it shouldn't do anything.
 function NullFunction() {}
 
 /*************************************** Testing functions ***************************************/
 function outer() {
     return function inner() {
         return "foo";
     }
--- a/js/src/tests/ecma_6/Promise/promise-basics.js
+++ b/js/src/tests/ecma_6/Promise/promise-basics.js
@@ -11,23 +11,33 @@ new Promise(res=>res('result'))
   .then(val=>{results.push('then ' + val); return 'first then rval';})
   .then(val=>results.push('chained then with val: ' + val));
 
 new Promise((res, rej)=>rej('rejection'))
   .catch(val=>{results.push('catch ' + val); return results.length;})
   .then(val=>results.push('then after catch with val: ' + val),
         val=>{throw new Error("mustn't be called")});
 
+new Promise((res, rej)=> {res('result');  rej('rejection'); })
+  .catch(val=>{throw new Error("mustn't be called");})
+  .then(val=>results.push('then after resolve+reject with val: ' + val),
+        val=>{throw new Error("mustn't be called")});
+  
+new Promise((res, rej)=> { rej('rejection'); res('result'); })
+  .catch(val=>{results.push('catch after reject+resolve with val: ' + val);})
+
 drainJobQueue();
 
-assertEq(results.length, 4);
+assertEq(results.length, 6);
 assertEq(results[0], 'then result');
 assertEq(results[1], 'catch rejection');
-assertEq(results[2], 'chained then with val: first then rval');
-assertEq(results[3], 'then after catch with val: 2');
+assertEq(results[2], 'catch after reject+resolve with val: rejection');
+assertEq(results[3], 'chained then with val: first then rval');
+assertEq(results[4], 'then after catch with val: 2');
+assertEq(results[5], 'then after resolve+reject with val: result');
 
 function callback() {}
 
 // Calling the executor function with content functions shouldn't assert:
 Promise.resolve.call(function(exec) { exec(callback, callback); });
 Promise.reject.call(function(exec) { exec(callback, callback); });
 Promise.all.call(function(exec) { exec(callback, callback); });
 Promise.race.call(function(exec) { exec(callback, callback); });
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -219,18 +219,23 @@
     macro(ownKeys, ownKeys, "ownKeys") \
     macro(parseFloat, parseFloat, "parseFloat") \
     macro(parseInt, parseInt, "parseInt") \
     macro(pattern, pattern, "pattern") \
     macro(preventExtensions, preventExtensions, "preventExtensions") \
     macro(promise, promise, "promise") \
     macro(state, state, "state") \
     macro(pending, pending, "pending") \
+    macro(fulfillHandler, fulfillHandler, "fulfillHandler") \
     macro(fulfilled, fulfilled, "fulfilled") \
+    macro(reject, reject, "reject") \
     macro(rejected, rejected, "rejected") \
+    macro(rejectHandler, rejectHandler, "rejectHandler") \
+    macro(resolve, resolve, "resolve") \
+    macro(incumbentGlobal, incumbentGlobal, "incumbentGlobal") \
     macro(propertyIsEnumerable, propertyIsEnumerable, "propertyIsEnumerable") \
     macro(proto, proto, "__proto__") \
     macro(prototype, prototype, "prototype") \
     macro(proxy, proxy, "proxy") \
     macro(reason, reason, "reason") \
     macro(Reify, Reify, "Reify") \
     macro(RequireObjectCoercible, RequireObjectCoercible, "RequireObjectCoercible") \
     macro(resumeGenerator, resumeGenerator, "resumeGenerator") \
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -205,17 +205,17 @@ intrinsic_UnsafeCallWrappedFunction(JSCo
     MOZ_ASSERT(IsCallable(args[0]));
     MOZ_ASSERT(IsWrapper(&args[0].toObject()));
     MOZ_ASSERT(args[1].isObject() || args[1].isUndefined());
 
     MOZ_RELEASE_ASSERT(args[0].isObject());
     RootedObject wrappedFun(cx, &args[0].toObject());
     RootedObject fun(cx, UncheckedUnwrap(wrappedFun));
     MOZ_RELEASE_ASSERT(fun->is<JSFunction>());
-    MOZ_RELEASE_ASSERT(fun->as<JSFunction>().isSelfHostedBuiltin());
+    MOZ_RELEASE_ASSERT(fun->as<JSFunction>().isSelfHostedOrIntrinsic());
 
     InvokeArgs args2(cx);
     if (!args2.init(args.length() - 2))
         return false;
 
     args2.setThis(args[1]);
 
     for (size_t i = 0; i < args2.length(); i++)
@@ -1830,35 +1830,16 @@ intrinsic_EnqueuePromiseReactionJob(JSCo
                                    objectFromIncumbentGlobal))
     {
         return false;
     }
     args.rval().setUndefined();
     return true;
 }
 
-static bool
-intrinsic_EnqueuePromiseResolveThenableJob(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-
-    MOZ_ASSERT(args.length() == 3);
-    MOZ_ASSERT(IsCallable(args[2]));
-
-    RootedValue promiseToResolve(cx, args[0]);
-    RootedValue thenable(cx, args[1]);
-    RootedValue then(cx, args[2]);
-
-    if (!EnqueuePromiseResolveThenableJob(cx, promiseToResolve, thenable, then))
-        return false;
-
-    args.rval().setUndefined();
-    return true;
-}
-
 // ES2016, February 12 draft, 25.4.1.9.
 static bool
 intrinsic_HostPromiseRejectionTracker(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 2);
     MOZ_ASSERT(args[0].toObject().is<PromiseObject>());
 
@@ -1983,30 +1964,16 @@ intrinsic_ConstructorForTypedArray(JSCon
     RootedObject ctor(cx);
     if (!GetBuiltinConstructor(cx, protoKey, &ctor))
         return false;
 
     args.rval().setObject(*ctor);
     return true;
 }
 
-static bool
-intrinsic_OriginalPromiseConstructor(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-    MOZ_ASSERT(args.length() == 0);
-
-    JSObject* obj = GlobalObject::getOrCreatePromiseConstructor(cx, cx->global());
-    if (!obj)
-        return false;
-
-    args.rval().setObject(*obj);
-    return true;
-}
-
 /**
  * Returns an object created in the embedding-provided incumbent global.
  *
  * Really, we want the incumbent global itself so we can pass it to other
  * embedding hooks which need it. Specifically, the enqueue promise hook
  * takes an incumbent global so it can set that on the PromiseCallbackJob
  * it creates.
  *
@@ -2040,57 +2007,16 @@ intrinsic_GetObjectFromIncumbentGlobal(J
     if (obj && !cx->compartment()->wrap(cx, &objVal))
         return false;
 
     args.rval().set(objVal);
     return true;
 }
 
 static bool
-intrinsic_RejectUnwrappedPromise(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-    MOZ_ASSERT(args.length() == 2);
-
-    RootedObject obj(cx, &args[0].toObject());
-    MOZ_ASSERT(IsWrapper(obj));
-    Rooted<PromiseObject*> promise(cx, &UncheckedUnwrap(obj)->as<PromiseObject>());
-    AutoCompartment ac(cx, promise);
-    RootedValue reasonVal(cx, args[1]);
-
-    // The rejection reason might've been created in a compartment with higher
-    // privileges than the Promise's. In that case, object-type rejection
-    // values might be wrapped into a wrapper that throws whenever the
-    // Promise's reaction handler wants to do anything useful with it. To
-    // avoid that situation, we synthesize a generic error that doesn't
-    // expose any privileged information but can safely be used in the
-    // rejection handler.
-    if (!promise->compartment()->wrap(cx, &reasonVal))
-        return false;
-    if (reasonVal.isObject() && !CheckedUnwrap(&reasonVal.toObject())) {
-        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
-                             JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON);
-        if (!GetAndClearException(cx, &reasonVal))
-            return false;
-    }
-
-    RootedAtom atom(cx, Atomize(cx, "RejectPromise", strlen("RejectPromise")));
-    if (!atom)
-        return false;
-    RootedPropertyName name(cx, atom->asPropertyName());
-
-    FixedInvokeArgs<2> args2(cx);
-
-    args2[0].setObject(*promise);
-    args2[1].set(reasonVal);
-
-    return CallSelfHostedFunction(cx, name, UndefinedHandleValue, args2, args.rval());
-}
-
-static bool
 intrinsic_IsWrappedPromiseObject(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 1);
 
     RootedObject obj(cx, &args[0].toObject());
     MOZ_ASSERT(!obj->is<PromiseObject>(),
                "Unwrapped promises should be filtered out in inlineable code");
@@ -2237,34 +2163,16 @@ intrinsic_ModuleNamespaceExports(JSConte
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 1);
     RootedModuleNamespaceObject namespace_(cx, &args[0].toObject().as<ModuleNamespaceObject>());
     args.rval().setObject(namespace_->exports());
     return true;
 }
 
-/**
- * Intrinsic used to tell the debugger about settled promises.
- *
- * This is invoked both when resolving and rejecting promises, after the
- * resulting state has been set on the promise, and it's up to the debugger
- * to act on this signal in whichever way it wants.
- */
-static bool
-intrinsic_onPromiseSettled(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-    MOZ_ASSERT(args.length() == 1);
-    Rooted<PromiseObject*> promise(cx, &args[0].toObject().as<PromiseObject>());
-    promise->onSettled(cx);
-    args.rval().setUndefined();
-    return true;
-}
-
 // The self-hosting global isn't initialized with the normal set of builtins.
 // Instead, individual C++-implemented functions that're required by
 // self-hosted code are defined as global functions. Accessing these
 // functions via a content compartment's builtins would be unsafe, because
 // content script might have changed the builtins' prototypes' members.
 // Installing the whole set of builtins in the self-hosting compartment, OTOH,
 // would be wasteful: it increases memory usage and initialization time for
 // self-hosting compartment.
@@ -2519,20 +2427,17 @@ static const JSFunctionSpec intrinsic_fu
     JS_FN("IsWeakSet", intrinsic_IsInstanceOfBuiltin<WeakSetObject>, 1,0),
     JS_FN("CallWeakSetMethodIfWrapped",
           CallNonGenericSelfhostedMethod<Is<WeakSetObject>>, 2, 0),
 
     JS_FN("_GetObjectFromIncumbentGlobal",  intrinsic_GetObjectFromIncumbentGlobal, 0, 0),
     JS_FN("IsPromise",                      intrinsic_IsInstanceOfBuiltin<PromiseObject>, 1,0),
     JS_FN("IsWrappedPromise",               intrinsic_IsWrappedPromiseObject,     1, 0),
     JS_FN("_EnqueuePromiseReactionJob",     intrinsic_EnqueuePromiseReactionJob,  2, 0),
-    JS_FN("_EnqueuePromiseResolveThenableJob", intrinsic_EnqueuePromiseResolveThenableJob, 2, 0),
     JS_FN("HostPromiseRejectionTracker",    intrinsic_HostPromiseRejectionTracker,2, 0),
-    JS_FN("_GetOriginalPromiseConstructor", intrinsic_OriginalPromiseConstructor, 0, 0),
-    JS_FN("RejectUnwrappedPromise",         intrinsic_RejectUnwrappedPromise,     2, 0),
     JS_FN("CallPromiseMethodIfWrapped",
           CallNonGenericSelfhostedMethod<Is<PromiseObject>>,      2,0),
 
     // See builtin/TypedObject.h for descriptors of the typedobj functions.
     JS_FN("NewOpaqueTypedObject",           js::NewOpaqueTypedObject, 1, 0),
     JS_FN("NewDerivedTypedObject",          js::NewDerivedTypedObject, 3, 0),
     JS_FN("TypedObjectBuffer",              TypedObject::GetBuffer, 1, 0),
     JS_FN("TypedObjectByteOffset",          TypedObject::GetByteOffset, 1, 0),
@@ -2632,18 +2537,16 @@ static const JSFunctionSpec intrinsic_fu
           intrinsic_InstantiateModuleFunctionDeclarations, 1, 0),
     JS_FN("SetModuleState", intrinsic_SetModuleState, 1, 0),
     JS_FN("EvaluateModule", intrinsic_EvaluateModule, 1, 0),
     JS_FN("IsModuleNamespace", intrinsic_IsInstanceOfBuiltin<ModuleNamespaceObject>, 1, 0),
     JS_FN("NewModuleNamespace", intrinsic_NewModuleNamespace, 2, 0),
     JS_FN("AddModuleNamespaceBinding", intrinsic_AddModuleNamespaceBinding, 4, 0),
     JS_FN("ModuleNamespaceExports", intrinsic_ModuleNamespaceExports, 1, 0),
 
-    JS_FN("_dbg_onPromiseSettled", intrinsic_onPromiseSettled, 1, 0),
-
     JS_FS_END
 };
 
 void
 js::FillSelfHostingCompileOptions(CompileOptions& options)
 {
     /*
      * In self-hosting mode, scripts use JSOP_GETINTRINSIC instead of