Bug 1289318 - Part 4: Only allocate the Promise reactions array once the first reaction record is added. r?efaust draft
authorTill Schneidereit <till@tillschneidereit.net>
Wed, 03 Aug 2016 20:01:10 +0200
changeset 400697 6429d48ca8d8547f7a2ecb737723c5d304a8e779
parent 400696 94d70296f22a8b1e3e1b707ce1a004d137d20a56
child 400698 2bf87b24ee110575403ed5496f9eeb6d4262b4ca
push id26245
push userbmo:till@tillschneidereit.net
push dateMon, 15 Aug 2016 14:02:13 +0000
reviewersefaust
bugs1289318
milestone51.0a1
Bug 1289318 - Part 4: Only allocate the Promise reactions array once the first reaction record is added. r?efaust Saves 96 bytes on reaction-less promises. It also saves 32 bytes on promises that have up to two reactions: empty arrays are initialized with an allocated length of 8, whereas providing an initial element initializes to 2. MozReview-Commit-ID: 3PtT7LDwL3k
js/src/builtin/Promise.cpp
js/src/builtin/Promise.js
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -110,21 +110,17 @@ PromiseObject::create(JSContext* cx, Han
         promise = NewObjectWithClassProto<PromiseObject>(cx, usedProto);
         if (!promise)
             return nullptr;
 
         // Step 4.
         promise->setFixedSlot(PROMISE_STATE_SLOT, Int32Value(PROMISE_STATE_PENDING));
 
         // Steps 5-6.
-        // We only have a single list of reaction records.
-        RootedArrayObject reactions(cx, NewDenseEmptyArray(cx));
-        if (!reactions)
-            return nullptr;
-        promise->setFixedSlot(PROMISE_REACTIONS_SLOT, ObjectValue(*reactions));
+        // Omitted, we allocate our single list of reaction records lazily.
 
         // Step 7.
         promise->setFixedSlot(PROMISE_IS_HANDLED_SLOT,
                               Int32Value(PROMISE_IS_HANDLED_STATE_UNHANDLED));
 
         // Store an allocation stack so we can later figure out what the
         // control flow was for some unexpected results. Frightfully expensive,
         // but oh well.
--- a/js/src/builtin/Promise.js
+++ b/js/src/builtin/Promise.js
@@ -142,17 +142,17 @@ function ResolvePromise(promise, valueOr
     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 = UnsafeGetObjectFromReservedSlot(promise, PROMISE_REACTIONS_SLOT);
+    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.
@@ -166,17 +166,18 @@ function ResolvePromise(promise, valueOr
     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.
-    return TriggerPromiseReactions(reactions, jobType, valueOrReason);
+    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.
 // Creates PromiseCapability records, see 25.4.1.1.
 function NewPromiseCapability(C) {
@@ -642,20 +643,21 @@ function BlockOnPromise(promise, blocked
 function AddDependentPromise(dependentPromise) {
     assert(IsPromise(this), "AddDependentPromise expects an unwrapped Promise as the receiver");
 
     let reaction = new PromiseReactionRecord(dependentPromise, NullFunction, NullFunction,
                                              NullFunction, NullFunction, null);
 
     let reactions = UnsafeGetReservedSlot(this, PROMISE_REACTIONS_SLOT);
 
-    // The reactions slot might've been reset because the Promise was resolved.
+    // The reactions list might not have been allocated yet or been reset
+    // because the Promise was resolved.
     if (!reactions) {
-        assert(GetPromiseState(this) !== PROMISE_STATE_PENDING,
-               "Pending promises must have reactions lists.");
+        if (GetPromiseState(this) === PROMISE_STATE_PENDING)
+            UnsafeSetReservedSlot(promise, PROMISE_REACTIONS_SLOT, [reaction]);
         return;
     }
     _DefineDataProperty(reactions, reactions.length, reaction);
 }
 
 // ES6, 25.4.4.4.
 function Promise_static_reject(r) {
     // Step 1.
@@ -895,18 +897,21 @@ function PerformPromiseThen(promise, onF
                                              onRejected,
                                              incumbentGlobal);
 
     // Step 7.
     let state = GetPromiseState(promise);
     if (state === PROMISE_STATE_PENDING) {
         // Steps 7.a,b.
         // We only have a single list for fulfill and reject reactions.
-        let reactions = UnsafeGetObjectFromReservedSlot(promise, PROMISE_REACTIONS_SLOT);
-        _DefineDataProperty(reactions, reactions.length, reaction);
+        let reactions = UnsafeGetReservedSlot(promise, PROMISE_REACTIONS_SLOT);
+        if (!reactions)
+            UnsafeSetReservedSlot(promise, PROMISE_REACTIONS_SLOT, [reaction]);
+        else
+            _DefineDataProperty(reactions, reactions.length, reaction);
     }
 
     // Step 8.
     else if (state === PROMISE_STATE_FULFILLED) {
         // Step 8.a.
         let value = UnsafeGetReservedSlot(promise, PROMISE_RESULT_SLOT);
 
         // Step 8.b.