blob: 12b005171041c2c4db7db2bfc41af4ca15b1e512 [file] [log] [blame]
/*
* Copyright (C) 2025 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. ``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
* 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.
*/
#pragma once
#include <JavaScriptCore/JSCJSValue.h>
#include <JavaScriptCore/Microtask.h>
#include <JavaScriptCore/SlotVisitorMacros.h>
#include <wtf/CompactPointerTuple.h>
#include <wtf/Compiler.h>
#include <wtf/Deque.h>
#include <wtf/Ref.h>
#include <wtf/RefCounted.h>
#include <wtf/SentinelLinkedList.h>
#include <wtf/TZoneMalloc.h>
#include <wtf/VectorTraits.h>
namespace JSC {
class JSCell;
class JSMicrotaskDispatcher;
}
WTF_ALLOW_COMPACT_POINTERS_TO_INCOMPLETE_TYPE(JSC::JSCell);
WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
namespace JSC {
class JSGlobalObject;
class MarkedMicrotaskDeque;
class MicrotaskDispatcher;
class MicrotaskQueue;
class QueuedTask;
class TopExceptionScope;
class VM;
class MicrotaskDispatcher : public RefCounted<MicrotaskDispatcher> {
WTF_MAKE_COMPACT_TZONE_ALLOCATED(MicrotaskDispatcher);
public:
enum class Type : uint8_t {
None,
JSCDebuggable,
// WebCoreMicrotaskDispatcher starts from here.
WebCoreUserGestureIndicator,
WebCoreFunction,
};
explicit MicrotaskDispatcher(Type type)
: m_type(type)
{ }
virtual ~MicrotaskDispatcher() = default;
virtual QueuedTaskResult run(QueuedTask&) = 0;
virtual bool isRunnable() const = 0;
Type type() const { return m_type; }
bool isWebCoreMicrotaskDispatcher() const { return static_cast<uint8_t>(m_type) >= static_cast<uint8_t>(Type::WebCoreUserGestureIndicator); }
private:
Type m_type { Type::None };
};
class DebuggableMicrotaskDispatcher final : public MicrotaskDispatcher {
WTF_MAKE_COMPACT_TZONE_ALLOCATED(DebuggableMicrotaskDispatcher);
public:
explicit DebuggableMicrotaskDispatcher()
: MicrotaskDispatcher(Type::JSCDebuggable)
{ }
static Ref<DebuggableMicrotaskDispatcher> create()
{
return adoptRef(*new DebuggableMicrotaskDispatcher());
}
QueuedTaskResult run(QueuedTask&) final;
bool isRunnable() const final;
};
class QueuedTask {
WTF_MAKE_TZONE_ALLOCATED(QueuedTask);
friend class MicrotaskQueue;
friend class MarkedMicrotaskDeque;
public:
static constexpr unsigned maxArguments = maxMicrotaskArguments;
using Result = QueuedTaskResult;
QueuedTask(JSMicrotaskDispatcher* dispatcher)
: m_dispatcher(std::bit_cast<JSCell*>(std::bit_cast<uintptr_t>(dispatcher) | isJSMicrotaskDispatcherFlag), static_cast<uint16_t>(InternalMicrotask::Opaque))
{
}
template<typename... Args>
requires (sizeof...(Args) <= maxArguments) && (std::is_convertible_v<Args, JSValue> && ...)
QueuedTask(JSMicrotaskDispatcher* dispatcher, InternalMicrotask job, uint8_t payload, JSGlobalObject* globalObject, Args&&...args)
: m_dispatcher(
dispatcher ? std::bit_cast<JSCell*>(std::bit_cast<uintptr_t>(dispatcher) | isJSMicrotaskDispatcherFlag) : std::bit_cast<JSCell*>(globalObject),
static_cast<uint16_t>(job) | (static_cast<uint16_t>(payload) << 8))
, m_arguments { std::forward<Args>(args)... }
{
}
void setDispatcher(JSMicrotaskDispatcher* dispatcher)
{
m_dispatcher.setPointer(std::bit_cast<JSCell*>(std::bit_cast<uintptr_t>(dispatcher) | isJSMicrotaskDispatcherFlag));
}
bool isRunnable() const;
// Defined in MicrotaskQueueInlines.h (requires JSType knowledge).
JSCell* dispatcher() const;
JSGlobalObject* globalObject() const;
JSMicrotaskDispatcher* jsMicrotaskDispatcher() const;
std::optional<MicrotaskIdentifier> identifier() const;
InternalMicrotask job() const { return static_cast<InternalMicrotask>(static_cast<uint8_t>(m_dispatcher.type())); }
// Task-specific metadata stored in upper 8 bits of type field.
// Typically holds JSPromise::Status or a nested InternalMicrotask value.
uint8_t payload() const { return static_cast<uint8_t>(m_dispatcher.type() >> 8); }
std::span<const JSValue, maxArguments> arguments() const { return std::span<const JSValue, maxArguments> { m_arguments, maxArguments }; }
private:
bool isJSMicrotaskDispatcher() const
{
return std::bit_cast<uintptr_t>(m_dispatcher.pointer()) & isJSMicrotaskDispatcherFlag;
}
static constexpr uintptr_t isJSMicrotaskDispatcherFlag = 0x1;
CompactPointerTuple<JSCell*, uint16_t> m_dispatcher;
JSValue m_arguments[maxArguments] { };
};
static_assert(sizeof(QueuedTask) <= 32, "Size of QueuedTask is critical for performance");
static_assert(std::is_trivially_destructible_v<QueuedTask>);
class MarkedMicrotaskDeque {
public:
friend class MicrotaskQueue;
MarkedMicrotaskDeque() = default;
const QueuedTask& front() const { return m_queue.first(); }
QueuedTask dequeue()
{
if (m_markedBefore)
--m_markedBefore;
return m_queue.takeFirst();
}
void enqueue(QueuedTask&& task)
{
m_queue.append(WTF::move(task));
}
bool isEmpty() const
{
return m_queue.isEmpty();
}
size_t size() const { return m_queue.size(); }
void clear()
{
m_queue.clear();
m_markedBefore = 0;
}
void beginMarking()
{
m_markedBefore = 0;
}
void swap(MarkedMicrotaskDeque& other)
{
m_queue.swap(other.m_queue);
std::swap(m_markedBefore, other.m_markedBefore);
}
JS_EXPORT_PRIVATE bool hasMicrotasksForFullyActiveDocument() const;
DECLARE_VISIT_AGGREGATE;
private:
Deque<QueuedTask> m_queue;
size_t m_markedBefore { 0 };
};
class MicrotaskQueue : public BasicRawSentinelNode<MicrotaskQueue>, public RefCounted<MicrotaskQueue> {
WTF_MAKE_TZONE_ALLOCATED_EXPORT(MicrotaskQueue, JS_EXPORT_PRIVATE);
WTF_MAKE_NONCOPYABLE(MicrotaskQueue);
public:
JS_EXPORT_PRIVATE static Ref<MicrotaskQueue> create(VM&);
JS_EXPORT_PRIVATE virtual ~MicrotaskQueue();
inline void enqueue(QueuedTask&&);
JS_EXPORT_PRIVATE void enqueueSlow(QueuedTask&&);
bool isEmpty() const
{
return m_queue.isEmpty();
}
size_t size() const { return m_queue.size(); }
void clear()
{
m_queue.clear();
m_toKeep.clear();
}
void beginMarking()
{
m_queue.beginMarking();
m_toKeep.beginMarking();
}
DECLARE_VISIT_AGGREGATE;
template<bool useCallOnEachMicrotask>
inline void performMicrotaskCheckpoint(VM&, NOESCAPE const Invocable<void(JSGlobalObject*, JSGlobalObject*)> auto& globalObjectSwitchCallback);
bool hasMicrotasksForFullyActiveDocument() const
{
return m_queue.hasMicrotasksForFullyActiveDocument();
}
bool isScheduledToRun() const { return m_isScheduledToRun; }
void setIsScheduledToRun(bool value) { m_isScheduledToRun = value; }
protected:
JS_EXPORT_PRIVATE MicrotaskQueue(VM&);
virtual void scheduleToRunIfNeeded()
{
setIsScheduledToRun(true);
}
bool m_isScheduledToRun { false };
private:
JS_EXPORT_PRIVATE std::pair<JSGlobalObject*, bool> drainWithUseCallOnEachMicrotask(JSGlobalObject* currentGlobalObject, VM&, TopExceptionScope&);
JS_EXPORT_PRIVATE std::pair<JSGlobalObject*, bool> drainWithoutUseCallOnEachMicrotask(JSGlobalObject* currentGlobalObject, VM&, TopExceptionScope&);
template<bool useCallOnEachMicrotask>
ALWAYS_INLINE std::pair<JSGlobalObject*, bool> drain(JSGlobalObject* globalObject, VM& vm, TopExceptionScope& scope)
{
if constexpr (useCallOnEachMicrotask)
return drainWithUseCallOnEachMicrotask(globalObject, vm, scope);
else
return drainWithoutUseCallOnEachMicrotask(globalObject, vm, scope);
}
template<bool useCallOnEachMicrotask>
std::pair<JSGlobalObject*, bool> drainImpl(JSGlobalObject*, VM&, TopExceptionScope&);
MarkedMicrotaskDeque m_queue;
MarkedMicrotaskDeque m_toKeep;
};
JS_EXPORT_PRIVATE void runMicrotaskWithDebugger(JSGlobalObject*, VM&, QueuedTask&);
} // namespace JSC
namespace WTF {
template<> struct VectorTraits<JSC::QueuedTask> : VectorTraitsBase<true, JSC::QueuedTask> {
static constexpr bool canCompareWithMemcmp = false;
};
} // namespace WTF
WTF_ALLOW_UNSAFE_BUFFER_USAGE_END