Skip to content
Permalink
Browse files
[JSC] Optimize async/await and microtask queue part 1
https://bugs.webkit.org/show_bug.cgi?id=244165

Reviewed by Ross Kirsling.

This patch improves our async await performance, including microtask queue implementation in JSC side.

1. Promise reaction now delivers |context| value additionally. It is passed as a second argument to the
fulfill / reject handlers if it exists. In async function code, we need to have generator reference in
the promise handlers, and we end up allocating a closure for them capturing |generator|. But (1) this
kind of case is common and (2) allocating closure is costly. In this patch, we additionally pass context
parameter so that handlers can get |generator| tied to this handler registration. This removes closure
allocations and improve async / await performance. Currently this context parameter is usable only when
using resolveWithoutPromise APIs. Keep in mind that we could attempt to optimize the current implementation
by making promiseOrCapability field to promiseOrCapabilityOrContext. So if users would like to use it,
not passing a promise as a context for the future refactoring, it could be broken.

2. We found that MicrotaskQueue can get *super* large. And current heap-allocated JSMicrotask and Strong<> implementation
is too costly for these cases. In this patch, we redesign MicrotaskQueue in JSC VM: QueuedTask holds JSValues directly,
and JSC GC scans this queue to keep them alive. We also make QueuedTask non heap-allocated structure so MicrotaskQueue
enqueue/dequeue and scanning (for GC) is super fast. We also introduce optimization to reduce scanning cost in GC by
maintaining scanning cursor so that we do not take much time in GC scanning. Currently these optimization is only applied
to JSC VM's MicrotaskQueue: WebCore has different implementation, so this is not applied to WebContent use case.
But we first would like to apply it to JavaScriptCore.framework use case, and then, we will apply this optimization / redesign
current WebCore MicrotaskQueue mechanism to be faster one later.

This offers 4% improvement in JetStream2/async-fs since it is using async / await.

