/*
 * Copyright (C) 2013-2021 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "JSPromise.h"

#include "BuiltinNames.h"
#include "DeferredWorkTimer.h"
#include "ErrorInstance.h"
#include "ErrorInstanceInlines.h"
#include "GlobalObjectMethodTable.h"
#include "JSCInlines.h"
#include "JSFunctionWithFields.h"
#include "JSMicrotask.h"
#include "JSPromiseCombinatorsContext.h"
#include "JSPromiseConstructor.h"
#include "JSPromisePrototype.h"
#include "JSPromiseReaction.h"
#include "Microtask.h"
#include "MicrotaskQueueInlines.h"
#include "ObjectConstructor.h"
#include "TopExceptionScope.h"
#include "VMInlines.h"

namespace JSC {

const ClassInfo JSPromise::s_info = { "Promise"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSPromise) };

JSPromise* JSPromise::create(VM& vm, Structure* structure)
{
    JSPromise* promise = new (NotNull, allocateCell<JSPromise>(vm)) JSPromise(vm, structure);
    promise->finishCreation(vm);
    return promise;
}

JSPromise* JSPromise::createWithInitialValues(VM& vm, Structure* structure)
{
    return create(vm, structure);
}

Structure* JSPromise::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
{
    return Structure::create(vm, globalObject, prototype, TypeInfo(JSPromiseType, StructureFlags), info());
}

JSPromise::JSPromise(VM& vm, Structure* structure)
    : Base(vm, structure)
{
}

template<typename Visitor>
void JSPromise::visitChildrenImpl(JSCell* cell, Visitor& visitor)
{
    auto* thisObject = uncheckedDowncast<JSPromise>(cell);
    ASSERT_GC_OBJECT_INHERITS(thisObject, info());
    Base::visitChildren(thisObject, visitor);
    if (JSCell* payload = thisObject->m_packed.pointer())
        visitor.appendUnbarriered(payload);
    visitor.append(thisObject->m_slot);
}

DEFINE_VISIT_CHILDREN(JSPromise);

JSValue JSPromise::createNewPromiseCapability(JSGlobalObject* globalObject, JSValue constructor)
{
    VM& vm = globalObject->vm();
    auto scope = DECLARE_THROW_SCOPE(vm);

    auto [promise, resolve, reject] = newPromiseCapability(globalObject, constructor);
    RETURN_IF_EXCEPTION(scope, { });
    return createPromiseCapability(vm, globalObject, promise, resolve, reject);
}

JSValue JSPromise::createPromiseCapability(VM& vm, JSGlobalObject* globalObject, JSObject* promise, JSObject* resolve, JSObject* reject)
{
    auto* capability = constructEmptyObject(vm, globalObject->promiseCapabilityObjectStructure());
    capability->putDirectOffset(vm, promiseCapabilityResolvePropertyOffset, resolve);
    capability->putDirectOffset(vm, promiseCapabilityRejectPropertyOffset, reject);
    capability->putDirectOffset(vm, promiseCapabilityPromisePropertyOffset, promise);
    return capability;
}

std::tuple<JSObject*, JSObject*, JSObject*> JSPromise::newPromiseCapability(JSGlobalObject* globalObject, JSValue constructor)
{
    VM& vm = globalObject->vm();
    auto scope = DECLARE_THROW_SCOPE(vm);

    if (constructor == globalObject->promiseConstructor()) {
        auto* promise = JSPromise::create(vm, globalObject->promiseStructure());
        auto [resolve, reject] = promise->createFirstResolvingFunctions(vm, globalObject);
        return { promise, resolve, reject };
    }

    auto* executor = JSFunctionWithFields::create(vm, globalObject, vm.promiseCapabilityExecutorExecutable());
    executor->setField(vm, JSFunctionWithFields::Field::ExecutorResolve, jsUndefined());
    executor->setField(vm, JSFunctionWithFields::Field::ExecutorReject, jsUndefined());

    MarkedArgumentBuffer args;
    args.append(executor);
    ASSERT(!args.hasOverflowed());
    JSObject* newObject = construct(globalObject, constructor, args, "argument is not a constructor"_s);
    RETURN_IF_EXCEPTION(scope, { });

    JSValue resolve = executor->getField(JSFunctionWithFields::Field::ExecutorResolve);
    JSValue reject = executor->getField(JSFunctionWithFields::Field::ExecutorReject);
    if (!resolve.isCallable()) [[unlikely]] {
        throwTypeError(globalObject, scope, "executor did not take a resolve function"_s);
        return { };
    }

    if (!reject.isCallable()) [[unlikely]] {
        throwTypeError(globalObject, scope, "executor did not take a reject function"_s);
        return { };
    }

    return { newObject, asObject(resolve), asObject(reject) };
}

JSPromise::DeferredData JSPromise::createDeferredData(JSGlobalObject* globalObject, JSPromiseConstructor* promiseConstructor)
{
    VM& vm = globalObject->vm();
    auto scope = DECLARE_THROW_SCOPE(vm);
    auto [ promiseCapability, resolveCapability, rejectCapability ] = newPromiseCapability(globalObject, promiseConstructor);
    RETURN_IF_EXCEPTION(scope, { });
    auto* promise = dynamicDowncast<JSPromise>(promiseCapability);
    auto* resolve = dynamicDowncast<JSFunction>(resolveCapability);
    auto* reject  = dynamicDowncast<JSFunction>(rejectCapability);
    if (promise && resolve && reject)
        return DeferredData { promise, resolve, reject };

    throwTypeError(globalObject, scope, "constructor is producing a bad value"_s);
    return { };
}

JSPromise* JSPromise::resolvedPromise(JSGlobalObject* globalObject, JSValue value)
{
    return uncheckedDowncast<JSPromise>(promiseResolve(globalObject, globalObject->promiseConstructor(), value));
}

JSPromise* JSPromise::rejectedPromise(JSGlobalObject* globalObject, JSValue value)
{
    VM& vm = globalObject->vm();
    JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure());
    promise->reject(vm, value);
    return promise;
}

void JSPromise::resolve(JSGlobalObject* globalObject, VM& vm, JSValue value)
{
    ASSERT(!value.inherits<Exception>());
    if (!isFirstResolvingFunctionCalled()) {
        setFlags(flags() | isFirstResolvingFunctionCalledFlag);
        resolvePromise(globalObject, vm, value);
    }
}

void JSPromise::reject(VM& vm, JSValue value)
{
    ASSERT(!value.inherits<Exception>());
    if (!isFirstResolvingFunctionCalled()) {
        setFlags(flags() | isFirstResolvingFunctionCalledFlag);
        rejectPromise(vm, value);
    }
}

void JSPromise::fulfill(VM& vm, JSValue value)
{
    ASSERT(!value.inherits<Exception>());
    if (!isFirstResolvingFunctionCalled()) {
        setFlags(flags() | isFirstResolvingFunctionCalledFlag);
        fulfillPromise(vm, value);
    }
}

void JSPromise::pipeFrom(VM& vm, JSPromise* from)
{
    if (isFirstResolvingFunctionCalled())
        return;
    setFlags(flags() | isFirstResolvingFunctionCalledFlag);

    from->performPromiseThenWithInternalMicrotask(vm, InternalMicrotask::PromiseFulfillWithoutHandlerJob, this, jsUndefined());
}

void JSPromise::performPromiseThenExported(VM& vm, JSGlobalObject* globalObject, JSValue onFulfilled, JSValue onRejected, JSValue promiseOrCapability)
{
    return performPromiseThen(vm, globalObject, onFulfilled, onRejected, promiseOrCapability);
}

void JSPromise::rejectAsHandled(VM& vm, JSValue value)
{
    // Setting isHandledFlag before calling reject since this removes round-trip between JSC and PromiseRejectionTracker, and it does not show an user-observable behavior.
    if (!isFirstResolvingFunctionCalled()) {
        markAsHandled();
        reject(vm, value);
    }
}

void JSPromise::reject(VM& vm, Exception* reason)
{
    reject(vm, reason->value());
}

void JSPromise::rejectAsHandled(VM& vm, Exception* reason)
{
    rejectAsHandled(vm, reason->value());
}

JSPromise* JSPromise::rejectWithCaughtException(VM& vm, ThrowScope& scope)
{
    Exception* exception = scope.exception();
    ASSERT(exception);
    TRY_CLEAR_EXCEPTION(scope, nullptr);
    scope.release();
    reject(vm, exception->value());
    return this;
}

void JSPromise::setInlineMicrotaskReaction(VM& vm, InternalMicrotask task, JSCell* cell, JSValue context)
{
    ASSERT(status() == Status::Pending);
    ASSERT(inlineReactionKind() == InlineReactionKind::None);
    ASSERT(!payloadCell());
    ASSERT(task != InternalMicrotask::None);
    // The inline reaction always implies markAsHandled; fold both into one flag update.
    uint16_t newFlags = flags()
        | isHandledFlag
        | (static_cast<uint16_t>(InlineReactionKind::InternalMicrotask) << inlineReactionKindShift)
        | (static_cast<uint16_t>(task) << inlineReactionMicrotaskShift);
    setSlot(vm, context);
    setPackedCell(vm, newFlags, cell);
}

void JSPromise::setInlineHandlerReaction(VM& vm, InlineReactionKind kind, JSPromise* resultPromise, JSValue handler)
{
    ASSERT(status() == Status::Pending);
    ASSERT(inlineReactionKind() == InlineReactionKind::None);
    ASSERT(!payloadCell());
    ASSERT(kind == InlineReactionKind::FulfillHandler || kind == InlineReactionKind::RejectHandler);
    ASSERT(resultPromise);
    uint16_t newFlags = flags()
        | isHandledFlag
        | (static_cast<uint16_t>(kind) << inlineReactionKindShift);
    setSlot(vm, handler);
    setPackedCell(vm, newFlags, resultPromise);
}

JSPromiseReaction* JSPromise::spillInlineReaction(VM& vm)
{
    auto kind = inlineReactionKind();
    ASSERT(kind != InlineReactionKind::None);
    JSSlimPromiseReaction* reaction = nullptr;
    switch (kind) {
    case InlineReactionKind::InternalMicrotask: {
        InternalMicrotask task = inlineReactionMicrotask();
        JSValue context = m_slot.get();
        JSCell* cell = payloadCell();
        reaction = JSSlimPromiseReaction::create(vm, cell ? JSValue(cell) : jsUndefined(), task, context, nullptr);
        break;
    }
    case InlineReactionKind::FulfillHandler:
    case InlineReactionKind::RejectHandler: {
        JSPromise* resultPromise = uncheckedDowncast<JSPromise>(payloadCell());
        JSValue handler = m_slot.get();
        bool isFulfill = kind == InlineReactionKind::FulfillHandler;
        reaction = JSSlimPromiseReaction::create(vm, resultPromise, handler, isFulfill, nullptr);
        break;
    }
    case InlineReactionKind::None:
        RELEASE_ASSERT_NOT_REACHED();
    }
    clearSlot();
    uint16_t newFlags = flags() & ~(inlineReactionKindMask | inlineReactionMicrotaskMask);
    setPackedCell(vm, newFlags, reaction);
    return reaction;
}

JSPromiseReaction* JSPromise::reactionHead(VM& vm)
{
    ASSERT(status() == Status::Pending);
    if (inlineReactionKind() != InlineReactionKind::None) [[unlikely]]
        return spillInlineReaction(vm);
    return uncheckedDowncast<JSPromiseReaction>(payloadCell());
}

JSValue JSPromise::asyncStackTraceContext() const
{
    if (status() != Status::Pending)
        return { };
    switch (inlineReactionKind()) {
    case InlineReactionKind::None: {
        auto* head = uncheckedDowncast<JSPromiseReaction>(payloadCell());
        return head ? JSPromiseReaction::tryGetContext(head) : JSValue();
    }
    case InlineReactionKind::InternalMicrotask: {
        if (promiseReactionPacksGlobalContextAndIndex(inlineReactionMicrotask())) {
            ASSERT(payloadCell());
            return JSValue(payloadCell());
        }
        return m_slot.get();
    }
    case InlineReactionKind::FulfillHandler:
    case InlineReactionKind::RejectHandler:
        return { };
    }
    return { };
}

void JSPromise::performPromiseThen(VM& vm, JSGlobalObject* globalObject, JSValue onFulfilled, JSValue onRejected, JSValue promiseOrCapability)
{
    bool fulfilledCallable = onFulfilled.isCallable();
    bool rejectedCallable = onRejected.isCallable();

    switch (status()) {
    case JSPromise::Status::Pending: {
        bool onlyFulfill = fulfilledCallable && !rejectedCallable;
        bool onlyReject = !fulfilledCallable && rejectedCallable;
        if (inlineReactionKind() == InlineReactionKind::None && !payloadCell()) {
            if ((onlyFulfill || onlyReject) && promiseOrCapability.inherits<JSPromise>()) [[likely]] {
                auto* resultPromise = uncheckedDowncast<JSPromise>(promiseOrCapability);
                setInlineHandlerReaction(vm, onlyFulfill ? InlineReactionKind::FulfillHandler : InlineReactionKind::RejectHandler, resultPromise, onlyFulfill ? onFulfilled : onRejected);
                break;
            }
        }
        JSPromiseReaction* existing = reactionHead(vm);
        JSPromiseReaction* reaction;
        if (onlyFulfill)
            reaction = JSSlimPromiseReaction::create(vm, promiseOrCapability, onFulfilled, true, existing);
        else if (onlyReject)
            reaction = JSSlimPromiseReaction::create(vm, promiseOrCapability, onRejected, false, existing);
        else if (fulfilledCallable) {
            ASSERT(rejectedCallable);
            reaction = JSFullPromiseReaction::create(vm, promiseOrCapability, onFulfilled, onRejected, jsUndefined(), existing);
        } else
            reaction = JSSlimPromiseReaction::create(vm, promiseOrCapability, InternalMicrotask::PromiseResolveWithoutHandlerJob, jsUndefined(), existing);
        setPackedCell(vm, flags() | isHandledFlag, reaction);
        break;
    }
    case JSPromise::Status::Rejected: {
        JSValue settled = settlementValue();
        if (!isHandled())
            globalObject->globalObjectMethodTable()->promiseRejectionTracker(globalObject, this, JSPromiseRejectionOperation::Handle);
        if (rejectedCallable)
            globalObject->queueMicrotask(vm, InternalMicrotask::PromiseReactionJob, static_cast<uint8_t>(Status::Rejected), promiseOrCapability, onRejected, settled);
        else
            globalObject->queueMicrotask(vm, InternalMicrotask::PromiseResolveWithoutHandlerJob, static_cast<uint8_t>(Status::Rejected), promiseOrCapability, settled, jsUndefined());
        markAsHandled();
        break;
    }
    case JSPromise::Status::Fulfilled: {
        JSValue settled = settlementValue();
        if (fulfilledCallable)
            globalObject->queueMicrotask(vm, InternalMicrotask::PromiseReactionJob, static_cast<uint8_t>(Status::Fulfilled), promiseOrCapability, onFulfilled, settled);
        else
            globalObject->queueMicrotask(vm, InternalMicrotask::PromiseResolveWithoutHandlerJob, static_cast<uint8_t>(Status::Fulfilled), promiseOrCapability, settled, jsUndefined());
        break;
    }
    }
}

void JSPromise::performPromiseThenWithInternalMicrotask(VM& vm, InternalMicrotask task, JSCell* cell, JSValue context)
{
    JSValue cellValue = cell ? JSValue(cell) : jsUndefined();
    switch (status()) {
    case JSPromise::Status::Pending: {
        if (inlineReactionKind() == InlineReactionKind::None && !payloadCell()) [[likely]] {
            setInlineMicrotaskReaction(vm, task, cell, context);
            break;
        }
        JSPromiseReaction* existing = reactionHead(vm);
        auto* reaction = JSSlimPromiseReaction::create(vm, cellValue, task, context, existing);
        setPackedCell(vm, flags() | isHandledFlag, reaction);
        break;
    }
    case JSPromise::Status::Rejected: {
        JSGlobalObject* globalObject = realm();
        JSValue settled = settlementValue();
        if (!isHandled())
            globalObject->globalObjectMethodTable()->promiseRejectionTracker(globalObject, this, JSPromiseRejectionOperation::Handle);
        globalObject->queueMicrotask(vm, task, static_cast<uint8_t>(Status::Rejected), cellValue, settled, context);
        markAsHandled();
        break;
    }
    case JSPromise::Status::Fulfilled: {
        JSGlobalObject* globalObject = realm();
        JSValue settled = settlementValue();
        globalObject->queueMicrotask(vm, task, static_cast<uint8_t>(Status::Fulfilled), cellValue, settled, context);
        break;
    }
    }
}

bool isDefinitelyNonThenable(JSObject* object, JSGlobalObject* globalObject)
{
    if (!globalObject->promiseThenWatchpointSet().isStillValid()) [[unlikely]]
        return false;

    auto* structure = object->structure();
    auto state = structure->definitelyNonThenableState();
    if (state == Structure::DefinitelyNonThenableState::NonThenable && structure->realm() == globalObject)
        return true;
    if (state == Structure::DefinitelyNonThenableState::MaybeThenable)
        return false;

    bool result = true;
    auto* current = structure;
    while (current) {
        if (current->hasSpecialProperties()
            || current->typeInfo().getOwnPropertySlotIsImpureForPropertyAbsence()
            || current->typeInfo().overridesGetPrototype()
            || !current->hasMonoProto()) {
            result = false;
            break;
        }
        current = current->storedPrototypeStructure();
    }

    // Dictionary structures are mutated in place when properties are added or removed,
    // so the cached state could become stale (e.g. caching NonThenable, then adding `then`).
    // Give up caching entirely for them; the per-call walk above remains correct because
    // `hasSpecialProperties` is updated in place even for dictionaries.
    if (state == Structure::DefinitelyNonThenableState::NotComputed && !structure->isDictionary()) [[unlikely]] {
        if (!result) {
            // Always safe: a stale `false` only loses the optimization, never miscompiles.
            structure->setDefinitelyNonThenableState(Structure::DefinitelyNonThenableState::MaybeThenable);
        } else {
            // A `true` result is cacheable only when the entire prototype chain stays
            // under the protection of promiseThenWatchpointSet, which watches `then`
            // absence on Object.prototype. That limits the cacheable chain to [self]
            // (null proto) or [self, Object.prototype]. Mark anything else Uncacheable
            // so subsequent calls skip this check and go straight to the walk.
            JSValue proto = structure->storedPrototype();
            if (!proto.isObject() || asObject(proto) == structure->realm()->objectPrototype())
                structure->setDefinitelyNonThenableState(Structure::DefinitelyNonThenableState::NonThenable);
            else
                structure->setDefinitelyNonThenableState(Structure::DefinitelyNonThenableState::Uncacheable);
        }
    }
    return result;
}

ALWAYS_INLINE void JSPromise::settleInlineInternalMicrotask(VM& vm, JSGlobalObject* globalObject, Status newStatus, JSValue argument, uint16_t flagsSnapshot)
{
    ASSERT((flagsSnapshot & inlineReactionKindMask) == (static_cast<uint16_t>(InlineReactionKind::InternalMicrotask) << inlineReactionKindShift));
    ASSERT(flagsSnapshot & isHandledFlag);
    InternalMicrotask task = static_cast<InternalMicrotask>((flagsSnapshot & inlineReactionMicrotaskMask) >> inlineReactionMicrotaskShift);
    JSValue context = m_slot.get();
    JSCell* cell = payloadCell();
    JSValue cellValue = cell ? JSValue(cell) : jsUndefined();
    uint16_t settledFlags = (flagsSnapshot & ~(inlineReactionKindMask | inlineReactionMicrotaskMask)) | static_cast<uint16_t>(newStatus);
    setSlot(vm, argument);
    setPackedCell(vm, settledFlags, nullptr);
    globalObject->queueMicrotask(vm, task, static_cast<uint8_t>(newStatus), cellValue, argument, context);
}

ALWAYS_INLINE void JSPromise::settleInlineHandler(VM& vm, JSGlobalObject* globalObject, Status newStatus, JSValue argument, uint16_t flagsSnapshot)
{
    ASSERT(flagsSnapshot & isHandledFlag);
    InlineReactionKind kind = static_cast<InlineReactionKind>((flagsSnapshot & inlineReactionKindMask) >> inlineReactionKindShift);
    ASSERT(kind == InlineReactionKind::FulfillHandler || kind == InlineReactionKind::RejectHandler);
    bool settledIsFulfilled = newStatus == Status::Fulfilled;
    bool handlerIsFulfill = kind == InlineReactionKind::FulfillHandler;
    JSPromise* resultPromise = uncheckedDowncast<JSPromise>(payloadCell());
    JSValue handler = m_slot.get();
    uint16_t settledFlags = (flagsSnapshot & ~(inlineReactionKindMask | inlineReactionMicrotaskMask)) | static_cast<uint16_t>(newStatus);
    setSlot(vm, argument);
    setPackedCell(vm, settledFlags, nullptr);
    if (settledIsFulfilled == handlerIsFulfill)
        globalObject->queueMicrotask(vm, InternalMicrotask::PromiseReactionJob, static_cast<uint8_t>(newStatus), resultPromise, handler, argument);
    else
        globalObject->queueMicrotask(vm, InternalMicrotask::PromiseResolveWithoutHandlerJob, static_cast<uint8_t>(newStatus), resultPromise, argument, jsUndefined());
}

void JSPromise::rejectPromise(VM& vm, JSValue argument)
{
    ASSERT(status() == Status::Pending);
    JSGlobalObject* globalObject = realm();
    uint16_t currentFlags = flags();
    auto kind = static_cast<InlineReactionKind>((currentFlags & inlineReactionKindMask) >> inlineReactionKindShift);
    switch (kind) {
    case InlineReactionKind::InternalMicrotask:
        return settleInlineInternalMicrotask(vm, globalObject, Status::Rejected, argument, currentFlags);

    case InlineReactionKind::FulfillHandler:
    case InlineReactionKind::RejectHandler:
        return settleInlineHandler(vm, globalObject, Status::Rejected, argument, currentFlags);

    case InlineReactionKind::None: {
        JSPromiseReaction* reactions = uncheckedDowncast<JSPromiseReaction>(payloadCell());
        uint16_t settledFlags = currentFlags | static_cast<uint16_t>(Status::Rejected);
        setSlot(vm, argument);
        setPackedCell(vm, settledFlags, nullptr);

        if (!isHandled())
            globalObject->globalObjectMethodTable()->promiseRejectionTracker(globalObject, this, JSPromiseRejectionOperation::Reject);

        if (!reactions)
            return;
        triggerPromiseReactions(vm, globalObject, Status::Rejected, reactions, argument);
        return;
    }
    }
}

void JSPromise::fulfillPromise(VM& vm, JSValue argument)
{
    ASSERT(status() == Status::Pending);
    JSGlobalObject* globalObject = realm();
    uint16_t currentFlags = flags();
    auto kind = static_cast<InlineReactionKind>((currentFlags & inlineReactionKindMask) >> inlineReactionKindShift);
    switch (kind) {
    case InlineReactionKind::InternalMicrotask:
        return settleInlineInternalMicrotask(vm, globalObject, Status::Fulfilled, argument, currentFlags);

    case InlineReactionKind::FulfillHandler:
    case InlineReactionKind::RejectHandler:
        return settleInlineHandler(vm, globalObject, Status::Fulfilled, argument, currentFlags);

    case InlineReactionKind::None: {
        JSPromiseReaction* reactions = uncheckedDowncast<JSPromiseReaction>(payloadCell());
        uint16_t settledFlags = currentFlags | static_cast<uint16_t>(Status::Fulfilled);
        setSlot(vm, argument);
        setPackedCell(vm, settledFlags, nullptr);

        if (!reactions)
            return;
        triggerPromiseReactions(vm, globalObject, Status::Fulfilled, reactions, argument);
        return;
    }
    }
}

void JSPromise::resolvePromise(JSGlobalObject* globalObject, VM& vm, JSValue resolution)
{
    if (resolution == this) [[unlikely]] {
        Structure* errorStructure = globalObject->errorStructure(ErrorType::TypeError);
        auto* error = ErrorInstance::create(vm, errorStructure, "Cannot resolve a promise with itself"_s, jsUndefined(), nullptr, TypeNothing, ErrorType::TypeError, false);
        return rejectPromise(vm, error);
    }

    if (!resolution.isObject())
        return fulfillPromise(vm, resolution);

    auto* resolutionObject = asObject(resolution);
    if (resolutionObject->inherits<JSPromise>()) {
        auto* promise = uncheckedDowncast<JSPromise>(resolutionObject);
        if (promise->isThenFastAndNonObservable())
            return promise->realm()->queueMicrotask(vm, InternalMicrotask::PromiseResolveThenableJobFast, 0, resolutionObject, this, jsUndefined());
    }

    if (isDefinitelyNonThenable(resolutionObject, globalObject))
        return fulfillPromise(vm, resolution);

    JSValue then;
    JSValue error;
    {
        auto catchScope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
        then = resolutionObject->get(globalObject, vm.propertyNames->then);
        if (catchScope.exception()) [[unlikely]] {
            error = catchScope.exception()->value();
            if (!catchScope.clearExceptionExceptTermination()) [[unlikely]]
                return;
        }
    }
    if (error) [[unlikely]]
        return rejectPromise(vm, error);

    if (!then.isCallable()) [[likely]]
        return fulfillPromise(vm, resolutionObject);

    return globalObject->queueMicrotask(vm, InternalMicrotask::PromiseResolveThenableJob, 0, resolutionObject, then, this);
}

JSC_DEFINE_HOST_FUNCTION(promiseResolvingFunctionResolve, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
    VM& vm = globalObject->vm();

    auto* callee = uncheckedDowncast<JSFunctionWithFields>(callFrame->jsCallee());
    auto* other = dynamicDowncast<JSFunctionWithFields>(callee->getField(JSFunctionWithFields::Field::ResolvingOther));
    if (!other) [[unlikely]]
        return JSValue::encode(jsUndefined());

    callee->setField(vm, JSFunctionWithFields::Field::ResolvingOther, jsNull());
    other->setField(vm, JSFunctionWithFields::Field::ResolvingOther, jsNull());

    auto* promise = uncheckedDowncast<JSPromise>(callee->getField(JSFunctionWithFields::Field::ResolvingPromise));
    JSValue argument = callFrame->argument(0);

    promise->resolvePromise(globalObject, vm, argument);
    return JSValue::encode(jsUndefined());
}

JSC_DEFINE_HOST_FUNCTION(promiseResolvingFunctionReject, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
    VM& vm = globalObject->vm();

    auto* callee = uncheckedDowncast<JSFunctionWithFields>(callFrame->jsCallee());
    auto* other = dynamicDowncast<JSFunctionWithFields>(callee->getField(JSFunctionWithFields::Field::ResolvingOther));
    if (!other) [[unlikely]]
        return JSValue::encode(jsUndefined());

    callee->setField(vm, JSFunctionWithFields::Field::ResolvingOther, jsNull());
    other->setField(vm, JSFunctionWithFields::Field::ResolvingOther, jsNull());

    auto* promise = uncheckedDowncast<JSPromise>(callee->getField(JSFunctionWithFields::Field::ResolvingPromise));
    JSValue argument = callFrame->argument(0);

    promise->rejectPromise(vm, argument);
    return JSValue::encode(jsUndefined());
}

JSC_DEFINE_HOST_FUNCTION(promiseFirstResolvingFunctionResolve, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
    auto* callee = uncheckedDowncast<JSFunctionWithFields>(callFrame->jsCallee());
    auto* promise = uncheckedDowncast<JSPromise>(callee->getField(JSFunctionWithFields::Field::FirstResolvingPromise));
    JSValue argument = callFrame->argument(0);

    promise->resolve(globalObject, globalObject->vm(), argument);
    return JSValue::encode(jsUndefined());
}

JSC_DEFINE_HOST_FUNCTION(promiseFirstResolvingFunctionReject, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
    auto* callee = uncheckedDowncast<JSFunctionWithFields>(callFrame->jsCallee());
    auto* promise = uncheckedDowncast<JSPromise>(callee->getField(JSFunctionWithFields::Field::FirstResolvingPromise));
    JSValue argument = callFrame->argument(0);

    promise->reject(globalObject->vm(), argument);
    return JSValue::encode(jsUndefined());
}

JSC_DEFINE_HOST_FUNCTION(promiseResolvingFunctionResolveWithInternalMicrotask, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
    VM& vm = globalObject->vm();

    auto* callee = uncheckedDowncast<JSFunctionWithFields>(callFrame->jsCallee());
    auto* other = dynamicDowncast<JSFunctionWithFields>(callee->getField(JSFunctionWithFields::Field::ResolvingWithInternalMicrotaskOther));
    if (!other) [[unlikely]]
        return JSValue::encode(jsUndefined());

    callee->setField(vm, JSFunctionWithFields::Field::ResolvingWithInternalMicrotaskOther, jsNull());
    other->setField(vm, JSFunctionWithFields::Field::ResolvingWithInternalMicrotaskOther, jsNull());

    auto* contextCell = uncheckedDowncast<JSSlimPromiseReaction>(callee->getField(JSFunctionWithFields::Field::ResolvingWithInternalMicrotaskContext));
    JSValue argument = callFrame->argument(0);
    JSPromise::resolveWithInternalMicrotask(globalObject, vm, argument, contextCell->internalMicrotask(), contextCell->handlerOrContext());
    return JSValue::encode(jsUndefined());
}

JSC_DEFINE_HOST_FUNCTION(promiseResolvingFunctionRejectWithInternalMicrotask, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
    VM& vm = globalObject->vm();

    auto* callee = uncheckedDowncast<JSFunctionWithFields>(callFrame->jsCallee());
    auto* other = dynamicDowncast<JSFunctionWithFields>(callee->getField(JSFunctionWithFields::Field::ResolvingWithInternalMicrotaskOther));
    if (!other) [[unlikely]]
        return JSValue::encode(jsUndefined());

    callee->setField(vm, JSFunctionWithFields::Field::ResolvingWithInternalMicrotaskOther, jsNull());
    other->setField(vm, JSFunctionWithFields::Field::ResolvingWithInternalMicrotaskOther, jsNull());

    auto* contextCell = uncheckedDowncast<JSSlimPromiseReaction>(callee->getField(JSFunctionWithFields::Field::ResolvingWithInternalMicrotaskContext));
    JSValue argument = callFrame->argument(0);
    JSPromise::rejectWithInternalMicrotask(vm, globalObject, argument, contextCell->internalMicrotask(), contextCell->handlerOrContext());
    return JSValue::encode(jsUndefined());
}

JSC_DEFINE_HOST_FUNCTION(promiseCapabilityExecutor, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
    VM& vm = globalObject->vm();
    auto scope = DECLARE_THROW_SCOPE(vm);

    auto* callee = uncheckedDowncast<JSFunctionWithFields>(callFrame->jsCallee());
    JSValue resolve = callee->getField(JSFunctionWithFields::Field::ExecutorResolve);
    if (!resolve.isUndefined()) [[unlikely]]
        return throwVMTypeError(globalObject, scope, "resolve function is already set"_s);

    JSValue reject = callee->getField(JSFunctionWithFields::Field::ExecutorReject);
    if (!reject.isUndefined()) [[unlikely]]
        return throwVMTypeError(globalObject, scope, "reject function is already set"_s);

    callee->setField(vm, JSFunctionWithFields::Field::ExecutorResolve, callFrame->argument(0));
    callee->setField(vm, JSFunctionWithFields::Field::ExecutorReject, callFrame->argument(1));

    return JSValue::encode(jsUndefined());
}

std::tuple<JSFunction*, JSFunction*> JSPromise::createResolvingFunctions(VM& vm, JSGlobalObject* globalObject)
{
    auto* resolve = JSFunctionWithFields::create(vm, globalObject, vm.promiseResolvingFunctionResolveExecutable());
    auto* reject = JSFunctionWithFields::create(vm, globalObject, vm.promiseResolvingFunctionRejectExecutable());

    resolve->setField(vm, JSFunctionWithFields::Field::ResolvingPromise, this);
    resolve->setField(vm, JSFunctionWithFields::Field::ResolvingOther, reject);

    reject->setField(vm, JSFunctionWithFields::Field::ResolvingPromise, this);
    reject->setField(vm, JSFunctionWithFields::Field::ResolvingOther, resolve);

    return std::tuple { resolve, reject };
}

JSFunction* JSPromise::createFirstResolveFunction(VM& vm, JSGlobalObject* globalObject)
{
    auto* resolve = JSFunctionWithFields::create(vm, globalObject, vm.promiseFirstResolvingFunctionResolveExecutable());
    resolve->setField(vm, JSFunctionWithFields::Field::FirstResolvingPromise, this);
    return resolve;
}

JSFunction* JSPromise::createFirstRejectFunction(VM& vm, JSGlobalObject* globalObject)
{
    auto* reject = JSFunctionWithFields::create(vm, globalObject, vm.promiseFirstResolvingFunctionRejectExecutable());
    reject->setField(vm, JSFunctionWithFields::Field::FirstResolvingPromise, this);
    return reject;
}

std::tuple<JSFunction*, JSFunction*> JSPromise::createFirstResolvingFunctions(VM& vm, JSGlobalObject* globalObject)
{
    return std::tuple { createFirstResolveFunction(vm, globalObject), createFirstRejectFunction(vm, globalObject) };
}

std::tuple<JSFunction*, JSFunction*> JSPromise::createResolvingFunctionsWithInternalMicrotask(VM& vm, JSGlobalObject* globalObject, InternalMicrotask task, JSValue context)
{
    auto* resolve = JSFunctionWithFields::create(vm, globalObject, vm.promiseResolvingFunctionResolveWithInternalMicrotaskExecutable());
    auto* reject = JSFunctionWithFields::create(vm, globalObject, vm.promiseResolvingFunctionRejectWithInternalMicrotaskExecutable());

    auto* contextCell = JSSlimPromiseReaction::create(vm, jsUndefined(), task, context, /* next */ nullptr);

    resolve->setField(vm, JSFunctionWithFields::Field::ResolvingWithInternalMicrotaskContext, contextCell);
    resolve->setField(vm, JSFunctionWithFields::Field::ResolvingWithInternalMicrotaskOther, reject);

    reject->setField(vm, JSFunctionWithFields::Field::ResolvingWithInternalMicrotaskContext, contextCell);
    reject->setField(vm, JSFunctionWithFields::Field::ResolvingWithInternalMicrotaskOther, resolve);

    return std::tuple { resolve, reject };
}

