Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement fast path for Promise then function call #1061

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 59 additions & 15 deletions src/builtins/BuiltinPromise.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,16 @@ static Value builtinPromiseAll(ExecutionState& state, Value thisValue, size_t ar
*remainingElementsCount = *remainingElementsCount + 1;
// Perform ? Invoke(nextPromise, "then", « resolveElement, resultCapability.[[Reject]] »).
Object* nextPromiseObject = nextPromise.toObject(state);
Value argv[] = { Value(resolveElement), Value(promiseCapability.m_rejectFunction) };
Object::call(state, nextPromiseObject->get(state, strings->then).value(state, nextPromiseObject), nextPromiseObject, 2, argv);
Value then = nextPromiseObject->get(state, strings->then).value(state, nextPromiseObject);

if (LIKELY(nextPromiseObject->isPromiseObject() && then.isObject() && then.asObject() == state.context()->globalObject()->promiseThen())) {
// fast path for then call
nextPromiseObject->asPromiseObject()->then(state, resolveElement, promiseCapability.m_rejectFunction);
} else {
Value argv[] = { Value(resolveElement), Value(promiseCapability.m_rejectFunction) };
Object::call(state, then, nextPromiseObject, 2, argv);
}

// Increase index by 1.
index++;
}
Expand Down Expand Up @@ -312,8 +320,15 @@ static Value builtinPromiseRace(ExecutionState& state, Value thisValue, size_t a
Value nextPromise = Object::call(state, promiseResolve, C, 1, &nextValue);
// Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], resultCapability.[[Reject]] »).
Object* nextPromiseObject = nextPromise.toObject(state);
Value argv[] = { Value(promiseCapability.m_resolveFunction), Value(promiseCapability.m_rejectFunction) };
Object::call(state, nextPromiseObject->get(state, strings->then).value(state, nextPromiseObject), nextPromiseObject, 2, argv);
Value then = nextPromiseObject->get(state, strings->then).value(state, nextPromiseObject);

if (LIKELY(nextPromiseObject->isPromiseObject() && then.isObject() && then.asObject() == state.context()->globalObject()->promiseThen())) {
// fast path for then call
nextPromiseObject->asPromiseObject()->then(state, promiseCapability.m_resolveFunction, promiseCapability.m_rejectFunction);
} else {
Value argv[] = { Value(promiseCapability.m_resolveFunction), Value(promiseCapability.m_rejectFunction) };
Object::call(state, then, nextPromiseObject, 2, argv);
}
}
} catch (const Value& e) {
Value exceptionValue = e;
Expand Down Expand Up @@ -368,6 +383,12 @@ static Value builtinPromiseCatch(ExecutionState& state, Value thisValue, size_t
Object* thisObject = thisValue.toObject(state);
Value onRejected = argv[0];
Value then = thisObject->get(state, strings->then).value(state, thisObject);

if (LIKELY(thisObject->isPromiseObject() && then.isObject() && then.asObject() == state.context()->globalObject()->promiseThen())) {
// fast path for then call
return thisObject->asPromiseObject()->then(state, Value(), onRejected);
}

Value arguments[] = { Value(), onRejected };
return Object::call(state, then, thisObject, 2, arguments);
}
Expand Down Expand Up @@ -402,17 +423,23 @@ static Value builtinPromiseFinally(ExecutionState& state, Value thisValue, size_
}

Value then = thisObject->get(state, strings->then).value(state, thisObject);

if (LIKELY(thisObject->isPromiseObject() && then.isObject() && then.asObject() == state.context()->globalObject()->promiseThen())) {
// fast path for then call
return thisObject->asPromiseObject()->then(state, arguments[0], arguments[1]);
}

return Object::call(state, then, thisObject, 2, arguments);
}