* LayoutTests/inspector/canvas/recording-bitmaprenderer-frameCount-expected.txt:
* LayoutTests/inspector/canvas/recording-bitmaprenderer-full-expected.txt:
* LayoutTests/inspector/canvas/recording-bitmaprenderer-memoryLimit-expected.txt:
* Source/JavaScriptCore/builtins/AsyncFromSyncIteratorPrototype.js:
(linkTimeConstant.asyncFromSyncIteratorOnRejected):
(linkTimeConstant.asyncFromSyncIteratorOnFulfilledContinue):
(linkTimeConstant.asyncFromSyncIteratorOnFulfilledDone):
(return):
(throw):
(next.try): Deleted.
(next): Deleted.
(return.try): Deleted.
(throw.try): Deleted.
* Source/JavaScriptCore/builtins/AsyncFunctionPrototype.js:
(linkTimeConstant.asyncFunctionResumeOnFulfilled):
(linkTimeConstant.asyncFunctionResumeOnRejected):
(linkTimeConstant.asyncFunctionResume):
* Source/JavaScriptCore/builtins/AsyncGeneratorPrototype.js:
(linkTimeConstant.asyncGeneratorYieldAwaited):
(linkTimeConstant.asyncGeneratorYieldOnRejected):
(linkTimeConstant.asyncGeneratorYield):
(linkTimeConstant.awaitValue):
(linkTimeConstant.doAsyncGeneratorBodyCallOnFulfilledNormal):
(linkTimeConstant.doAsyncGeneratorBodyCallOnFulfilledReturn):
(linkTimeConstant.doAsyncGeneratorBodyCall):
(linkTimeConstant.asyncGeneratorResumeNextOnFulfilled):
(linkTimeConstant.asyncGeneratorResumeNextOnRejected):
(linkTimeConstant.asyncGeneratorResumeNext):
(asyncGeneratorYieldAwaited): Deleted.
(onRejected): Deleted.
(onFulfilled): Deleted.
* Source/JavaScriptCore/builtins/BuiltinNames.h:
* Source/JavaScriptCore/builtins/InternalPromiseConstructor.js:
(internalAll):
* Source/JavaScriptCore/builtins/PromiseOperations.js:
(linkTimeConstant.pushNewPromiseReaction):
(linkTimeConstant.triggerPromiseReactions):
(linkTimeConstant.promiseReactionJobWithoutPromise):
(linkTimeConstant.resolveWithoutPromise):
(linkTimeConstant.rejectWithoutPromise):
(linkTimeConstant.fulfillWithoutPromise):
(linkTimeConstant.resolveWithoutPromiseForAsyncAwait):
(linkTimeConstant.createResolvingFunctionsWithoutPromise):
(linkTimeConstant.promiseReactionJob):
(linkTimeConstant.promiseResolveThenableJobFast):
(linkTimeConstant.promiseResolveThenableJobWithoutPromiseFast):
(linkTimeConstant.promiseResolveThenableJobWithDerivedPromise):
(linkTimeConstant.performPromiseThen):
* Source/JavaScriptCore/builtins/PromisePrototype.js:
(then):
* Source/JavaScriptCore/bytecode/BytecodeIntrinsicRegistry.cpp:
(JSC::BytecodeIntrinsicRegistry::BytecodeIntrinsicRegistry):
* Source/JavaScriptCore/bytecode/BytecodeIntrinsicRegistry.h:
* Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::BytecodeGenerator):
* Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp:
(JSC::generatorInternalFieldIndex):
(JSC::FunctionNode::emitBytecode):
* Source/JavaScriptCore/debugger/Debugger.cpp:
(JSC::Debugger::didQueueMicrotask):
(JSC::Debugger::willRunMicrotask):
(JSC::Debugger::didRunMicrotask):
* Source/JavaScriptCore/debugger/Debugger.h:
(JSC::Debugger::Observer::didQueueMicrotask):
(JSC::Debugger::Observer::willRunMicrotask):
(JSC::Debugger::Observer::didRunMicrotask):
* Source/JavaScriptCore/heap/Heap.cpp:
(JSC::Heap::beginMarking):
(JSC::Heap::addCoreConstraints):
* Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.cpp:
(Inspector::InspectorDebuggerAgent::asyncCallIdentifier):
(Inspector::InspectorDebuggerAgent::didScheduleAsyncCall):
(Inspector::InspectorDebuggerAgent::didCancelAsyncCall):
(Inspector::InspectorDebuggerAgent::willDispatchAsyncCall):
(Inspector::InspectorDebuggerAgent::didDispatchAsyncCall):
(Inspector::InspectorDebuggerAgent::didQueueMicrotask):
(Inspector::InspectorDebuggerAgent::willRunMicrotask):
(Inspector::InspectorDebuggerAgent::didRunMicrotask):
(Inspector::InspectorDebuggerAgent::didClearAsyncStackTraceData):
* Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.h:
* Source/JavaScriptCore/runtime/JSGenerator.h:
* Source/JavaScriptCore/runtime/JSGlobalObject.cpp:
(JSC::JSC_DEFINE_HOST_FUNCTION):
(JSC::JSGlobalObject::queueMicrotask):
* Source/JavaScriptCore/runtime/JSGlobalObject.h:
* Source/JavaScriptCore/runtime/JSMicrotask.cpp:
(JSC::runJSMicrotask):
(JSC::JSMicrotask::run):
* Source/JavaScriptCore/runtime/JSMicrotask.h:
* Source/JavaScriptCore/runtime/JSPromise.cpp:
(JSC::JSPromise::performPromiseThen):
* Source/JavaScriptCore/runtime/Microtask.h:
(JSC::Microtask::Microtask):
(JSC::Microtask::identifier const):
* Source/JavaScriptCore/runtime/VM.cpp:
(JSC::VM::queueMicrotask):
(JSC::VM::drainMicrotasks):
(JSC::VM::beginMarking):
(JSC::VM::visitAggregateImpl):
(JSC::QueuedTask::run):
(JSC::MicrotaskQueue::visitAggregateImpl):
* Source/JavaScriptCore/runtime/VM.h:
(JSC::QueuedTask::QueuedTask):
(JSC::QueuedTask::identifier const):
(JSC::MicrotaskQueue::dequeue):
(JSC::MicrotaskQueue::enqueue):
(JSC::MicrotaskQueue::isEmpty const):
(JSC::MicrotaskQueue::clear):
(JSC::MicrotaskQueue::beginMarking):
* Source/WTF/wtf/Deque.h:
(WTF::DequeIterator::operator+=):
(WTF::DequeIterator::operator+ const):
(WTF::DequeConstIterator::operator+=):
(WTF::DequeConstIterator::operator+ const):
(WTF::inlineCapacity>::increment):
* Source/WebCore/bindings/js/JSDOMMicrotask.cpp:
(WebCore::JSDOMMicrotask::run):

