From d8180830936951072c27bd18e8c4f6806ef870bd Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Fri, 28 Jun 2024 15:47:43 +0200 Subject: [PATCH 1/2] [Hermes] Bump Hermes --- packages/react-native/ReactCommon/jsi/jsi/decorator.h | 7 +++++++ packages/react-native/ReactCommon/jsi/jsi/jsi.h | 7 +++++++ packages/react-native/sdks/.hermesversion | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/react-native/ReactCommon/jsi/jsi/decorator.h b/packages/react-native/ReactCommon/jsi/jsi/decorator.h index 7bddd1fad80a52..6f4351a420da69 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/decorator.h +++ b/packages/react-native/ReactCommon/jsi/jsi/decorator.h @@ -126,6 +126,9 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation { const std::shared_ptr& js) override { return plain().evaluatePreparedJavaScript(js); } + void queueMicrotask(const jsi::Function& callback) override { + return plain().queueMicrotask(callback); + } bool drainMicrotasks(int maxMicrotasksHint) override { return plain().drainMicrotasks(maxMicrotasksHint); } @@ -544,6 +547,10 @@ class WithRuntimeDecorator : public RuntimeDecorator { Around around{with_}; return RD::evaluatePreparedJavaScript(js); } + void queueMicrotask(const Function& callback) override { + Around around{with_}; + RD::queueMicrotask(callback); + } bool drainMicrotasks(int maxMicrotasksHint) override { Around around{with_}; return RD::drainMicrotasks(maxMicrotasksHint); diff --git a/packages/react-native/ReactCommon/jsi/jsi/jsi.h b/packages/react-native/ReactCommon/jsi/jsi/jsi.h index 962dae93609423..d77255f61a86c7 100644 --- a/packages/react-native/ReactCommon/jsi/jsi/jsi.h +++ b/packages/react-native/ReactCommon/jsi/jsi/jsi.h @@ -209,6 +209,13 @@ class JSI_EXPORT Runtime { virtual Value evaluatePreparedJavaScript( const std::shared_ptr& js) = 0; + /// Queues a microtask in the JavaScript VM internal Microtask (a.k.a. Job in + /// ECMA262) queue, to be executed when the host drains microtasks in + /// its event loop implementation. + /// + /// \param callback a function to be executed as a microtask. + virtual void queueMicrotask(const jsi::Function& callback) = 0; + /// Drain the JavaScript VM internal Microtask (a.k.a. Job in ECMA262) queue. /// /// \param maxMicrotasksHint a hint to tell an implementation that it should diff --git a/packages/react-native/sdks/.hermesversion b/packages/react-native/sdks/.hermesversion index 038c63a3fd7afc..ffcbbccc462b7c 100644 --- a/packages/react-native/sdks/.hermesversion +++ b/packages/react-native/sdks/.hermesversion @@ -1 +1 @@ -hermes-2024-06-03-RNv0.74.2-bb1e74fe1e95c2b5a2f4f9311152da052badc2bc +hermes-2024-06-28-RNv0.74.3-7bda0c267e76d11b68a585f84cfdd65000babf85 From 3f558bd5db82b8935ee4da00fd951f63fbd2b591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Mon, 4 Mar 2024 05:03:39 -0800 Subject: [PATCH 2/2] Implement queueMicrotask and drainMicrotasks in JSC Summary: Changelog: [internal] ## Context We want to enable the new React Native event loop by default for all users on the new RN architecture (on the bridgeless initialization path more concretely), which requires support for microtasks in all the JS engines that the support (Hermes already has it, JSC doesn't). ## Changes This adds initial support for microtasks in JSC, so we can schedule and execute microtasks in this runtime. One limitation about this approach is that, AFAIK, the public API for JSC doesn't allow us to customize its internal microtask queue or specify the method to be used by its built-in `Promise` or native `async function`, so we're forced to continue using a polyfill in that case (which uses `setImmediate` that will be mapped to `queueMicrotask`). Reviewed By: NickGerleman Differential Revision: D54302534 fbshipit-source-id: 47f71620344a81bc6624917f77452106ffbf55a3 --- .../ReactCommon/jsc/JSCRuntime.cpp | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/react-native/ReactCommon/jsc/JSCRuntime.cpp b/packages/react-native/ReactCommon/jsc/JSCRuntime.cpp index 235028a2b355a4..730da01a697a21 100644 --- a/packages/react-native/ReactCommon/jsc/JSCRuntime.cpp +++ b/packages/react-native/ReactCommon/jsc/JSCRuntime.cpp @@ -13,8 +13,8 @@ #include #include #include +#include #include -#include #include #include @@ -51,6 +51,12 @@ class JSCRuntime : public jsi::Runtime { const std::shared_ptr& buffer, const std::string& sourceURL) override; + // If we use this interface to implement microtasks in the host we need to + // polyfill `Promise` to use these methods, because JSC doesn't currently + // support providing a custom queue for its built-in implementation. + // Not doing this would result in a non-compliant behavior, as microtasks + // wouldn't execute in the order in which they were queued. + void queueMicrotask(const jsi::Function& callback) override; bool drainMicrotasks(int maxMicrotasksHint = -1) override; jsi::Object global() override; @@ -265,6 +271,7 @@ class JSCRuntime : public jsi::Runtime { std::atomic ctxInvalid_; std::string desc_; JSValueRef nativeStateSymbol_ = nullptr; + std::deque microtaskQueue_; #ifndef NDEBUG mutable std::atomic objectCounter_; mutable std::atomic symbolCounter_; @@ -380,6 +387,10 @@ JSCRuntime::JSCRuntime(JSGlobalContextRef ctx) } JSCRuntime::~JSCRuntime() { + // We need to clear the microtask queue to remove all references to the + // callbacks, so objectCounter_ would be 0 below. + microtaskQueue_.clear(); + // On shutting down and cleaning up: when JSC is actually torn down, // it calls JSC::Heap::lastChanceToFinalize internally which // finalizes anything left over. But at this point, @@ -436,7 +447,24 @@ jsi::Value JSCRuntime::evaluateJavaScript( return createValue(res); } -bool JSCRuntime::drainMicrotasks(int maxMicrotasksHint) { +void JSCRuntime::queueMicrotask(const jsi::Function& callback) { + microtaskQueue_.emplace_back( + jsi::Value(*this, callback).asObject(*this).asFunction(*this)); +} + +bool JSCRuntime::drainMicrotasks(int /*maxMicrotasksHint*/) { + // Note that new jobs can be enqueued during the draining. + while (!microtaskQueue_.empty()) { + jsi::Function callback = std::move(microtaskQueue_.front()); + + // We need to pop before calling the callback because that might throw. + // When that happens, the host will call `drainMicrotasks` again to execute + // the remaining microtasks, and this one shouldn't run again. + microtaskQueue_.pop_front(); + + callback.call(*this); + } + return true; }