void JSPromise::triggerPromiseReactions(VM& vm, JSGlobalObject* globalObject, Status status, JSPromiseReaction* head, JSValue argument)
{
    bool isResolved = status == JSPromise::Status::Fulfilled;

    auto queue = [&](JSPromiseReaction* reaction) ALWAYS_INLINE_LAMBDA {
        JSValue promise = reaction->promise();
        InternalMicrotask task = InternalMicrotask::PromiseReactionJob;
        JSValue handler;
        JSValue arg = argument;

        switch (reaction->type()) {
        case JSSlimPromiseReactionType: {
            auto* slimReaction = uncheckedDowncast<JSSlimPromiseReaction>(reaction);
            if (auto internalTask = slimReaction->internalMicrotask(); internalTask != InternalMicrotask::None) {
                task = internalTask;
                handler = argument;
                arg = slimReaction->handlerOrContext();
            } else if (slimReaction->isFulfillHandler() == isResolved)
                handler = slimReaction->handlerOrContext();
            else {
                task = InternalMicrotask::PromiseResolveWithoutHandlerJob;
                handler = argument;
                arg = jsUndefined();
            }
            break;
        }
        case JSFullPromiseReactionType: {
            auto* fullReaction = uncheckedDowncast<JSFullPromiseReaction>(reaction);
            handler = isResolved ? fullReaction->onFulfilled() : fullReaction->onRejected();
            ASSERT(fullReaction->context().isUndefinedOrNull());
            break;
        }
        default:
            RELEASE_ASSERT_NOT_REACHED();
        }

        globalObject->queueMicrotask(vm, task, static_cast<uint8_t>(status), promise, handler, arg);
    };

    ASSERT(head);
    if (!head->next()) [[likely]] {
        queue(head);
        return;
    }

    // Reverse the order of singly-linked-list.
    JSPromiseReaction* previous = nullptr;
    {
        auto* current = head;
        while (current) {
            auto* next = current->next();
            current->setNext(vm, previous);
            previous = current;
            current = next;
        }
    }
    head = previous;

    auto* current = head;
    do {
        auto* next = current->next();
        queue(current);
        current = next;
    } while (current);
}