Canonical link: https://commits.webkit.org/253651@main
  • Loading branch information
Constellation committed Aug 22, 2022
1 parent dfaf825 commit ecb9f873dce9e00649d9f3d5bc668d5fd4e9bcb1
Show file tree
Hide file tree
Showing 30 changed files with 392 additions and 211 deletions.
@@ -19,6 +19,5 @@ frames:
1: (anonymous function)
2: executeFrameFunction
3: (anonymous function)
4: (anonymous function)
snapshot: <filtered>

@@ -19,7 +19,6 @@ frames:
1: (anonymous function)
2: executeFrameFunction
3: (anonymous function)
4: (anonymous function)
snapshot: <filtered>
1: (duration)
0: width
@@ -19,6 +19,5 @@ frames:
1: (anonymous function)
2: executeFrameFunction
3: (anonymous function)
4: (anonymous function)
snapshot: <filtered>

@@ -24,6 +24,30 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

@linkTimeConstant
function asyncFromSyncIteratorOnRejected(error, promise)
{
"use strict";

return @rejectPromiseWithFirstResolvingFunctionCallCheck(promise, error);
}

@linkTimeConstant
function asyncFromSyncIteratorOnFulfilledContinue(result, promise)
{
"use strict";

return @resolvePromiseWithFirstResolvingFunctionCallCheck(promise, { value: result, done: false });
}

@linkTimeConstant
function asyncFromSyncIteratorOnFulfilledDone(result, promise)
{
"use strict";

return @resolvePromiseWithFirstResolvingFunctionCallCheck(promise, { value: result, done: true });
}

function next(value)
{
"use strict";
@@ -40,11 +64,8 @@ function next(value)

try {
var nextResult = @argumentCount() === 0 ? nextMethod.@call(syncIterator) : nextMethod.@call(syncIterator, value);
var nextDone = !!nextResult.done;
var nextValue = nextResult.value;
@resolveWithoutPromiseForAsyncAwait(nextValue,
function (result) { @resolvePromiseWithFirstResolvingFunctionCallCheck(promise, { value: result, done: nextDone }); },
function (error) { @rejectPromiseWithFirstResolvingFunctionCallCheck(promise, error); });
var onFulfilled = nextResult.done ? @asyncFromSyncIteratorOnFulfilledDone : @asyncFromSyncIteratorOnFulfilledContinue;
@resolveWithoutPromiseForAsyncAwait(nextResult.value, onFulfilled, @asyncFromSyncIteratorOnRejected, promise);
} catch (e) {
@rejectPromiseWithFirstResolvingFunctionCallCheck(promise, e);
}
@@ -87,11 +108,8 @@ function return(value)
return promise;
}

var resultDone = !!returnResult.done;
var resultValue = returnResult.value;
@resolveWithoutPromiseForAsyncAwait(resultValue,
function (result) { @resolvePromiseWithFirstResolvingFunctionCallCheck(promise, { value: result, done: resultDone }); },
function (error) { @rejectPromiseWithFirstResolvingFunctionCallCheck(promise, error); });
var onFulfilled = returnResult.done ? @asyncFromSyncIteratorOnFulfilledDone : @asyncFromSyncIteratorOnFulfilledContinue;
@resolveWithoutPromiseForAsyncAwait(returnResult.value, onFulfilled, @asyncFromSyncIteratorOnRejected, promise);
} catch (e) {
@rejectPromiseWithFirstResolvingFunctionCallCheck(promise, e);
}
@@ -134,11 +152,8 @@ function throw(exception)
return promise;
}

var throwDone = !!throwResult.done;
var throwValue = throwResult.value;
@resolveWithoutPromiseForAsyncAwait(throwValue,
function (result) { @resolvePromiseWithFirstResolvingFunctionCallCheck(promise, { value: result, done: throwDone }); },
function (error) { @rejectPromiseWithFirstResolvingFunctionCallCheck(promise, error); });
var onFulfilled = throwResult.done ? @asyncFromSyncIteratorOnFulfilledDone : @asyncFromSyncIteratorOnFulfilledContinue;
@resolveWithoutPromiseForAsyncAwait(throwResult.value, onFulfilled, @asyncFromSyncIteratorOnRejected, promise);
} catch (e) {
@rejectPromiseWithFirstResolvingFunctionCallCheck(promise, e);
}
@@ -25,32 +25,36 @@
*/

