Changeset 273718 in webkit


Ignore:
Timestamp:
Mar 1, 2021 6:14:51 PM (17 months ago)
Author:
keith_miller@apple.com
Message:

Reduce promise reaction memory usage when there are multiple reactions
https://bugs.webkit.org/show_bug.cgi?id=222533

Reviewed by Yusuke Suzuki.

Previously, we would store each reaction in a linked list. This
meant each reaction required 8 bytes to point to the next
reaction. Instead, this patch makes it so the first reaction is
store inline an object and any additional reactions are store into
index storage for that object. This doesn't save memory for the
first reaction since we now need to have a count of all the out of
line reactions but extra reactions use 8 bytes less each.

  • builtins/BuiltinNames.h:
  • builtins/PromiseOperations.js:

(globalPrivate.pushNewPromiseReaction):
(globalPrivate.triggerPromiseReactions):
(globalPrivate.newPromiseReaction): Deleted.
(globalPrivate.resolvePromise): Deleted.
(globalPrivate.rejectPromise): Deleted.
(globalPrivate.fulfillPromise): Deleted.
(globalPrivate.resolvePromiseWithFirstResolvingFunctionCallCheck): Deleted.
(globalPrivate.fulfillPromiseWithFirstResolvingFunctionCallCheck): Deleted.
(globalPrivate.rejectPromiseWithFirstResolvingFunctionCallCheck): Deleted.
(globalPrivate.createResolvingFunctions): Deleted.
(globalPrivate.promiseReactionJobWithoutPromise): Deleted.
(globalPrivate.resolveWithoutPromise): Deleted.
(globalPrivate.rejectWithoutPromise): Deleted.
(globalPrivate.fulfillWithoutPromise): Deleted.
(globalPrivate.createResolvingFunctionsWithoutPromise): Deleted.
(globalPrivate.promiseReactionJob): Deleted.
(globalPrivate.promiseResolveThenableJobFast): Deleted.
(globalPrivate.promiseResolveThenableJobWithoutPromiseFast): Deleted.
(globalPrivate.promiseResolveThenableJob): Deleted.
(globalPrivate.promiseResolveThenableJobWithDerivedPromise): Deleted.
(onFulfilled): Deleted.
(onRejected): Deleted.
(globalPrivate.performPromiseThen): Deleted.

  • runtime/JSGlobalObject.cpp:

(JSC::JSC_DEFINE_HOST_FUNCTION):

  • runtime/JSMicrotask.cpp:

(JSC::createJSMicrotask):

  • runtime/JSMicrotask.h:
Location:
trunk/Source/JavaScriptCore
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/ChangeLog

    r273717 r273718  
     12021-03-01  Keith Miller  <keith_miller@apple.com>
     2
     3        Reduce promise reaction memory usage when there are multiple reactions
     4        https://bugs.webkit.org/show_bug.cgi?id=222533
     5
     6        Reviewed by Yusuke Suzuki.
     7
     8        Previously, we would store each reaction in a linked list. This
     9        meant each reaction required 8 bytes to point to the next
     10        reaction.  Instead, this patch makes it so the first reaction is
     11        store inline an object and any additional reactions are store into
     12        index storage for that object. This doesn't save memory for the
     13        first reaction since we now need to have a count of all the out of
     14        line reactions but extra reactions use 8 bytes less each.
     15
     16        * builtins/BuiltinNames.h:
     17        * builtins/PromiseOperations.js:
     18        (globalPrivate.pushNewPromiseReaction):
     19        (globalPrivate.triggerPromiseReactions):
     20        (globalPrivate.newPromiseReaction): Deleted.
     21        (globalPrivate.resolvePromise): Deleted.
     22        (globalPrivate.rejectPromise): Deleted.
     23        (globalPrivate.fulfillPromise): Deleted.
     24        (globalPrivate.resolvePromiseWithFirstResolvingFunctionCallCheck): Deleted.
     25        (globalPrivate.fulfillPromiseWithFirstResolvingFunctionCallCheck): Deleted.
     26        (globalPrivate.rejectPromiseWithFirstResolvingFunctionCallCheck): Deleted.
     27        (globalPrivate.createResolvingFunctions): Deleted.
     28        (globalPrivate.promiseReactionJobWithoutPromise): Deleted.
     29        (globalPrivate.resolveWithoutPromise): Deleted.
     30        (globalPrivate.rejectWithoutPromise): Deleted.
     31        (globalPrivate.fulfillWithoutPromise): Deleted.
     32        (globalPrivate.createResolvingFunctionsWithoutPromise): Deleted.
     33        (globalPrivate.promiseReactionJob): Deleted.
     34        (globalPrivate.promiseResolveThenableJobFast): Deleted.
     35        (globalPrivate.promiseResolveThenableJobWithoutPromiseFast): Deleted.
     36        (globalPrivate.promiseResolveThenableJob): Deleted.
     37        (globalPrivate.promiseResolveThenableJobWithDerivedPromise): Deleted.
     38        (onFulfilled): Deleted.
     39        (onRejected): Deleted.
     40        (globalPrivate.performPromiseThen): Deleted.
     41        * runtime/JSGlobalObject.cpp:
     42        (JSC::JSC_DEFINE_HOST_FUNCTION):
     43        * runtime/JSMicrotask.cpp:
     44        (JSC::createJSMicrotask):
     45        * runtime/JSMicrotask.h:
     46
    1472021-03-01  Yusuke Suzuki  <ysuzuki@apple.com>
    248
  • trunk/Source/JavaScriptCore/builtins/BuiltinNames.h

    r273107 r273718  
    178178    macro(hasOwnPropertyFunction) \
    179179    macro(createPrivateSymbol) \
    180     macro(entries)
     180    macro(entries) \
     181    macro(outOfLineReactionCounts)
    181182
    182183
  • trunk/Source/JavaScriptCore/builtins/PromiseOperations.js

    r273605 r273718  
    2828
    2929@globalPrivate
    30 function newPromiseReaction(promiseOrCapability, onFulfilled, onRejected)
    31 {
    32     "use strict";
    33 
    34     return {
    35         @promiseOrCapability: promiseOrCapability,
    36         @onFulfilled: onFulfilled,
    37         @onRejected: onRejected,
    38         @next: @undefined,
    39     };
     30function pushNewPromiseReaction(thenable, existingReactions, promiseOrCapability, onFulfilled, onRejected)
     31{
     32    "use strict";
     33
     34    if (!existingReactions) {
     35        existingReactions = {
     36            @promiseOrCapability: promiseOrCapability,
     37            @onFulfilled: onFulfilled,
     38            @onRejected: onRejected,
     39            // This is 3x then number of out of line reactions (promise, fulfill callback, reject callback).
     40            @outOfLineReactionCounts: 0,
     41        };
     42        @putPromiseInternalField(thenable, @promiseFieldReactionsOrResult, existingReactions);
     43    } else {
     44        var outOfLineReactionCounts = existingReactions.@outOfLineReactionCounts;
     45        @putByValDirect(existingReactions, outOfLineReactionCounts++, promiseOrCapability);
     46        @putByValDirect(existingReactions, outOfLineReactionCounts++, onFulfilled);
     47        @putByValDirect(existingReactions, outOfLineReactionCounts++, onRejected);
     48        existingReactions.@outOfLineReactionCounts = outOfLineReactionCounts;
     49    }
    4050}
    4151
     
    138148    "use strict";
    139149
    140     // Reverse the order of singly-linked-list.
    141     var previous = @undefined;
    142     var current = reactions;
    143     while (current) {
    144         var next = current.@next;
    145         current.@next = previous;
    146         previous = current;
    147         current = next;
    148     }
    149     reactions = previous;
    150 
    151     current = reactions;
    152     while (current) {
    153         @enqueueJob(@promiseReactionJob, state, current, argument);
    154         current = current.@next;
    155     }
     150    if (!reactions)
     151        return;
     152
     153    var isResolved = state === @promiseStateFulfilled;
     154
     155    @enqueueJob(@promiseReactionJob, state, reactions.@promiseOrCapability, isResolved ? reactions.@onFulfilled : reactions.@onRejected, argument);
     156
     157    for (var i = 0, count = reactions.@outOfLineReactionCounts; i < count; i += 3) {
     158        var promise = reactions[i];
     159        var handler = isResolved ? reactions[i + 1] : reactions[i + 2];
     160        @enqueueJob(@promiseReactionJob, state, promise, handler, argument);
     161    }
     162    @assert(i === count);
    156163}
    157164
     
    373380
    374381@globalPrivate
    375 function promiseReactionJob(state, reaction, argument)
     382function promiseReactionJob(state, promiseOrCapability, handler, argument)
    376383{
    377384    // Promise Reaction has four types.
     
    386393    "use strict";
    387394
    388     var promiseOrCapability = reaction.@promiseOrCapability;
    389 
    390395    // Case (3).
    391     if (@isUndefinedOrNull(reaction.@onRejected)) {
    392         @assert(@isUndefinedOrNull(reaction.@onFulfilled));
     396    if (@isUndefinedOrNull(handler)) {
    393397        try {
    394398            @assert(@isPromise(promiseOrCapability));
     
    402406        return;
    403407    }
    404 
    405     var handler = (state === @promiseStateFulfilled) ? reaction.@onFulfilled: reaction.@onRejected;
    406408
    407409    // Case (4).
     
    449451    var flags = @getPromiseInternalField(thenable, @promiseFieldFlags);
    450452    var state = flags & @promiseStateMask;
    451     var reaction = @newPromiseReaction(promiseToResolve, @undefined, @undefined);
    452     if (state === @promiseStatePending) {
    453         reaction.@next = @getPromiseInternalField(thenable, @promiseFieldReactionsOrResult);
    454         @putPromiseInternalField(thenable, @promiseFieldReactionsOrResult, reaction);
    455     } else {
     453    var reactionsOrResult = @getPromiseInternalField(thenable, @promiseFieldReactionsOrResult);
     454    if (state === @promiseStatePending)
     455        @pushNewPromiseReaction(thenable, reactionsOrResult, promiseToResolve, @undefined, @undefined);
     456    else {
    456457        if (state === @promiseStateRejected && !(flags & @promiseFlagsIsHandled))
    457458            @hostPromiseRejectionTracker(thenable, @promiseRejectionHandle);
    458         @enqueueJob(@promiseReactionJob, state, reaction, @getPromiseInternalField(thenable, @promiseFieldReactionsOrResult));
     459        @enqueueJob(@promiseReactionJob, state, promiseToResolve, @undefined, reactionsOrResult);
    459460    }
    460461    @putPromiseInternalField(thenable, @promiseFieldFlags, @getPromiseInternalField(thenable, @promiseFieldFlags) | @promiseFlagsIsHandled);
     
    478479    var flags = @getPromiseInternalField(thenable, @promiseFieldFlags);
    479480    var state = flags & @promiseStateMask;
    480     if (state === @promiseStatePending) {
    481         var reaction = @newPromiseReaction(@undefined, onFulfilled, onRejected);
    482         reaction.@next = @getPromiseInternalField(thenable, @promiseFieldReactionsOrResult);
    483         @putPromiseInternalField(thenable, @promiseFieldReactionsOrResult, reaction);
    484     } else {
    485         var result = @getPromiseInternalField(thenable, @promiseFieldReactionsOrResult);
     481    var reactionsOrResult = @getPromiseInternalField(thenable, @promiseFieldReactionsOrResult);
     482    if (state === @promiseStatePending)
     483        @pushNewPromiseReaction(thenable, reactionsOrResult, @undefined, onFulfilled, onRejected);
     484    else {
    486485        if (state === @promiseStateRejected) {
    487486            if (!(flags & @promiseFlagsIsHandled))
    488487                @hostPromiseRejectionTracker(thenable, @promiseRejectionHandle);
    489             @rejectWithoutPromise(result, onFulfilled, onRejected);
     488            @rejectWithoutPromise(reactionsOrResult, onFulfilled, onRejected);
    490489        } else
    491             @fulfillWithoutPromise(result, onFulfilled, onRejected);
     490            @fulfillWithoutPromise(reactionsOrResult, onFulfilled, onRejected);
    492491    }
    493492    @putPromiseInternalField(thenable, @promiseFieldFlags, @getPromiseInternalField(thenable, @promiseFieldFlags) | @promiseFlagsIsHandled);
     
    547546        onRejected = @promiseEmptyOnRejected;
    548547
    549     var reaction = @newPromiseReaction(promiseOrCapability, onFulfilled, onRejected);
    550 
     548    var reactionsOrResult = @getPromiseInternalField(promise, @promiseFieldReactionsOrResult);
    551549    var flags = @getPromiseInternalField(promise, @promiseFieldFlags);
    552550    var state = flags & @promiseStateMask;
    553     if (state === @promiseStatePending) {
    554         reaction.@next = @getPromiseInternalField(promise, @promiseFieldReactionsOrResult);
    555         @putPromiseInternalField(promise, @promiseFieldReactionsOrResult, reaction);
    556     } else {
    557         if (state === @promiseStateRejected && !(flags & @promiseFlagsIsHandled))
    558             @hostPromiseRejectionTracker(promise, @promiseRejectionHandle);
    559         @enqueueJob(@promiseReactionJob, state, reaction, @getPromiseInternalField(promise, @promiseFieldReactionsOrResult));
     551    if (state === @promiseStatePending)
     552        @pushNewPromiseReaction(promise, reactionsOrResult, promiseOrCapability, onFulfilled, onRejected);
     553    else {
     554        var handler;
     555
     556        if (state === @promiseStateRejected) {
     557            handler = onRejected;
     558            if (!(flags & @promiseFlagsIsHandled))
     559                @hostPromiseRejectionTracker(promise, @promiseRejectionHandle);
     560        } else
     561            handler = onFulfilled;
     562        @enqueueJob(@promiseReactionJob, state, promiseOrCapability, handler, reactionsOrResult);
    560563    }
    561564    @putPromiseInternalField(promise, @promiseFieldFlags, @getPromiseInternalField(promise, @promiseFieldFlags) | @promiseFlagsIsHandled);
  • trunk/Source/JavaScriptCore/runtime/JSGlobalObject.cpp

    r273138 r273718  
    482482    JSValue argument1 = callFrame->argument(2);
    483483    JSValue argument2 = callFrame->argument(3);
    484 
    485     globalObject->queueMicrotask(createJSMicrotask(vm, job, argument0, argument1, argument2));
    486 
    487     return JSValue::encode(jsUndefined());
     484    JSValue argument3 = callFrame->argument(4);
     485
     486    globalObject->queueMicrotask(createJSMicrotask(vm, job, argument0, argument1, argument2, argument3));
     487
     488    return encodedJSUndefined();
    488489}
    489490
  • trunk/Source/JavaScriptCore/runtime/JSMicrotask.cpp

    r266074 r273718  
    3838class JSMicrotask final : public Microtask {
    3939public:
    40     static constexpr unsigned maxArguments = 3;
    41     JSMicrotask(VM& vm, JSValue job, JSValue argument0, JSValue argument1, JSValue argument2)
     40    static constexpr unsigned maxArguments = 4;
     41    JSMicrotask(VM& vm, JSValue job, JSValue argument0, JSValue argument1, JSValue argument2, JSValue argument3)
    4242    {
    4343        m_job.set(vm, job);
     
    4545        m_arguments[1].set(vm, argument1);
    4646        m_arguments[2].set(vm, argument2);
     47        m_arguments[3].set(vm, argument3);
    4748    }
    4849
     
    6465}
    6566
    66 Ref<Microtask> createJSMicrotask(VM& vm, JSValue job, JSValue argument0, JSValue argument1, JSValue argument2)
     67Ref<Microtask> createJSMicrotask(VM& vm, JSValue job, JSValue argument0, JSValue argument1, JSValue argument2, JSValue argument3)
    6768{
    68     return adoptRef(*new JSMicrotask(vm, job, argument0, argument1, argument2));
     69    return adoptRef(*new JSMicrotask(vm, job, argument0, argument1, argument2, argument3));
    6970}
    7071
  • trunk/Source/JavaScriptCore/runtime/JSMicrotask.h

    r249509 r273718  
    3535
    3636JS_EXPORT_PRIVATE Ref<Microtask> createJSMicrotask(VM&, JSValue job);
    37 JS_EXPORT_PRIVATE Ref<Microtask> createJSMicrotask(VM&, JSValue job, JSValue, JSValue, JSValue);
     37JS_EXPORT_PRIVATE Ref<Microtask> createJSMicrotask(VM&, JSValue job, JSValue, JSValue, JSValue, JSValue);
    3838
    3939} // namespace JSC
Note: See TracChangeset for help on using the changeset viewer.