void JSPromise::resolveWithInternalMicrotaskForAsyncAwait(JSGlobalObject* globalObject, VM& vm, JSValue resolution, InternalMicrotask task, JSValue context)
{
    if (resolution.inherits<JSPromise>()) {
        auto* promise = uncheckedDowncast<JSPromise>(resolution);
        if (promise->realm() == globalObject && promiseSpeciesWatchpointIsValid(vm, promise)) [[likely]]
            return promise->performPromiseThenWithInternalMicrotask(vm, task, nullptr, context);

        JSValue constructor;
        JSValue error;
        {
            auto catchScope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
            constructor = promise->get(globalObject, vm.propertyNames->constructor);
            if (catchScope.exception()) [[unlikely]] {
                error = catchScope.exception()->value();
                if (!catchScope.clearExceptionExceptTermination()) [[unlikely]]
                    return;
            }
        }
        if (error) [[unlikely]] {
            std::array<JSValue, maxMicrotaskArguments> arguments { {
                jsUndefined(),
                error,
                context,
            } };
            runInternalMicrotask(globalObject, vm, task, static_cast<uint8_t>(JSPromise::Status::Rejected), arguments);
            return;
        }

        if (constructor == globalObject->promiseConstructor())
            return promise->performPromiseThenWithInternalMicrotask(vm, task, nullptr, context);
    }

    resolveWithInternalMicrotask(globalObject, vm, resolution, task, context);
}