@linkTimeConstant
function asyncFunctionResume(generator, promise, sentValue, resumeMode)
function asyncFunctionResumeOnFulfilled(value, generator)
{
"use strict";

@assert(@isPromise(promise));
return @asyncFunctionResume(generator, value, @GeneratorResumeModeNormal);
}

@linkTimeConstant
function asyncFunctionResumeOnRejected(error, generator)
{
"use strict";

return @asyncFunctionResume(generator, error, @GeneratorResumeModeThrow);
}

@linkTimeConstant
function asyncFunctionResume(generator, sentValue, resumeMode)
{
"use strict";

var state = @getGeneratorInternalField(generator, @generatorFieldState);
var value = @undefined;

try {
@putGeneratorInternalField(generator, @generatorFieldState, @GeneratorStateExecuting);
value = @getGeneratorInternalField(generator, @generatorFieldNext).@call(@getGeneratorInternalField(generator, @generatorFieldThis), generator, state, sentValue, resumeMode, @getGeneratorInternalField(generator, @generatorFieldFrame));
if (@getGeneratorInternalField(generator, @generatorFieldState) === @GeneratorStateExecuting) {
@resolvePromiseWithFirstResolvingFunctionCallCheck(promise, value);
return promise;
}
var value = @getGeneratorInternalField(generator, @generatorFieldNext).@call(@getGeneratorInternalField(generator, @generatorFieldThis), generator, state, sentValue, resumeMode, @getGeneratorInternalField(generator, @generatorFieldFrame));
if (@getGeneratorInternalField(generator, @generatorFieldState) === @GeneratorStateExecuting)
return @resolvePromiseWithFirstResolvingFunctionCallCheck(@getGeneratorInternalField(generator, @generatorFieldContext), value);
} catch (error) {
@rejectPromiseWithFirstResolvingFunctionCallCheck(promise, error);
return promise;
return @rejectPromiseWithFirstResolvingFunctionCallCheck(@getGeneratorInternalField(generator, @generatorFieldContext), error);
}

var capturedGenerator = generator;
var capturedPromise = promise;
@resolveWithoutPromiseForAsyncAwait(value,
function(value) { @asyncFunctionResume(capturedGenerator, capturedPromise, value, @GeneratorResumeModeNormal); },
function(error) { @asyncFunctionResume(capturedGenerator, capturedPromise, error, @GeneratorResumeModeThrow); });

return promise;
return @resolveWithoutPromiseForAsyncAwait(value, @asyncFunctionResumeOnFulfilled, @asyncFunctionResumeOnRejected, generator);
}
@@ -120,28 +120,53 @@ function asyncGeneratorResolve(generator, value, done)
}

@linkTimeConstant
function asyncGeneratorYield(generator, value, resumeMode)
function asyncGeneratorYieldAwaited(result, generator)
{
"use strict";

function asyncGeneratorYieldAwaited(result)
{
@putAsyncGeneratorInternalField(generator, @asyncGeneratorFieldSuspendReason, @AsyncGeneratorSuspendReasonYield);
@asyncGeneratorResolve(generator, result, false);
}
@putAsyncGeneratorInternalField(generator, @asyncGeneratorFieldSuspendReason, @AsyncGeneratorSuspendReasonYield);
@asyncGeneratorResolve(generator, result, false);
}

@putAsyncGeneratorInternalField(generator, @asyncGeneratorFieldSuspendReason, @AsyncGeneratorSuspendReasonAwait);
@linkTimeConstant
function asyncGeneratorYieldOnRejected(result, generator)
{
"use strict";

@awaitValue(generator, value, asyncGeneratorYieldAwaited);
@doAsyncGeneratorBodyCall(generator, result, @GeneratorResumeModeThrow);
}

@linkTimeConstant
function asyncGeneratorYield(generator, value, resumeMode)
{
"use strict";

@putAsyncGeneratorInternalField(generator, @asyncGeneratorFieldSuspendReason, @AsyncGeneratorSuspendReasonAwait);
@awaitValue(generator, value, @asyncGeneratorYieldAwaited);
}

