From 4a12d3c7d22e9b55b1f594383f5f71ec416bb2c4 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 27 May 2022 14:53:56 -0700 Subject: [PATCH] Support PreparedScriptStore for V8 Node-API (#9995) --- ...-8f4cd7f3-9cd6-4681-8d63-b378c912d67d.json | 7 ++ .../JSI/NodeApiJsiRuntime.cpp | 20 ++++- .../JSI/NodeApiJsiRuntime.h | 48 ++++++++++++ vnext/PropertySheets/JSEngine.props | 2 +- vnext/Shared/JSI/NapiJsiV8RuntimeHolder.cpp | 74 ++++++++++++++++++- vnext/Shared/JSI/NapiJsiV8RuntimeHolder.h | 2 + 6 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 change/react-native-windows-8f4cd7f3-9cd6-4681-8d63-b378c912d67d.json diff --git a/change/react-native-windows-8f4cd7f3-9cd6-4681-8d63-b378c912d67d.json b/change/react-native-windows-8f4cd7f3-9cd6-4681-8d63-b378c912d67d.json new file mode 100644 index 00000000000..f6de1ead485 --- /dev/null +++ b/change/react-native-windows-8f4cd7f3-9cd6-4681-8d63-b378c912d67d.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Support PreparedScriptStore for V8 Node-API.", + "packageName": "react-native-windows", + "email": "vmorozov@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Microsoft.ReactNative.Cxx/JSI/NodeApiJsiRuntime.cpp b/vnext/Microsoft.ReactNative.Cxx/JSI/NodeApiJsiRuntime.cpp index d44f90ac7f6..94a2f3370ba 100644 --- a/vnext/Microsoft.ReactNative.Cxx/JSI/NodeApiJsiRuntime.cpp +++ b/vnext/Microsoft.ReactNative.Cxx/JSI/NodeApiJsiRuntime.cpp @@ -441,6 +441,7 @@ struct NapiJsiRuntime : facebook::jsi::Runtime { private: // Shared NAPI call helpers napi_value RunScript(napi_value script, const char *sourceUrl); + napi_value RunScriptBuffer(const std::shared_ptr &buffer, const char *sourceUrl); std::vector SerializeScript(napi_value script, const char *sourceUrl); napi_value RunSerializedScript(span serialized, napi_value source, const char *sourceUrl); napi_ext_ref CreateReference(napi_value value) const; @@ -622,9 +623,7 @@ NapiJsiRuntime::NapiJsiRuntime(napi_env env) noexcept : m_env{env} { Value NapiJsiRuntime::evaluateJavaScript(const shared_ptr &buffer, const string &sourceUrl) { EnvScope envScope{m_env}; - napi_value script = CreateStringUtf8(buffer->data(), buffer->size()); - napi_value result = RunScript(script, sourceUrl.c_str()); - + napi_value result = RunScriptBuffer(buffer, sourceUrl.c_str()); return ToJsiValue(result); } @@ -1414,6 +1413,21 @@ napi_value NapiJsiRuntime::RunScript(napi_value script, const char *sourceUrl) { return result; } +napi_value NapiJsiRuntime::RunScriptBuffer( + const std::shared_ptr &buffer, + const char *sourceUrl) { + napi_ext_buffer napiBuffer{}; + napiBuffer.buffer_object = NativeObjectWrapper>::Wrap( + std::shared_ptr{buffer}); + napiBuffer.data = buffer->data(); + napiBuffer.byte_size = buffer->size(); + + napi_value result{}; + CHECK_NAPI(napi_ext_run_script_buffer(m_env, &napiBuffer, sourceUrl, &result)); + + return result; +} + // Serializes script with the sourceUrl origin. vector NapiJsiRuntime::SerializeScript(napi_value script, const char *sourceUrl) { vector result; diff --git a/vnext/Microsoft.ReactNative.Cxx/JSI/NodeApiJsiRuntime.h b/vnext/Microsoft.ReactNative.Cxx/JSI/NodeApiJsiRuntime.h index b2ea468b7ac..f7c48de83ea 100644 --- a/vnext/Microsoft.ReactNative.Cxx/JSI/NodeApiJsiRuntime.h +++ b/vnext/Microsoft.ReactNative.Cxx/JSI/NodeApiJsiRuntime.h @@ -20,6 +20,54 @@ namespace Microsoft::JSI { /// std::unique_ptr __cdecl MakeNodeApiJsiRuntime(napi_env env) noexcept; +template +struct NativeObjectWrapper; + +template +struct NativeObjectWrapper> { + static napi_ext_native_data Wrap(std::unique_ptr &&obj) noexcept { + napi_ext_native_data nativeData{}; + nativeData.data = obj.release(); + nativeData.finalize_cb = [](napi_env /*env*/, void *data, void * /*finalizeHint*/) { + std::unique_ptr obj{reinterpret_cast(data)}; + }; + return nativeData; + } + + static T *Unwrap(napi_ext_native_data &nativeData) noexcept { + return reinterpret_cast(nativeData.data); + } +}; + +template +struct NativeObjectWrapper> { + static napi_ext_native_data Wrap(std::shared_ptr &&obj) noexcept { + static_assert( + sizeof(SharedPtrHolder) == sizeof(std::shared_ptr), "std::shared_ptr expected to have size of two pointers"); + SharedPtrHolder ptrHolder; + new (std::addressof(ptrHolder)) std::shared_ptr(std::move(obj)); + napi_ext_native_data nativeData{}; + nativeData.data = ptrHolder.ptr1; + nativeData.finalize_hint = ptrHolder.ptr2; + nativeData.finalize_cb = [](napi_env /*env*/, void *data, void *finalizeHint) { + SharedPtrHolder ptrHolder{data, finalizeHint}; + std::shared_ptr obj(std::move(*reinterpret_cast *>(std::addressof(ptrHolder)))); + }; + return nativeData; + } + + static std::shared_ptr Unwrap(napi_ext_native_data &nativeData) noexcept { + SharedPtrHolder ptrHolder{nativeData.data, nativeData.finalize_hint}; + return *reinterpret_cast *>(std::addressof(ptrHolder)); + } + + private: + struct SharedPtrHolder { + void *ptr1; + void *ptr2; + }; +}; + } // namespace Microsoft::JSI #endif // MICROSOFT_REACTNATIVE_JSI_NODEAPIJSIRUNTIME diff --git a/vnext/PropertySheets/JSEngine.props b/vnext/PropertySheets/JSEngine.props index 16dda15e7ef..7c25197c041 100644 --- a/vnext/PropertySheets/JSEngine.props +++ b/vnext/PropertySheets/JSEngine.props @@ -18,7 +18,7 @@ false false - 0.65.11 + 0.69.4 ReactNative.V8Jsi.Windows $(V8PackageName).UWP $(NuGetPackageRoot)\$(V8PackageName).$(V8Version) diff --git a/vnext/Shared/JSI/NapiJsiV8RuntimeHolder.cpp b/vnext/Shared/JSI/NapiJsiV8RuntimeHolder.cpp index 3f4a2cf6066..8c0255aa326 100644 --- a/vnext/Shared/JSI/NapiJsiV8RuntimeHolder.cpp +++ b/vnext/Shared/JSI/NapiJsiV8RuntimeHolder.cpp @@ -77,17 +77,20 @@ NapiJsiV8RuntimeHolder::NapiJsiV8RuntimeHolder( m_preparedScriptStore{std::move(preparedScriptStore)} {} void NapiJsiV8RuntimeHolder::InitRuntime() noexcept { - napi_env env{}; napi_ext_env_settings settings{}; settings.this_size = sizeof(settings); - settings.flags.enable_gc_api = true; if (m_debuggerPort > 0) settings.inspector_port = m_debuggerPort; settings.flags.enable_inspector = m_useDirectDebugger; settings.flags.wait_for_debugger = m_debuggerBreakOnNextLine; + // TODO: args.debuggerRuntimeName = debuggerRuntimeName_; settings.foreground_scheduler = &NapiJsiV8RuntimeHolder::ScheduleTaskCallback; + napi_ext_script_cache scriptCache = InitScriptCache(std::move(m_preparedScriptStore)); + settings.script_cache = &scriptCache; + + napi_env env{}; napi_ext_create_env(&settings, &env); // Associate environment to holder. napi_set_instance_data(env, this, nullptr /*finalize_cb*/, nullptr /*finalize_hint*/); @@ -96,6 +99,73 @@ void NapiJsiV8RuntimeHolder::InitRuntime() noexcept { m_ownThreadId = std::this_thread::get_id(); } +struct NodeApiJsiBuffer : facebook::jsi::Buffer { + static std::shared_ptr CreateJsiBuffer(const napi_ext_buffer *buffer) { + if (buffer && buffer->data) { + return std::shared_ptr(new NodeApiJsiBuffer(buffer)); + } else { + return {}; + } + } + + NodeApiJsiBuffer(const napi_ext_buffer *buffer) noexcept : buffer_(*buffer) {} + + ~NodeApiJsiBuffer() override { + if (buffer_.buffer_object.finalize_cb) { + buffer_.buffer_object.finalize_cb(nullptr, buffer_.buffer_object.data, buffer_.buffer_object.finalize_hint); + } + } + + const uint8_t *data() const override { + return buffer_.data; + } + + size_t size() const override { + return buffer_.byte_size; + } + + private: + napi_ext_buffer buffer_; +}; + +napi_ext_script_cache NapiJsiV8RuntimeHolder::InitScriptCache( + unique_ptr &&preparedScriptStore) noexcept { + napi_ext_script_cache scriptCache{}; + scriptCache.cache_object = NativeObjectWrapper>::Wrap(std::move(preparedScriptStore)); + scriptCache.load_cached_script = [](napi_env env, + napi_ext_script_cache *script_cache, + napi_ext_cached_script_metadata *script_metadata, + napi_ext_buffer *result) -> napi_status { + PreparedScriptStore *scriptStore = reinterpret_cast(script_cache->cache_object.data); + std::shared_ptr buffer = scriptStore->tryGetPreparedScript( + ScriptSignature{script_metadata->source_url, script_metadata->source_hash}, + JSRuntimeSignature{script_metadata->runtime_name, script_metadata->runtime_version}, + script_metadata->tag); + if (buffer) { + result->buffer_object = NativeObjectWrapper>::Wrap( + std::shared_ptr{buffer}); + result->data = buffer->data(); + result->byte_size = buffer->size(); + } else { + *result = napi_ext_buffer{}; + } + return napi_ok; + }; + scriptCache.store_cached_script = [](napi_env env, + napi_ext_script_cache *script_cache, + napi_ext_cached_script_metadata *script_metadata, + const napi_ext_buffer *buffer) -> napi_status { + PreparedScriptStore *scriptStore = reinterpret_cast(script_cache->cache_object.data); + scriptStore->persistPreparedScript( + NodeApiJsiBuffer::CreateJsiBuffer(buffer), + ScriptSignature{script_metadata->source_url, script_metadata->source_hash}, + JSRuntimeSignature{script_metadata->runtime_name, script_metadata->runtime_version}, + script_metadata->tag); + return napi_ok; + }; + return scriptCache; +} + #pragma region Microsoft::JSI::RuntimeHolderLazyInit facebook::react::JSIEngineOverride NapiJsiV8RuntimeHolder::getRuntimeType() noexcept { diff --git a/vnext/Shared/JSI/NapiJsiV8RuntimeHolder.h b/vnext/Shared/JSI/NapiJsiV8RuntimeHolder.h index eb4c0b3fb83..dfdd433a2dd 100644 --- a/vnext/Shared/JSI/NapiJsiV8RuntimeHolder.h +++ b/vnext/Shared/JSI/NapiJsiV8RuntimeHolder.h @@ -31,6 +31,8 @@ class NapiJsiV8RuntimeHolder : public Microsoft::JSI::RuntimeHolderLazyInit { void *finalizeHint); void InitRuntime() noexcept; + napi_ext_script_cache InitScriptCache( + std::unique_ptr &&preparedScriptStore) noexcept; std::shared_ptr m_runtime; std::shared_ptr m_jsQueue;