void JSPromise::resolveWithInternalMicrotask(JSGlobalObject* globalObject, VM& vm, JSValue resolution, InternalMicrotask task, JSValue context)
{
    if (!resolution.isObject())
        return fulfillWithInternalMicrotask(vm, globalObject, resolution, task, context);

    auto* resolutionObject = asObject(resolution);
    if (resolutionObject->inherits<JSPromise>()) {
        auto* promise = uncheckedDowncast<JSPromise>(resolutionObject);
        if (promise->realm() == globalObject && promise->isThenFastAndNonObservable())
            return globalObject->queueMicrotask(vm, InternalMicrotask::PromiseResolveThenableJobWithInternalMicrotaskFast, static_cast<uint8_t>(task), resolutionObject, context, jsUndefined());
    }

    if (isDefinitelyNonThenable(resolutionObject, globalObject))
        return fulfillWithInternalMicrotask(vm, globalObject, resolution, task, context);

    JSValue then;
    JSValue error;
    {
        auto catchScope = DECLARE_TOP_EXCEPTION_SCOPE(vm);
        then = resolutionObject->get(globalObject, vm.propertyNames->then);
        if (catchScope.exception()) [[unlikely]] {
            error = catchScope.exception()->value();
            if (!catchScope.clearExceptionExceptTermination()) [[unlikely]]
                return;
        }
    }
    if (error) [[unlikely]]
        return rejectWithInternalMicrotask(vm, globalObject, error, task, context);

    if (!then.isCallable()) [[likely]]
        return fulfillWithInternalMicrotask(vm, globalObject, resolution, task, context);

    return globalObject->queueMicrotask(vm, InternalMicrotask::PromiseResolveThenableJobWithInternalMicrotask, static_cast<uint8_t>(task), resolutionObject, then, context);
}

