Bug 1289318 - Part 9: Port Promise.resolve and Promise.reject to C++ and optimize various common cases. r?efaust draft
authorTill Schneidereit <till@tillschneidereit.net>
Sun, 14 Aug 2016 02:00:18 +0200
changeset 401141 e23bac24c9c438be048aeee991b5e51030a0afd2
parent 400701 a7b0f6d3a055a4f4a94171cebb306b9db84e9b2c
child 528407 764977f4aaad766135d39fb1853eda749bacb796
push id26373
push userbmo:till@tillschneidereit.net
push dateTue, 16 Aug 2016 12:20:59 +0000
reviewersefaust
bugs1289318
milestone51.0a1
Bug 1289318 - Part 9: Port Promise.resolve and Promise.reject to C++ and optimize various common cases. r?efaust This adds a lot of C++ code, but it allows us to optimize cases that would be annoying to optimize in JS. MozReview-Commit-ID: CbKWXEs8pMv
js/src/builtin/Promise.cpp
js/src/builtin/Promise.h
js/src/builtin/Promise.js
js/src/jsapi.cpp
js/src/tests/ecma_6/Promise/promise-basics.js
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -17,35 +17,16 @@
 #include "vm/SelfHosting.h"
 
 #include "jsobjinlines.h"
 
 #include "vm/NativeObject-inl.h"
 
 using namespace js;
 
-static const JSFunctionSpec promise_methods[] = {
-    JS_SELF_HOSTED_FN("catch", "Promise_catch", 1, 0),
-    JS_SELF_HOSTED_FN("then", "Promise_then", 2, 0),
-    JS_FS_END
-};
-
-static const JSFunctionSpec promise_static_methods[] = {
-    JS_SELF_HOSTED_FN("all", "Promise_static_all", 1, 0),
-    JS_SELF_HOSTED_FN("race", "Promise_static_race", 1, 0),
-    JS_SELF_HOSTED_FN("reject", "Promise_static_reject", 1, 0),
-    JS_SELF_HOSTED_FN("resolve", "Promise_static_resolve", 1, 0),
-    JS_FS_END
-};
-
-static const JSPropertySpec promise_static_properties[] = {
-    JS_SELF_HOSTED_SYM_GET(species, "Promise_static_get_species", 0),
-    JS_PS_END
-};
-
 static double
 MillisecondsSinceStartup()
 {
     auto now = mozilla::TimeStamp::Now();
     bool ignored;
     return (now - mozilla::TimeStamp::ProcessCreation(ignored)).ToMilliseconds();
 }
 
@@ -275,16 +256,55 @@ RejectPromiseFunction(JSContext* cx, uns
 
     // Step 6.
     bool result = RejectMaybeWrappedPromise(cx, promise, reasonVal);
     if (result)
         args.rval().setUndefined();
     return result;
 }
 
+// ES2016, 25.4.1.3.2, steps 7-13.
+static bool
+ResolvePromiseInternal(JSContext* cx, HandleObject promise, HandleValue resolutionVal)
+{
+    // Step 7.
+    if (!resolutionVal.isObject())
+        return FulfillMaybeWrappedPromise(cx, promise, resolutionVal);
+
+    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)) {
+        return FulfillMaybeWrappedPromise(cx, promise, resolutionVal);
+    }
+
+    // Step 12.
+    RootedValue promiseVal(cx, ObjectValue(*promise));
+    if (!EnqueuePromiseResolveThenableJob(cx, promiseVal, resolutionVal, thenVal))
+        return false;
+
+    // Step 13.
+    return true;
+}
+
 // 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));