static Value builtinPromiseThen(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
{
auto strings = &state.context()->staticStrings();
if (!thisValue.isObject() || !thisValue.asObject()->isPromiseObject())
if (!thisValue.isObject() || !thisValue.asObject()->isPromiseObject()) {
ErrorObject::throwBuiltinError(state, ErrorObject::TypeError, strings->Promise.string(), false, strings->then.string(), "%s: not a Promise object");
Value C = thisValue.asObject()->speciesConstructor(state, state.context()->globalObject()->promise());
PromiseReaction::Capability promiseCapability = PromiseObject::newPromiseCapability(state, C.asObject(), thisValue.asObject()->asPromiseObject());
return thisValue.asObject()->asPromiseObject()->then(state, argv[0], argv[1], promiseCapability).value();
}

return thisValue.asObject()->asPromiseObject()->then(state, argv[0], argv[1]);
}

// https://tc39.es/ecma262/#sec-performpromiseallsettled
Expand Down Expand Up @@ -507,9 +534,17 @@ static Value performPromiseAllSettled(ExecutionState& state, IteratorRecord* ite

// Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] + 1.
*remainingElementsCount = *remainingElementsCount + 1;

// Perform ? Invoke(nextPromise, "then", « resolveElement, rejectElement »).
Value argv[2] = { resolveElement, rejectElement };
Object::call(state, Object::getMethod(state, nextPromise, state.context()->staticStrings().then), nextPromise, 2, argv);
Value then = Object::getMethod(state, nextPromise, state.context()->staticStrings().then);
if (nextPromise.isObject() && nextPromise.asObject()->isPromiseObject() && then.isObject() && then.asObject() == state.context()->globalObject()->promiseThen()) {
// fast path for then call
nextPromise.asObject()->asPromiseObject()->then(state, resolveElement, rejectElement);
} else {
Value argv[2] = { resolveElement, rejectElement };
Object::call(state, then, nextPromise, 2, argv);
}

// Set index to index + 1.
index++;
}
Expand Down Expand Up @@ -668,8 +703,16 @@ static Value performPromiseAny(ExecutionState& state, IteratorRecord* iteratorRe

// Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], rejectElement »).
Object* nextPromiseObject = nextPromise.toObject(state);
Value argv2[] = { Value(resultCapability.m_resolveFunction), Value(rejectElement) };
Object::call(state, nextPromiseObject->get(state, strings->then).value(state, nextPromiseObject), nextPromiseObject, 2, argv2);
Value then = nextPromiseObject->get(state, strings->then).value(state, nextPromiseObject);

if (LIKELY(nextPromiseObject->isPromiseObject() && then.isObject() && then.asObject() == state.context()->globalObject()->promiseThen())) {
// fast path for then call
nextPromiseObject->asPromiseObject()->then(state, resultCapability.m_resolveFunction, rejectElement);
} else {
Value argv2[] = { Value(resultCapability.m_resolveFunction), Value(rejectElement) };
Object::call(state, then, nextPromiseObject, 2, argv2);
}

// Set index to index + 1.
index++;
}
Expand Down Expand Up @@ -771,8 +814,6 @@ void GlobalObject::installPromise(ExecutionState& state)
m_promisePrototype->setGlobalIntrinsicObject(state, true);

m_promisePrototype->defineOwnProperty(state, ObjectPropertyName(strings->constructor), ObjectPropertyDescriptor(m_promise, (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
m_promisePrototype->defineOwnPropertyThrowsException(state, ObjectPropertyName(state.context()->vmInstance()->globalSymbols().toStringTag),
ObjectPropertyDescriptor(Value(state.context()->staticStrings().Promise.string()), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::ConfigurablePresent)));

// $25.4.4.1 Promise.all(iterable);
m_promise->defineOwnPropertyThrowsException(state, ObjectPropertyName(strings->all),
Expand All @@ -794,10 +835,13 @@ void GlobalObject::installPromise(ExecutionState& state)
m_promisePrototype->defineOwnPropertyThrowsException(state, ObjectPropertyName(strings->stringCatch),
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(strings->stringCatch, builtinPromiseCatch, 1, NativeFunctionInfo::Strict)),
(ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));

// $25.4.5.3 Promise.prototype.then(onFulfilled, onRejected)
m_promiseThen = new NativeFunctionObject(state, NativeFunctionInfo(strings->then, builtinPromiseThen, 2, NativeFunctionInfo::Strict));
m_promisePrototype->defineOwnPropertyThrowsException(state, ObjectPropertyName(strings->then),
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(strings->then, builtinPromiseThen, 2, NativeFunctionInfo::Strict)),
ObjectPropertyDescriptor(m_promiseThen,
(ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));

// $25.6.5.3 Promise.prototype.finally ( onFinally )
m_promisePrototype->defineOwnPropertyThrowsException(state, ObjectPropertyName(strings->finally),
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(strings->finally, builtinPromiseFinally, 1, NativeFunctionInfo::Strict)),
Expand Down
2 changes: 1 addition & 1 deletion src/interpreter/ByteCodeInterpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3517,7 +3517,7 @@ NEVER_INLINE void ByteCodeInterpreter::callFunctionComplexCase(ExecutionState& s
ExtendedNativeFunctionObject* onRejected = new ExtendedNativeFunctionObjectImpl<1>(state, NativeFunctionInfo(AtomicString(), callDynamicImportRejected, 1));
onRejected->setInternalSlotAsPointer(0, promiseCapability.m_promise);
// Perform ! PerformPromiseThen(capability.[[Promise]], onFulfilled, onRejected).
innerPromiseCapability.m_promise->asPromiseObject()->then(state, onFulfilled, onRejected);
innerPromiseCapability.m_promise->asPromiseObject()->then(state, onFulfilled, onRejected, PromiseReaction::Capability());

Global::platform()->hostImportModuleDynamically(byteCodeBlock->m_codeBlock->context(),
referencingScriptOrModule, specifierString, innerPromiseCapability.m_promise->asPromiseObject());
Expand Down
2 changes: 1 addition & 1 deletion src/parser/Script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1252,7 +1252,7 @@ void Script::moduleExecuteAsyncModule(ExecutionState& state)
ExtendedNativeFunctionObject* onRejected = new ExtendedNativeFunctionObjectImpl<1>(state, NativeFunctionInfo(AtomicString(), asyncModuleRejectedFunction, 1));
onRejected->setInternalSlotAsPointer(0, this);
// Perform ! PerformPromiseThen(capability.[[Promise]], onFulfilled, onRejected).
capability.m_promise->asPromiseObject()->then(state, onFulfilled, onRejected);
capability.m_promise->asPromiseObject()->then(state, onFulfilled, onRejected, PromiseReaction::Capability());

// Perform ! module.ExecuteModule(capability).
moduleExecute(state, capability);
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/AsyncGeneratorObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ Value AsyncGeneratorObject::asyncGeneratorResumeNext(ExecutionState& state, Asyn
ExtendedNativeFunctionObject* onRejected = new ExtendedNativeFunctionObjectImpl<1>(state, NativeFunctionInfo(AtomicString(), asyncGeneratorResumeNextReturnProcessorRejectedFunction, 1));
onRejected->setInternalSlot(AsyncGeneratorObject::BuiltinFunctionSlot::Generator, generator);
// Perform ! PerformPromiseThen(promise, onFulfilled, onRejected).
promise->then(state, onFulfilled, onRejected);
promise->then(state, onFulfilled, onRejected, PromiseReaction::Capability());
// Return undefined.
return Value();
} else {
Expand Down
3 changes: 2 additions & 1 deletion src/runtime/GlobalObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ class FunctionObject;

#define GLOBALOBJECT_BUILTIN_PROMISE(F, objName) \
F(promise, FunctionObject, objName) \
F(promisePrototype, Object, objName)
F(promisePrototype, Object, objName) \
F(promiseThen, FunctionObject, objName)
#define GLOBALOBJECT_BUILTIN_PROXY(F, objName) \
F(proxy, FunctionObject, objName)
#define GLOBALOBJECT_BUILTIN_REFLECT(F, objName) \
Expand Down
5 changes: 5 additions & 0 deletions src/runtime/Job.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ SandBox::SandBoxResult PromiseResolveThenableJob::run()

SandBox sb(state.context());
auto res = sb.run([&]() -> Value {
if (LIKELY(m_thenable->isPromiseObject() && m_then == state.context()->globalObject()->promiseThen())) {
// fast path for then call
return m_thenable->asPromiseObject()->then(state, capability.m_resolveFunction, capability.m_rejectFunction);
}

Value arguments[] = { capability.m_resolveFunction, capability.m_rejectFunction };
return Object::call(state, m_then, m_thenable, 2, arguments);
});
Expand Down
6 changes: 3 additions & 3 deletions src/runtime/Object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1993,14 +1993,14 @@ ValueVectorWithInlineStorage Object::enumerableOwnProperties(ExecutionState& sta

Value Object::speciesConstructor(ExecutionState& state, const Value& defaultConstructor)
{
ASSERT(isObject());
ASSERT(isObject() & defaultConstructor.isObject());
Value C = asObject()->get(state, state.context()->staticStrings().constructor).value(state, this);

if (C.isUndefined()) {
if (UNLIKELY(C.isUndefined())) {
return defaultConstructor;
}

if (!C.isObject()) {
if (UNLIKELY(!C.isObject())) {
ErrorObject::throwBuiltinError(state, ErrorObject::TypeError, "constructor is not an object");
}

Expand Down
15 changes: 7 additions & 8 deletions src/runtime/PromiseObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,18 +174,21 @@ Object* PromiseObject::then(ExecutionState& state, Value handler)
return then(state, handler, Value(), newPromiseResultCapability(state)).value();
}

Object* PromiseObject::then(ExecutionState& state, Value onFulfilled, Value onRejected)
{
return then(state, onFulfilled, onRejected, newPromiseResultCapability(state)).value();
}

Object* PromiseObject::catchOperation(ExecutionState& state, Value handler)
{
return then(state, Value(), handler, newPromiseResultCapability(state)).value();
}

Optional<Object*> PromiseObject::then(ExecutionState& state, Value onFulfilledValue, Value onRejectedValue, Optional<PromiseReaction::Capability> resultCapability)
Optional<Object*> PromiseObject::then(ExecutionState& state, Value onFulfilledValue, Value onRejectedValue, PromiseReaction::Capability capability)
{
Object* onFulfilled = onFulfilledValue.isCallable() ? onFulfilledValue.asObject() : (Object*)(1);
Object* onRejected = onRejectedValue.isCallable() ? onRejectedValue.asObject() : (Object*)(2);

PromiseReaction::Capability capability = resultCapability.hasValue() ? resultCapability.value() : PromiseReaction::Capability(nullptr, nullptr, nullptr);

#ifdef ESCARGOT_DEBUGGER
if (state.context()->debugger() != nullptr) {
capability.m_savedStackTrace = Debugger::saveStackTrace(state);
Expand All @@ -211,11 +214,7 @@ Optional<Object*> PromiseObject::then(ExecutionState& state, Value onFulfilledVa
break;
}

if (resultCapability) {
return capability.m_promise;
} else {
return nullptr;
}
return capability.m_promise;
}

void PromiseObject::triggerPromiseReactions(ExecutionState& state, PromiseObject::Reactions& reactions)
Expand Down
3 changes: 2 additions & 1 deletion src/runtime/PromiseObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,11 @@ class PromiseObject : public Object {
}

Object* then(ExecutionState& state, Value handler);
Object* then(ExecutionState& state, Value onFulfilled, Value onRejected);
Object* catchOperation(ExecutionState& state, Value handler);
// http://www.ecma-international.org/ecma-262/10.0/#sec-performpromisethen
// You can get return value when you give resultCapability
Optional<Object*> then(ExecutionState& state, Value onFulfilled, Value onRejected, Optional<PromiseReaction::Capability> resultCapability = Optional<PromiseReaction::Capability>());
Optional<Object*> then(ExecutionState& state, Value onFulfilled, Value onRejected, PromiseReaction::Capability capability);

void* operator new(size_t size);
void* operator new[](size_t size) = delete;
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/ScriptAsyncFunctionObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ PromiseObject* ScriptAsyncFunctionObject::awaitOperationBeforePause(ExecutionSta
FunctionObject* onRejected = new ScriptAsyncFunctionHelperFunctionObject(state, NativeFunctionInfo(AtomicString(), awaitRejectedFunction, 1), executionPauser, source);

// Perform ! PerformPromiseThen(promise, onFulfilled, onRejected).
promise->then(state, onFulfilled, onRejected, Optional<PromiseReaction::Capability>());
promise->then(state, onFulfilled, onRejected, PromiseReaction::Capability());

return promise;
}
Expand Down