void JSPromise::rejectWithInternalMicrotask(VM& vm, JSGlobalObject* globalObject, JSValue argument, InternalMicrotask task, JSValue context)
{
    globalObject->queueMicrotask(vm, task, static_cast<uint8_t>(Status::Rejected), jsUndefined(), argument, context);
}

void JSPromise::fulfillWithInternalMicrotask(VM& vm, JSGlobalObject* globalObject, JSValue argument, InternalMicrotask task, JSValue context)
{
    globalObject->queueMicrotask(vm, task, static_cast<uint8_t>(Status::Fulfilled), jsUndefined(), argument, context);
}

bool JSPromise::isThenFastAndNonObservable()
{
    JSGlobalObject* globalObject = this->realm();
    Structure* structure = this->structure();
    if (!globalObject->promiseThenWatchpointSet().isStillValid()) [[unlikely]]
        return false;

    if (structure == globalObject->promiseStructure())
        return true;

    if (getPrototypeDirect() != globalObject->promisePrototype())
        return false;

    VM& vm = globalObject->vm();
    if (getDirectOffset(vm, vm.propertyNames->then) != invalidOffset)
        return false;

    return true;
}

JSObject* promiseSpeciesConstructor(JSGlobalObject* globalObject, JSObject* thisObject)
{
    VM& vm = getVM(globalObject);
    auto scope = DECLARE_THROW_SCOPE(vm);

    if (auto* promise = dynamicDowncast<JSPromise>(thisObject)) [[likely]] {
        if (promiseSpeciesWatchpointIsValid(vm, promise)) [[likely]]
            return globalObject->promiseConstructor();
    }

    JSValue constructor = thisObject->get(globalObject, vm.propertyNames->constructor);
    RETURN_IF_EXCEPTION(scope, { });

    if (constructor.isUndefined())
        return globalObject->promiseConstructor();

    if (!constructor.isObject()) [[unlikely]] {
        throwTypeError(globalObject, scope, "|this|.constructor is not an Object or undefined"_s);
        return { };
    }

    constructor = asObject(constructor)->get(globalObject, vm.propertyNames->speciesSymbol);
    RETURN_IF_EXCEPTION(scope, { });

    if (constructor.isUndefinedOrNull())
        return globalObject->promiseConstructor();

    if (constructor.isConstructor()) [[likely]]
        return asObject(constructor);

    throwTypeError(globalObject, scope, "|this|.constructor[Symbol.species] is not a constructor"_s);
    return { };
}

