From 92aeb0af5594516d6444c9fcc69d56682e34771b Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 22 Jun 2022 06:43:57 -0700 Subject: [PATCH] Add a way to schedule microtasks from C++ --- .../jsc/bindings/ScriptExecutionContext.h | 85 +++++++++++-------- .../jsc/bindings/ZigGlobalObject.cpp | 11 +++ src/javascript/jsc/bindings/ZigGlobalObject.h | 5 ++ .../jsc/bindings/webcore/JSCallbackData.h | 4 +- src/javascript/jsc/event_loop.zig | 17 +++- 5 files changed, 82 insertions(+), 40 deletions(-) diff --git a/src/javascript/jsc/bindings/ScriptExecutionContext.h b/src/javascript/jsc/bindings/ScriptExecutionContext.h index e5644c2fc9..72837368e1 100644 --- a/src/javascript/jsc/bindings/ScriptExecutionContext.h +++ b/src/javascript/jsc/bindings/ScriptExecutionContext.h @@ -14,8 +14,12 @@ namespace uWS { template struct WebSocketContext; - } + +#ifndef ZIG_GLOBAL_OBJECT_DEFINED +#include "ZigGlobalObject.h" +#endif + struct us_socket_t; struct us_socket_context_t; struct us_loop_t; @@ -24,42 +28,48 @@ namespace WebCore { class WebSocket; +class ScriptExecutionContext; + +class EventLoopTask { + WTF_MAKE_FAST_ALLOCATED; + +public: + enum CleanupTaskTag { CleanupTask }; + + template::value && std::is_convertible>::value>::type> + EventLoopTask(T task) + : m_task(WTFMove(task)) + , m_isCleanupTask(false) + { + } + + EventLoopTask(Function&& task) + : m_task([task = WTFMove(task)](ScriptExecutionContext&) { task(); }) + , m_isCleanupTask(false) + { + } + + template>::value>::type> + EventLoopTask(CleanupTaskTag, T task) + : m_task(WTFMove(task)) + , m_isCleanupTask(true) + { + } + + void performTask(ScriptExecutionContext& context) + { + m_task(context); + delete this; + } + bool isCleanupTask() const { return m_isCleanupTask; } + +protected: + Function m_task; + bool m_isCleanupTask; +}; + class ScriptExecutionContext : public CanMakeWeakPtr { public: - class Task { - WTF_MAKE_FAST_ALLOCATED; - - public: - enum CleanupTaskTag { CleanupTask }; - - template::value && std::is_convertible>::value>::type> - Task(T task) - : m_task(WTFMove(task)) - , m_isCleanupTask(false) - { - } - - Task(Function&& task) - : m_task([task = WTFMove(task)](ScriptExecutionContext&) { task(); }) - , m_isCleanupTask(false) - { - } - - template>::value>::type> - Task(CleanupTaskTag, T task) - : m_task(WTFMove(task)) - , m_isCleanupTask(true) - { - } - - void performTask(ScriptExecutionContext& context) { m_task(context); } - bool isCleanupTask() const { return m_isCleanupTask; } - - protected: - Function m_task; - bool m_isCleanupTask; - }; - public: ScriptExecutionContext(JSC::VM* vm, JSC::JSGlobalObject* globalObject) : m_vm(vm) @@ -96,9 +106,10 @@ public: // { // } - void postTask(Task&& task) + void postTask(Function&& lambda) { - + auto* task = new EventLoopTask(WTFMove(lambda)); + reinterpret_cast(m_globalObject)->queueTask(task); } // Executes the task on context's thread asynchronously. template diff --git a/src/javascript/jsc/bindings/ZigGlobalObject.cpp b/src/javascript/jsc/bindings/ZigGlobalObject.cpp index 53872f325c..e99f9a1ccd 100644 --- a/src/javascript/jsc/bindings/ZigGlobalObject.cpp +++ b/src/javascript/jsc/bindings/ZigGlobalObject.cpp @@ -2107,6 +2107,17 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) visitor.addOpaqueRoot(context); } +extern "C" void Bun__queueMicrotask(JSC__JSGlobalObject*, WebCore::EventLoopTask* task); +extern "C" void Bun__performTask(Zig::GlobalObject* globalObject, WebCore::EventLoopTask* task) +{ + task->performTask(*globalObject->scriptExecutionContext()); +} + +void GlobalObject::queueTask(WebCore::EventLoopTask* task) +{ + Bun__queueMicrotask(this, task); +} + DEFINE_VISIT_CHILDREN(GlobalObject); // void GlobalObject::destroy(JSCell* cell) diff --git a/src/javascript/jsc/bindings/ZigGlobalObject.h b/src/javascript/jsc/bindings/ZigGlobalObject.h index 4f8a7871ec..41556e639f 100644 --- a/src/javascript/jsc/bindings/ZigGlobalObject.h +++ b/src/javascript/jsc/bindings/ZigGlobalObject.h @@ -13,6 +13,7 @@ class LazyClassStructure; namespace WebCore { class ScriptExecutionContext; class DOMGuardedObject; +class EventLoopTask; } #include "root.h" @@ -37,6 +38,8 @@ namespace Zig { using JSDOMStructureMap = HashMap>; using DOMGuardedObjectSet = HashSet; +#define ZIG_GLOBAL_OBJECT_DEFINED + class GlobalObject : public JSC::JSGlobalObject { using Base = JSC::JSGlobalObject; @@ -111,6 +114,8 @@ public: WebCore::ScriptExecutionContext* scriptExecutionContext(); WebCore::ScriptExecutionContext* scriptExecutionContext() const; + void queueTask(WebCore::EventLoopTask* task); + JSDOMStructureMap& structures() WTF_REQUIRES_LOCK(m_gcLock) { return m_structures; } JSDOMStructureMap& structures(NoLockingNecessaryTag) WTF_IGNORES_THREAD_SAFETY_ANALYSIS { diff --git a/src/javascript/jsc/bindings/webcore/JSCallbackData.h b/src/javascript/jsc/bindings/webcore/JSCallbackData.h index 07114ce41c..7ddd70ba79 100644 --- a/src/javascript/jsc/bindings/webcore/JSCallbackData.h +++ b/src/javascript/jsc/bindings/webcore/JSCallbackData.h @@ -109,11 +109,11 @@ private: JSC::Weak m_callback; }; -class DeleteCallbackDataTask : public ScriptExecutionContext::Task { +class DeleteCallbackDataTask : public EventLoopTask { public: template explicit DeleteCallbackDataTask(CallbackDataType* data) - : ScriptExecutionContext::Task(ScriptExecutionContext::Task::CleanupTask, [data = std::unique_ptr(data)](ScriptExecutionContext&) { + : EventLoopTask(EventLoopTask::CleanupTask, [data = std::unique_ptr(data)](ScriptExecutionContext&) { }) { } diff --git a/src/javascript/jsc/event_loop.zig b/src/javascript/jsc/event_loop.zig index f7ba4c49ed..2623551f6d 100644 --- a/src/javascript/jsc/event_loop.zig +++ b/src/javascript/jsc/event_loop.zig @@ -259,6 +259,14 @@ pub const AnyTask = struct { }; } }; + +pub const CppTask = opaque { + extern fn Bun__performTask(globalObject: *JSGlobalObject, task: *CppTask) void; + pub fn run(this: *CppTask, global: *JSGlobalObject) void { + JSC.markBinding(); + Bun__performTask(global, this); + } +}; const ThreadSafeFunction = JSC.napi.ThreadSafeFunction; const MicrotaskForDefaultGlobalObject = JSC.MicrotaskForDefaultGlobalObject; // const PromiseTask = JSInternalPromise.Completion.PromiseTask; @@ -274,6 +282,7 @@ pub const Task = TaggedPointerUnion(.{ AnyTask, napi_async_work, ThreadSafeFunction, + CppTask, // PromiseTask, // TimeoutTasklet, }); @@ -287,7 +296,7 @@ pub const EventLoop = struct { concurrent_lock: Lock = Lock.init(), global: *JSGlobalObject = undefined, virtual_machine: *VirtualMachine = undefined, - pub const Queue = bun.LinearFifo(Task, .Dynamic); + pub const Queue = std.fifo.LinearFifo(Task, .Dynamic); pub fn tickWithCount(this: *EventLoop) u32 { var finished: u32 = 0; @@ -357,6 +366,12 @@ pub const EventLoop = struct { finished += 1; vm_.active_tasks -|= 1; }, + @field(Task.Tag, @typeName(CppTask)) => { + var any: *CppTask = task.get(CppTask).?; + any.run(global); + finished += 1; + vm_.active_tasks -|= 1; + }, else => if (Environment.allow_assert) { bun.Output.prettyln("\nUnexpected tag: {s}\n", .{@tagName(task.tag())}); } else unreachable,