@linkTimeConstant
function awaitValue(generator, value, onFulfilled)
{
"use strict";

var onRejected = function (result) { @doAsyncGeneratorBodyCall(generator, result, @GeneratorResumeModeThrow); };
@resolveWithoutPromiseForAsyncAwait(value, onFulfilled, onRejected);
@resolveWithoutPromiseForAsyncAwait(value, onFulfilled, @asyncGeneratorYieldOnRejected, generator);
}

@linkTimeConstant
function doAsyncGeneratorBodyCallOnFulfilledNormal(result, generator)
{
"use strict";

@doAsyncGeneratorBodyCall(generator, result, @GeneratorResumeModeNormal);
}

@linkTimeConstant
function doAsyncGeneratorBodyCallOnFulfilledReturn(result, generator)
{
"use strict";

@doAsyncGeneratorBodyCall(generator, result, @GeneratorResumeModeReturn);
}

@linkTimeConstant
@@ -150,10 +175,8 @@ function doAsyncGeneratorBodyCall(generator, resumeValue, resumeMode)
"use strict";

if (resumeMode === @GeneratorResumeModeReturn && @isSuspendYieldState(generator)) {
var onFulfilled = function(result) { @doAsyncGeneratorBodyCall(generator, result, @GeneratorResumeModeReturn); };

@putAsyncGeneratorInternalField(generator, @asyncGeneratorFieldSuspendReason, @AsyncGeneratorSuspendReasonAwait);
@awaitValue(generator, resumeValue, onFulfilled);
@awaitValue(generator, resumeValue, @doAsyncGeneratorBodyCallOnFulfilledReturn);
return;
}

@@ -179,9 +202,7 @@ function doAsyncGeneratorBodyCall(generator, resumeValue, resumeMode)

var reason = @getAsyncGeneratorInternalField(generator, @asyncGeneratorFieldSuspendReason);
if (reason === @AsyncGeneratorSuspendReasonAwait) {
var onFulfilled = function(result) { @doAsyncGeneratorBodyCall(generator, result, @GeneratorResumeModeNormal); };

@awaitValue(generator, value, onFulfilled);
@awaitValue(generator, value, @doAsyncGeneratorBodyCallOnFulfilledNormal);
return;
}

@@ -195,6 +216,24 @@ function doAsyncGeneratorBodyCall(generator, resumeValue, resumeMode)
}
}

@linkTimeConstant
function asyncGeneratorResumeNextOnFulfilled(result, generator)
{
"use strict";

@putAsyncGeneratorInternalField(generator, @generatorFieldState, @AsyncGeneratorStateCompleted);
@asyncGeneratorResolve(generator, result, true);
}

@linkTimeConstant
function asyncGeneratorResumeNextOnRejected(error, generator)
{
"use strict";

@putAsyncGeneratorInternalField(generator, @generatorFieldState, @AsyncGeneratorStateCompleted);
@asyncGeneratorReject(generator, error);
}

@linkTimeConstant
function asyncGeneratorResumeNext(generator)
{
@@ -223,15 +262,7 @@ function asyncGeneratorResumeNext(generator)
if (state === @AsyncGeneratorStateCompleted) {
if (next.resumeMode === @GeneratorResumeModeReturn) {
@putAsyncGeneratorInternalField(generator, @generatorFieldState, @AsyncGeneratorStateAwaitingReturn);
@resolveWithoutPromiseForAsyncAwait(next.value,
function (result) {
@putAsyncGeneratorInternalField(generator, @generatorFieldState, @AsyncGeneratorStateCompleted);
@asyncGeneratorResolve(generator, result, true);
},
function (error) {
@putAsyncGeneratorInternalField(generator, @generatorFieldState, @AsyncGeneratorStateCompleted);
@asyncGeneratorReject(generator, error);
});
@resolveWithoutPromiseForAsyncAwait(next.value, @asyncGeneratorResumeNextOnFulfilled, @asyncGeneratorResumeNextOnRejected, generator);
return;
}

@@ -93,6 +93,7 @@ namespace JSC {
macro(get) \
macro(set) \
macro(clear) \
macro(context) \
macro(delete) \
macro(size) \
macro(shift) \
@@ -73,7 +73,7 @@ function internalAll(array)
var value = array[index];
@putByValDirect(values, index, @undefined);
++remainingElementsCount;
@resolveWithoutPromise(value, newResolveElement(index), reject);
@resolveWithoutPromise(value, newResolveElement(index), reject, @undefined);
}
}
} catch (error) {

0 comments on commit ecb9f87

Please sign in to comment.