@@ -316,56 +336,20 @@ ResolvePromiseFunction(JSContext* cx, un
 
         // 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;
+    bool status = ResolvePromiseInternal(cx, promise, resolutionVal);
+    if (status)
+        args.rval().setUndefined();
+    return status;
 }
 
 // ES2016, 25.4.1.3.
 static MOZ_MUST_USE bool
 CreateResolvingFunctions(JSContext* cx, HandleValue promise,
                          MutableHandleValue resolveVal,
                          MutableHandleValue rejectVal)
 {
@@ -392,17 +376,311 @@ CreateResolvingFunctions(JSContext* cx, 
     reject->setExtendedSlot(ResolutionFunctionSlot_OtherFunction, ObjectValue(*resolve));
 
     resolveVal.setObject(*resolve);
     rejectVal.setObject(*reject);
 
     return true;
 }
 
+static PromiseObject*
+CreatePromiseObjectInternal(JSContext* cx, HandleObject proto, bool protoIsWrapped)
+{
+    // Step 3.
+    Rooted<PromiseObject*> promise(cx);
+    // Enter the unwrapped proto's compartment, if that's different from
+    // the current one.
+    // All state stored in a Promise's fixed slots must be created in the
+    // same compartment, so we get all of that out of the way here.
+    // (Except for the resolution functions, which are created below.)
+    mozilla::Maybe<AutoCompartment> ac;
+    if (protoIsWrapped)
+        ac.emplace(cx, proto);
+
+    promise = NewObjectWithClassProto<PromiseObject>(cx, proto);
+    if (!promise)
+        return nullptr;
+
+    // Step 4.
+    promise->setFixedSlot(PROMISE_FLAGS_SLOT, Int32Value(0));
+
+    // Steps 5-6.
+    // Omitted, we allocate our single list of reaction records lazily.
+
+    // Step 7.
+    // Implicit, the handled flag is unset by default.
+
+    // Store an allocation stack so we can later figure out what the
+    // control flow was for some unexpected results. Frightfully expensive,
+    // but oh well.
+    RootedObject stack(cx);
+    if (cx->options().asyncStack() || cx->compartment()->isDebuggee()) {
+        if (!JS::CaptureCurrentStack(cx, &stack, JS::StackCapture(JS::AllFrames())))
+            return nullptr;
+    }
+    promise->setFixedSlot(PROMISE_ALLOCATION_SITE_SLOT, ObjectOrNullValue(stack));
+    promise->setFixedSlot(PROMISE_ALLOCATION_TIME_SLOT,
+                          DoubleValue(MillisecondsSinceStartup()));
+
+    return promise;
+}
+
+/**
+ * Unforgeable version of ES2016, 25.4.4.4, Promise.reject.
+ */
+/* static */ JSObject*
+PromiseObject::unforgeableReject(JSContext* cx, HandleValue value)
+{
+    // Steps 1-2 (omitted).
+
+    // Roughly step 3.
+    Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx, nullptr, false));
+    if (!promise)
+        return nullptr;
+
+    // Let the Debugger know about this Promise.
+    JS::dbg::onNewPromise(cx, promise);
+
+    // Roughly step 4.
+    if (!ResolvePromise(cx, promise, value, JS::PromiseState::Rejected))
+        return nullptr;
+
+    // Step 5.
+    return promise;
+}
+
+/**
+ * Unforgeable version of ES2016, 25.4.4.5, Promise.resolve.
+ */
+/* static */ JSObject*
+PromiseObject::unforgeableResolve(JSContext* cx, HandleValue value)
+{
+    // Steps 1-2 (omitted).
+
+    // Step 3.
+    if (value.isObject()) {
+        JSObject* obj = &value.toObject();
+        if (IsWrapper(obj))
+            obj = CheckedUnwrap(obj);
+        // Instead of getting the `constructor` property, do an unforgeable
+        // check.
+        if (obj && obj->is<PromiseObject>())
+            return obj;
+    }
+
+    // Step 4.
+    Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx, nullptr, false));
+    if (!promise)
+        return nullptr;
+
+    // Let the Debugger know about this Promise.
+    JS::dbg::onNewPromise(cx, promise);
+
+    // Steps 5.
+    if (!ResolvePromiseInternal(cx, promise, value))
+        return nullptr;
+
+    // Step 6.
+    return promise;
+}
+
+// ES6, 25.4.1.5.1.
+/* static */ bool
+GetCapabilitiesExecutor(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    RootedFunction F(cx, &args.callee().as<JSFunction>());
+
+    // Steps 1-2 (implicit).
+
+    // Steps 3-4.
+    if (!F->getExtendedSlot(0).isUndefined() || !F->getExtendedSlot(1).isUndefined()) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
+                             JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY);
+        return false;
+    }
+
+    // Step 5.
+    F->setExtendedSlot(0, args.get(0));
+
+    // Step 6.
+    F->setExtendedSlot(1, args.get(1));
+
+    // Step 7.
+    args.rval().setUndefined();
+    return true;
+}
+
+// ES2016, 25.4.1.5.
+// Creates PromiseCapability records, see 25.4.1.1.
+static bool
+NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise,
+                     MutableHandleObject resolve, MutableHandleObject reject)
+{
+    RootedValue cVal(cx, ObjectValue(*C));
+
+    // Steps 1-2.
+    if (!IsConstructor(C)) {
+        ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, -1, cVal, nullptr);
+        return false;
+    }
+
+    // Step 3 (omitted).
+
+    // Step 4.
+    RootedAtom funName(cx, cx->names().empty);
+    RootedFunction executor(cx, NewNativeFunction(cx, GetCapabilitiesExecutor, 2, funName,
+                                                  gc::AllocKind::FUNCTION_EXTENDED));
+    if (!executor)
+        return false;
+
+    // Step 5 (omitted).
+
+    // Step 6.
+    FixedConstructArgs<1> cargs(cx);
+    cargs[0].setObject(*executor);
+    if (!Construct(cx, cVal, cargs, cVal, promise))
+        return false;
+
+    // Step 7.
+    RootedValue resolveVal(cx, executor->getExtendedSlot(0));
+    if (!IsCallable(resolveVal)) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
+                             JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE);
+        return false;
+    }
+
+    // Step 8.
+    RootedValue rejectVal(cx, executor->getExtendedSlot(1));
+    if (!IsCallable(rejectVal)) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
+                             JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE);
+        return false;
+    }
+
+    // Step 9 (well, the equivalent for all of promiseCapabilities' fields.)
+    resolve.set(&resolveVal.toObject());
+    reject.set(&rejectVal.toObject());
+
+    // Step 10.
+    return true;
+}
+
+enum ResolveOrRejectMode {
+    ResolveMode,
+    RejectMode
+};
+
+static bool
+CommonStaticResolveRejectImpl(JSContext* cx, unsigned argc, Value* vp, ResolveOrRejectMode mode)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    RootedValue x(cx, args.get(0));
+
+    // Steps 1-2.
+    if (!args.thisv().isObject()) {
+        const char* msg = mode == ResolveMode
+                          ? "Receiver of Promise.resolve call"
+                          : "Receiver of Promise.reject call";
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, msg);
+        return false;
+    }
+    RootedValue cVal(cx, args.thisv());
+    RootedObject C(cx, &cVal.toObject());
+
+    // Step 3 of Resolve.
+    if (mode == ResolveMode && x.isObject()) {
+        RootedObject xObj(cx, &x.toObject());
+        bool isWrapper = false;
+        if (IsWrapper(xObj)) {
+            xObj = CheckedUnwrap(xObj);
+            isWrapper = true;
+        }
+        if (xObj->is<PromiseObject>()) {
+            mozilla::Maybe<AutoCompartment> ac;
+            if (isWrapper)
+                ac.emplace(cx, xObj);
+            RootedValue ctorVal(cx);
+            if (!GetProperty(cx, xObj, xObj, cx->names().constructor, &ctorVal))
+                return false;
+            if (ctorVal == cVal) {
+                args.rval().set(x);
+                return true;
+            }
+        }
+    }
+
+    // Steps 4-5 of Resolve, 3-4 of Reject.
+    RootedObject promiseCtor(cx);
+    if (!GetBuiltinConstructor(cx, JSProto_Promise, &promiseCtor))
+        return false;
+    RootedObject promise(cx);
+
+    // If the current constructor is the original Promise constructor, we can
+    // optimize things by skipping the creation and invocation of the resolve
+    // and reject callbacks, directly creating and resolving the new Promise.
+    if (promiseCtor == C) {
+        // Roughly step 4 of Resolve, 3 of Reject.
+        promise = CreatePromiseObjectInternal(cx, nullptr, false);
+        if (!promise)
+            return false;
+
+        // Let the Debugger know about this Promise.
+        JS::dbg::onNewPromise(cx, promise);
+
+        // Roughly step 5 of Resolve.
+        if (mode == ResolveMode) {
+            if (!ResolvePromiseInternal(cx, promise, x))
+                return false;
+        } else {
+            // Roughly step 4 of Reject.
+            Rooted<PromiseObject*> promiseObj(cx, &promise->as<PromiseObject>());
+            if (!ResolvePromise(cx, promiseObj, x, JS::PromiseState::Rejected))
+                return false;
+        }
+    } else {
+        // Step 4 of Resolve, 3 of Reject.
+        RootedObject resolveFun(cx);
+        RootedObject rejectFun(cx);
+        if (!NewPromiseCapability(cx, C, &promise, &resolveFun, &rejectFun))
+            return false;
+
+        // Step 5 of Resolve, 4 of Reject.
+        FixedInvokeArgs<1> args2(cx);
+        args2[0].set(x);
+        RootedValue calleeOrRval(cx, ObjectValue(mode == ResolveMode ? *resolveFun : *rejectFun));
+        if (!Call(cx, calleeOrRval, UndefinedHandleValue, args2, &calleeOrRval))
+            return false;
+    }
+
+    // Step 6 of Resolve, 4 of Reject.
+    args.rval().setObject(*promise);
+    return true;
+}
+
+/**
+ * ES2016, 25.4.4.4, Promise.reject.
+ */
+static bool
+Promise_reject(JSContext* cx, unsigned argc, Value* vp)
+{
+    return CommonStaticResolveRejectImpl(cx, argc, vp, RejectMode);
+}
+
+/**
+ * ES2016, 25.4.4.5, Promise.resolve.
+ */
+static bool
+Promise_resolve(JSContext* cx, unsigned argc, Value* vp)
+{
+    return CommonStaticResolveRejectImpl(cx, argc, vp, ResolveMode);
+}
+
 // ES2016, February 12 draft, 25.4.3.1. steps 3-11.