Structure* createPromiseCapabilityObjectStructure(VM& vm, JSGlobalObject& globalObject)
{
    Structure* structure = globalObject.structureCache().emptyObjectStructureForPrototype(&globalObject, globalObject.objectPrototype(), JSFinalObject::defaultInlineCapacity);
    PropertyOffset offset;
    structure = Structure::addPropertyTransition(vm, structure, vm.propertyNames->resolve, 0, offset);
    RELEASE_ASSERT(offset == promiseCapabilityResolvePropertyOffset);
    structure = Structure::addPropertyTransition(vm, structure, vm.propertyNames->reject, 0, offset);
    RELEASE_ASSERT(offset == promiseCapabilityRejectPropertyOffset);
    structure = Structure::addPropertyTransition(vm, structure, vm.propertyNames->promise, 0, offset);
    RELEASE_ASSERT(offset == promiseCapabilityPromisePropertyOffset);
    return structure;
}

JSObject* JSPromise::then(JSGlobalObject* globalObject, JSValue onFulfilled, JSValue onRejected)
{
    VM& vm = globalObject->vm();
    auto scope = DECLARE_THROW_SCOPE(vm);

    JSObject* resultPromise;
    JSValue resultPromiseCapability;
    if (promiseSpeciesWatchpointIsValid(vm, this)) [[likely]] {
        resultPromise = JSPromise::create(vm, globalObject->promiseStructure());
        resultPromiseCapability = resultPromise;
    } else {
        auto* constructor = promiseSpeciesConstructor(globalObject, this);
        RETURN_IF_EXCEPTION(scope, { });

        auto [promise, resolve, reject] = JSPromise::newPromiseCapability(globalObject, constructor);
        RETURN_IF_EXCEPTION(scope, { });

        resultPromise = promise;
        resultPromiseCapability = JSPromise::createPromiseCapability(vm, globalObject, promise, resolve, reject);
    }

    scope.release();
    performPromiseThen(vm, globalObject, onFulfilled, onRejected, resultPromiseCapability);
    return resultPromise;
}