+/* static */
 PromiseObject*
 PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto /* = nullptr */)
 {
     MOZ_ASSERT(executor->isCallable());
 
     RootedObject usedProto(cx, proto);
     bool wrappedProto = false;
     // If the proto is wrapped, that means the current function is running
@@ -412,53 +690,18 @@ PromiseObject::create(JSContext* cx, Han
     if (proto && IsWrapper(proto)) {
         wrappedProto = true;
         usedProto = CheckedUnwrap(proto);
         if (!usedProto)
             return nullptr;
     }
 
 
-    // Step 3.
-    Rooted<PromiseObject*> promise(cx);
-    {
-        // Enter the unwrapped proto's compartment, if that's different from
-        // the current one.
-        // All state stored in a Promise's fixed slots must be created in the
-        // same compartment, so we get all of that out of the way here.
-        // (Except for the resolution functions, which are created below.)
-        mozilla::Maybe<AutoCompartment> ac;
-        if (wrappedProto)
-            ac.emplace(cx, usedProto);
-
-        promise = NewObjectWithClassProto<PromiseObject>(cx, usedProto);
-        if (!promise)
-            return nullptr;
-
-        // Step 4.
-        promise->setFixedSlot(PROMISE_FLAGS_SLOT, Int32Value(0));
-
-        // Steps 5-6.
-        // Omitted, we allocate our single list of reaction records lazily.
-
-        // Step 7.
-        // Implicit, the handled flag is unset by default.
-
-        // Store an allocation stack so we can later figure out what the
-        // control flow was for some unexpected results. Frightfully expensive,
-        // but oh well.
-        RootedObject stack(cx);
-        if (cx->options().asyncStack() || cx->compartment()->isDebuggee()) {
-            if (!JS::CaptureCurrentStack(cx, &stack, JS::StackCapture(JS::AllFrames())))
-                return nullptr;
-        }
-        promise->setFixedSlot(PROMISE_ALLOCATION_SITE_SLOT, ObjectOrNullValue(stack));
-        promise->setFixedSlot(PROMISE_ALLOCATION_TIME_SLOT,
-                              DoubleValue(MillisecondsSinceStartup()));
-    }
+    // Steps 3-7.
+    Rooted<PromiseObject*> promise(cx, CreatePromiseObjectInternal(cx, usedProto, wrappedProto));
 
     RootedValue promiseVal(cx, ObjectValue(*promise));
     if (wrappedProto && !cx->compartment()->wrap(cx, &promiseVal))
         return nullptr;
 
     // Step 8.
     // The resolving functions are created in the compartment active when the
     // (maybe wrapped) Promise constructor was called. They contain checks and
@@ -1099,16 +1342,35 @@ EnqueuePromiseResolveThenableJob(JSConte
 } // namespace js
 
 static JSObject*
 CreatePromisePrototype(JSContext* cx, JSProtoKey key)
 {
     return cx->global()->createBlankPrototype(cx, &PromiseObject::protoClass_);
 }
 
+static const JSFunctionSpec promise_methods[] = {
+    JS_SELF_HOSTED_FN("catch", "Promise_catch", 1, 0),
+    JS_SELF_HOSTED_FN("then", "Promise_then", 2, 0),
+    JS_FS_END
+};
+
+static const JSFunctionSpec promise_static_methods[] = {
+    JS_SELF_HOSTED_FN("all", "Promise_static_all", 1, 0),
+    JS_SELF_HOSTED_FN("race", "Promise_static_race", 1, 0),
+    JS_FN("reject", Promise_reject, 1, 0),
+    JS_FN("resolve", Promise_resolve, 1, 0),
+    JS_FS_END
+};
+
+static const JSPropertySpec promise_static_properties[] = {
+    JS_SELF_HOSTED_SYM_GET(species, "Promise_static_get_species", 0),
+    JS_PS_END
+};
+
 static const ClassSpec PromiseObjectClassSpec = {
     GenericCreateConstructor<PromiseConstructor, 1, gc::AllocKind::FUNCTION>,
     CreatePromisePrototype,
     promise_static_methods,
     promise_static_properties,
     promise_methods
 };
 
--- a/js/src/builtin/Promise.h
+++ b/js/src/builtin/Promise.h
@@ -18,16 +18,19 @@ class PromiseObject : public NativeObjec
 {
   public:
     static const unsigned RESERVED_SLOTS = 8;
     static const Class class_;
     static const Class protoClass_;
     static PromiseObject* create(JSContext* cx, HandleObject executor,
                                  HandleObject proto = nullptr);
 
+    static JSObject* unforgeableResolve(JSContext* cx, HandleValue value);
+    static JSObject* unforgeableReject(JSContext* cx, HandleValue value);
+
     JS::PromiseState state() {
         int32_t flags = getFixedSlot(PROMISE_FLAGS_SLOT).toInt32();
         if (!(flags & PROMISE_FLAG_RESOLVED)) {
             MOZ_ASSERT(!(flags & PROMISE_FLAG_FULFILLED));
             return JS::PromiseState::Pending;
         }
         if (flags & PROMISE_FLAG_FULFILLED)
             return JS::PromiseState::Fulfilled;
--- a/js/src/builtin/Promise.js
+++ b/js/src/builtin/Promise.js
@@ -490,57 +490,19 @@ function AddDependentPromise(dependentPr
 
     // The reactions list might not have been allocated yet.
     if (!reactions)
         UnsafeSetReservedSlot(dependentPromise, PROMISE_REACTIONS_OR_RESULT_SLOT, [reaction]);
     else
         _DefineDataProperty(reactions, reactions.length, reaction);
 }
 
-// ES6, 25.4.4.4.
-function Promise_static_reject(r) {
-    // Step 1.
-    let C = this;
-
-    // Step 2.
-    if (!IsObject(C))
-        ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.reject call");
-
-    // Steps 3-4.
-    let promiseCapability = NewPromiseCapability(C);
-
-    // Steps 5-6.
-    callContentFunction(promiseCapability.reject, undefined, r);
-
-    // Step 7.
-    return promiseCapability.promise;
-}
+// ES2016, 25.4.4.4 (implemented in C++).
 
-// ES6, 25.4.4.5.
-function Promise_static_resolve(x) {
-    // Step 1.
-    let C = this;
-
-    // Step 2.
-    if (!IsObject(C))
-        ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.resolve call");
-
-    // Step 3.
-    if (IsObject(x) && (IsPromise(x) || IsWrappedPromise(x)) && x.constructor === C)
-        return x;
-
-    // Steps 4-5.
-    let promiseCapability = NewPromiseCapability(C);
-
-    // Steps 6-7.
-    callContentFunction(promiseCapability.resolve, undefined, x);
-
-    // Step 8.
-    return promiseCapability.promise;
-}
+// ES2016, 25.4.4.5 (implemented in C++).
 
 // ES6, 25.4.4.6.
 function Promise_static_get_species() {
     // Step 1.
     return this;
 }
 _SetCanonicalName(Promise_static_get_species, "get [Symbol.species]");
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4765,61 +4765,27 @@ JS::DumpPromiseResolutionSite(JSContext*
     if (stackStr.get())
         fputs(stackStr.get(), stderr);
 }
 #endif
 
 JS_PUBLIC_API(JSObject*)
 JS::CallOriginalPromiseResolve(JSContext* cx, JS::HandleValue resolutionValue)
 {
-    RootedObject promiseCtor(cx, GetPromiseConstructor(cx));
-    if (!promiseCtor)
-        return nullptr;
-
-    JSObject* obj;
-    {
-        FixedInvokeArgs<1> args(cx);
-
-        args[0].set(resolutionValue);
-
-        RootedValue thisvOrRval(cx, ObjectValue(*promiseCtor));
-        if (!CallSelfHostedFunction(cx, "Promise_static_resolve", thisvOrRval, args, &thisvOrRval))
-            return nullptr;
-
-        MOZ_ASSERT(thisvOrRval.isObject());
-        obj = &thisvOrRval.toObject();
-    }
-
-    MOZ_ASSERT(obj->is<PromiseObject>());
-    return obj;
+    RootedObject promise(cx, PromiseObject::unforgeableResolve(cx, resolutionValue));
+    MOZ_ASSERT_IF(promise, promise->is<PromiseObject>());
+    return promise;
 }
 
 JS_PUBLIC_API(JSObject*)
 JS::CallOriginalPromiseReject(JSContext* cx, JS::HandleValue rejectionValue)
 {
-    RootedObject promiseCtor(cx, GetPromiseConstructor(cx));
-    if (!promiseCtor)
-        return nullptr;
-
-    JSObject* obj;
-    {
-        FixedInvokeArgs<1> args(cx);
-
-        args[0].set(rejectionValue);
-
-        RootedValue thisvOrRval(cx, ObjectValue(*promiseCtor));
-        if (!CallSelfHostedFunction(cx, "Promise_static_reject", thisvOrRval, args, &thisvOrRval))
-            return nullptr;
-
-        MOZ_ASSERT(thisvOrRval.isObject());
-        obj = &thisvOrRval.toObject();
-    }
-
-    MOZ_ASSERT(obj->is<PromiseObject>());
-    return obj;
+    RootedObject promise(cx, PromiseObject::unforgeableReject(cx, rejectionValue));
+    MOZ_ASSERT_IF(promise, promise->is<PromiseObject>());
+    return promise;
 }
 
 JS_PUBLIC_API(bool)
 JS::ResolvePromise(JSContext* cx, JS::HandleObject promise, JS::HandleValue resolutionValue)
 {
     MOZ_ASSERT(promise->is<PromiseObject>());
     return promise->as<PromiseObject>().resolve(cx, resolutionValue);
 }
--- a/js/src/tests/ecma_6/Promise/promise-basics.js
+++ b/js/src/tests/ecma_6/Promise/promise-basics.js
@@ -19,45 +19,76 @@ new Promise((res, rej)=>rej('rejection')
 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, 6);
 assertEq(results[0], 'then result');
 assertEq(results[1], 'catch rejection');
 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');
 
+results = [];
+
+Promise.resolve('resolution').then(res=>results.push(res), 
+                                   _=>{throw new Error("mustn't be called")});
+
+let thenCalled = false;
+Promise.reject('rejection').then(_=>{thenCalled = true},
+                                 rej=>results.push(rej));
+
+drainJobQueue();
+
+assertEq(thenCalled, false);
+assertEq(results.length, 2);
+assertEq(results[0], 'resolution');
+assertEq(results[1], 'rejection');
+
+
 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); });
 
+let resolveResult = undefined;
+function resolveFun() {resolveResult = "resolveCalled";}
+Promise.resolve.call(function(exec) { exec(resolveFun, callback); });
+assertEq(resolveResult, "resolveCalled");
+
+let rejectResult = undefined;
+function rejectFun() {rejectResult = "rejectCalled";}
+Promise.reject.call(function(exec) { exec(callback, rejectFun); });
+assertEq(rejectResult, "rejectCalled");
+
 // These should throw:
+var wasCalled = false;
 var hasThrown = false;
 try {
     // Calling the executor function twice, providing a resolve callback both times.
     Promise.resolve.call(function(executor) {
+        wasCalled = true;
         executor(callback, undefined);
         executor(callback, callback);
     });
 } catch (e) {
     hasThrown = true;
 }
+assertEq(wasCalled, true);
 assertEq(hasThrown, true);
 
 var hasThrown = false;
 try {
     // Calling the executor function twice, providing a reject callback both times.
     Promise.resolve.call(function(executor) {
         executor(undefined, callback);
         executor(callback, callback);