JSObject* JSPromise::promiseResolve(JSGlobalObject* globalObject, JSObject* constructor, JSValue argument)
{
    VM& vm = globalObject->vm();
    auto scope = DECLARE_THROW_SCOPE(vm);

    if (argument.inherits<JSPromise>()) {
        auto* promise = uncheckedDowncast<JSPromise>(argument);
        if (promiseSpeciesWatchpointIsValid(vm, promise)) [[likely]] {
            if (constructor == promise->realm()->promiseConstructor())
                return promise;
        } else {
            auto property = promise->get(globalObject, vm.propertyNames->constructor);
            RETURN_IF_EXCEPTION(scope, { });

            if (property == constructor)
                return promise;
        }
    }

    if (constructor == globalObject->promiseConstructor()) [[likely]] {
        JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure());
        scope.release();
        promise->resolve(globalObject, vm, argument);
        return promise;
    }

    auto [promise, resolve, reject] = newPromiseCapability(globalObject, constructor);
    RETURN_IF_EXCEPTION(scope, { });

    MarkedArgumentBuffer arguments;
    arguments.append(argument);
    ASSERT(!arguments.hasOverflowed());
    scope.release();
    call(globalObject, resolve, jsUndefined(), arguments, "resolve is not a function"_s);
    return promise;
}

JSObject* JSPromise::promiseReject(JSGlobalObject* globalObject, JSObject* constructor, JSValue argument)
{
    VM& vm = globalObject->vm();
    auto scope = DECLARE_THROW_SCOPE(vm);

    if (constructor == globalObject->promiseConstructor()) [[likely]] {
        JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure());
        promise->reject(vm, argument);
        return promise;
    }

    auto [promise, resolve, reject] = newPromiseCapability(globalObject, constructor);
    RETURN_IF_EXCEPTION(scope, { });

    MarkedArgumentBuffer arguments;
    arguments.append(argument);
    ASSERT(!arguments.hasOverflowed());
    scope.release();
    call(globalObject, reject, jsUndefined(), arguments, "reject is not a function"_s);
    return promise;
}

} // namespace JSC
