diff --git a/change/@react-native-windows-telemetry-778ea18a-3b80-476b-92e7-d46058128073.json b/change/@react-native-windows-telemetry-778ea18a-3b80-476b-92e7-d46058128073.json new file mode 100644 index 00000000000..8d57ff071d1 --- /dev/null +++ b/change/@react-native-windows-telemetry-778ea18a-3b80-476b-92e7-d46058128073.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Use latest Hermes ABI-safe API", + "packageName": "@react-native-windows/telemetry", + "email": "vmoroz@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/change/react-native-windows-af5ee70c-4767-4145-82a9-61331fa11ab8.json b/change/react-native-windows-af5ee70c-4767-4145-82a9-61331fa11ab8.json new file mode 100644 index 00000000000..2fd7a34e344 --- /dev/null +++ b/change/react-native-windows-af5ee70c-4767-4145-82a9-61331fa11ab8.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Fix Hermes sampling profiler", + "packageName": "react-native-windows", + "email": "vmoroz@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/packages/@react-native-windows/telemetry/src/test/projects/UsesPackagesConfig/packages.config b/packages/@react-native-windows/telemetry/src/test/projects/UsesPackagesConfig/packages.config index bfdf98cee24..36e557597d4 100644 --- a/packages/@react-native-windows/telemetry/src/test/projects/UsesPackagesConfig/packages.config +++ b/packages/@react-native-windows/telemetry/src/test/projects/UsesPackagesConfig/packages.config @@ -2,5 +2,5 @@ - + \ No newline at end of file diff --git a/packages/playground/windows/playground-composition/packages.config b/packages/playground/windows/playground-composition/packages.config index 5ae64eb1317..c48e07e6e6e 100644 --- a/packages/playground/windows/playground-composition/packages.config +++ b/packages/playground/windows/playground-composition/packages.config @@ -9,5 +9,5 @@ - + \ No newline at end of file diff --git a/packages/playground/windows/playground-win32/packages.config b/packages/playground/windows/playground-win32/packages.config index 4bfd09e1c4f..4a8c3a4ded2 100644 --- a/packages/playground/windows/playground-win32/packages.config +++ b/packages/playground/windows/playground-win32/packages.config @@ -9,5 +9,5 @@ - + \ No newline at end of file diff --git a/packages/playground/windows/playground/packages.config b/packages/playground/windows/playground/packages.config index 157347a6a7b..1b6be79b5ec 100644 --- a/packages/playground/windows/playground/packages.config +++ b/packages/playground/windows/playground/packages.config @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/vnext/Microsoft.ReactNative/IReactDispatcher.cpp b/vnext/Microsoft.ReactNative/IReactDispatcher.cpp index e45aa59eb71..5787721515c 100644 --- a/vnext/Microsoft.ReactNative/IReactDispatcher.cpp +++ b/vnext/Microsoft.ReactNative/IReactDispatcher.cpp @@ -124,6 +124,10 @@ void ReactDispatcher::InvokeElsePost(Mso::DispatchTask &&task) const noexcept { return jsThreadDispatcherProperty; } +/*static*/ IReactDispatcher ReactDispatcher::GetJSDispatcher(IReactPropertyBag const &properties) noexcept { + return properties.Get(JSDispatcherProperty()).try_as(); +} + /*static*/ IReactPropertyName ReactDispatcher::JSDispatcherTaskStartingEventName() noexcept { static IReactPropertyName jsThreadDispatcherProperty{ReactPropertyBagHelper::GetName( ReactPropertyBagHelper::GetNamespace(L"ReactNative.Dispatcher"), L"JSDispatcherTaskStartingEventName")}; diff --git a/vnext/Microsoft.ReactNative/IReactDispatcher.h b/vnext/Microsoft.ReactNative/IReactDispatcher.h index f13cfb0b4b4..52e749b782b 100644 --- a/vnext/Microsoft.ReactNative/IReactDispatcher.h +++ b/vnext/Microsoft.ReactNative/IReactDispatcher.h @@ -38,6 +38,7 @@ struct ReactDispatcher : implementsProperties()), nullptr); if (onDestroyed) { onDestroyed.Get()->Invoke(reactContext); } @@ -484,10 +485,14 @@ void ReactInstanceWin::Initialize() noexcept { std::unique_ptr preparedScriptStore = nullptr; switch (m_options.JsiEngine()) { - case JSIEngine::Hermes: - devSettings->jsiRuntimeHolder = + case JSIEngine::Hermes: { + auto hermesRuntimeHolder = std::make_shared(devSettings, m_jsMessageThread.Load()); + facebook::react::HermesRuntimeHolder::storeTo( + ReactPropertyBag(m_reactContext->Properties()), hermesRuntimeHolder); + devSettings->jsiRuntimeHolder = hermesRuntimeHolder; break; + } case JSIEngine::V8: #if defined(USE_V8) { diff --git a/vnext/Microsoft.ReactNative/Views/DevMenu.cpp b/vnext/Microsoft.ReactNative/Views/DevMenu.cpp index 98943f8b16a..d79f0f3662b 100644 --- a/vnext/Microsoft.ReactNative/Views/DevMenu.cpp +++ b/vnext/Microsoft.ReactNative/Views/DevMenu.cpp @@ -119,7 +119,7 @@ void DevMenuManager::CreateAndShowUI() noexcept { std::ostringstream os; if (Microsoft::ReactNative::HermesSamplingProfiler::IsStarted()) { - os << "Hermes Sampling profiler is running.. !"; + os << "Hermes Sampling profiler is running!"; } else { os << "Click to start."; } @@ -210,9 +210,9 @@ void DevMenuManager::CreateAndShowUI() noexcept { if (auto strongThis = wkThis.lock()) { strongThis->Hide(); if (!Microsoft::ReactNative::HermesSamplingProfiler::IsStarted()) { - Microsoft::ReactNative::HermesSamplingProfiler::Start(); + Microsoft::ReactNative::HermesSamplingProfiler::Start(strongThis->m_context); } else { - auto traceFilePath = co_await Microsoft::ReactNative::HermesSamplingProfiler::Stop(); + auto traceFilePath = co_await Microsoft::ReactNative::HermesSamplingProfiler::Stop(strongThis->m_context); auto uiDispatcher = React::implementation::ReactDispatcher::GetUIDispatcher(strongThis->m_context->Properties()); uiDispatcher.Post([traceFilePath]() { diff --git a/vnext/PropertySheets/JSEngine.props b/vnext/PropertySheets/JSEngine.props index 3418fb228c6..d62a7ac852b 100644 --- a/vnext/PropertySheets/JSEngine.props +++ b/vnext/PropertySheets/JSEngine.props @@ -14,7 +14,7 @@ false - 0.0.0-2302.1001-19052299 + 0.0.0-2302.1002-2d4bf1df $(PkgReactNative_Hermes_Windows) $(NuGetPackageRoot)\ReactNative.Hermes.Windows\$(HermesVersion) false diff --git a/vnext/Shared/HermesRuntimeHolder.cpp b/vnext/Shared/HermesRuntimeHolder.cpp index 5496151ad6d..d37af39f65b 100644 --- a/vnext/Shared/HermesRuntimeHolder.cpp +++ b/vnext/Shared/HermesRuntimeHolder.cpp @@ -3,38 +3,40 @@ #include "pch.h" -#include -#include +#include "HermesRuntimeHolder.h" #include +#include #include #include #include -#include "HermesRuntimeHolder.h" #include "HermesShim.h" #if defined(HERMES_ENABLE_DEBUGGER) #include #endif +#include +#include + using namespace facebook; using namespace Microsoft::ReactNative; -namespace facebook { -namespace react { +namespace React { +using namespace winrt::Microsoft::ReactNative; +} -namespace { +namespace facebook::react { -std::unique_ptr makeHermesRuntimeSystraced(bool enableDefaultCrashHandler) { - SystraceSection s("HermesExecutorFactory::makeHermesRuntimeSystraced"); - if (enableDefaultCrashHandler) { - return HermesShim::makeHermesRuntimeWithWER(); - } else { - auto runtimeConfig = ::hermes::vm::RuntimeConfig(); - return HermesShim::makeHermesRuntime(runtimeConfig); - } +React::ReactPropertyId>> +HermesRuntimeHolderProperty() noexcept { + static React::ReactPropertyId>> propId{ + L"ReactNative.HermesRuntimeHolder", L"HermesRuntimeHolder"}; + return propId; } +namespace { + #ifdef HERMES_ENABLE_DEBUGGER class HermesExecutorRuntimeAdapter final : public facebook::hermes::inspector::RuntimeAdapter { public: @@ -71,10 +73,19 @@ class HermesExecutorRuntimeAdapter final : public facebook::hermes::inspector::R }; #endif +std::shared_ptr makeHermesShimSystraced(bool enableDefaultCrashHandler) { + SystraceSection s("HermesExecutorFactory::makeHermesRuntimeSystraced"); + if (enableDefaultCrashHandler) { + return HermesShim::makeWithWER(); + } else { + return HermesShim::make(); + } +} + } // namespace void HermesRuntimeHolder::crashHandler(int fileDescriptor) noexcept { - HermesShim::hermesCrashHandler(*m_hermesRuntime, fileDescriptor); + m_hermesShim->dumpCrashData(fileDescriptor); } void HermesRuntimeHolder::teardown() noexcept { @@ -90,15 +101,9 @@ facebook::react::JSIEngineOverride HermesRuntimeHolder::getRuntimeType() noexcep } std::shared_ptr HermesRuntimeHolder::getRuntime() noexcept { - std::call_once(m_once_flag, [this]() { initRuntime(); }); - - if (!m_hermesRuntime) - std::terminate(); - - // Make sure that the runtime instance is not consumed from multiple threads. - if (m_own_thread_id != std::this_thread::get_id()) - std::terminate(); - + std::call_once(m_onceFlag, [this]() { initRuntime(); }); + VerifyElseCrash(m_hermesRuntime); + VerifyElseCrashSz(m_ownThreadId == std::this_thread::get_id(), "Must be accessed from JS thread."); return m_hermesRuntime; } @@ -109,11 +114,11 @@ HermesRuntimeHolder::HermesRuntimeHolder( void HermesRuntimeHolder::initRuntime() noexcept { auto devSettings = m_weakDevSettings.lock(); - if (!devSettings) - std::terminate(); + VerifyElseCrash(devSettings); - m_hermesRuntime = makeHermesRuntimeSystraced(devSettings->enableDefaultCrashHandler); - m_own_thread_id = std::this_thread::get_id(); + m_hermesShim = makeHermesShimSystraced(devSettings->enableDefaultCrashHandler); + m_hermesRuntime = m_hermesShim->getRuntime(); + m_ownThreadId = std::this_thread::get_id(); #ifdef HERMES_ENABLE_DEBUGGER if (devSettings->useDirectDebugger) { @@ -132,5 +137,23 @@ void HermesRuntimeHolder::initRuntime() noexcept { errorPrototype.setProperty(*m_hermesRuntime, "jsEngine", "hermes"); } -} // namespace react -} // namespace facebook +std::shared_ptr HermesRuntimeHolder::loadFrom( + React::ReactPropertyBag const &propertyBag) noexcept { + return *(propertyBag.Get(HermesRuntimeHolderProperty())); +} + +void HermesRuntimeHolder::storeTo( + React::ReactPropertyBag const &propertyBag, + std::shared_ptr const &holder) noexcept { + propertyBag.Set(HermesRuntimeHolderProperty(), holder); +} + +void HermesRuntimeHolder::addToProfiling() const noexcept { + m_hermesShim->addToProfiling(); +} + +void HermesRuntimeHolder::removeFromProfiling() const noexcept { + m_hermesShim->removeFromProfiling(); +} + +} // namespace facebook::react diff --git a/vnext/Shared/HermesRuntimeHolder.h b/vnext/Shared/HermesRuntimeHolder.h index a9d6befb34d..9a5b357742d 100644 --- a/vnext/Shared/HermesRuntimeHolder.h +++ b/vnext/Shared/HermesRuntimeHolder.h @@ -8,37 +8,52 @@ #include #include +#include namespace facebook::hermes { class HermesRuntime; } -namespace facebook { -namespace react { +namespace Microsoft::ReactNative { +class HermesShim; +} + +namespace facebook::react { + +class MessageQueueThread; class HermesRuntimeHolder : public Microsoft::JSI::RuntimeHolderLazyInit { - public: + public: // RuntimeHolderLazyInit implementation. std::shared_ptr getRuntime() noexcept override; facebook::react::JSIEngineOverride getRuntimeType() noexcept override; - void crashHandler(int fileDescriptor) noexcept override; - void teardown() noexcept override; + public: HermesRuntimeHolder( std::shared_ptr devSettings, std::shared_ptr jsQueue) noexcept; + static std::shared_ptr loadFrom( + winrt::Microsoft::ReactNative::ReactPropertyBag const &propertyBag) noexcept; + + static void storeTo( + winrt::Microsoft::ReactNative::ReactPropertyBag const &propertyBag, + std::shared_ptr const &holder) noexcept; + + void addToProfiling() const noexcept; + void removeFromProfiling() const noexcept; + private: void initRuntime() noexcept; - std::shared_ptr m_hermesRuntime; - - std::once_flag m_once_flag; - std::thread::id m_own_thread_id; + private: + std::shared_ptr m_hermesShim; + std::shared_ptr m_hermesRuntime; + std::once_flag m_onceFlag{}; + std::thread::id m_ownThreadId{}; std::weak_ptr m_weakDevSettings; std::shared_ptr m_jsQueue; }; -} // namespace react -} // namespace facebook +} // namespace facebook::react diff --git a/vnext/Shared/HermesSamplingProfiler.cpp b/vnext/Shared/HermesSamplingProfiler.cpp index 52f0f8128cc..8ed58e08fcf 100644 --- a/vnext/Shared/HermesSamplingProfiler.cpp +++ b/vnext/Shared/HermesSamplingProfiler.cpp @@ -7,13 +7,46 @@ #include #include +#include "HermesRuntimeHolder.h" #include "HermesSamplingProfiler.h" #include "HermesShim.h" +#include "IReactDispatcher.h" +#include "ReactPropertyBag.h" #include "Utils.h" +using namespace winrt::Microsoft::ReactNative; +using namespace facebook::react; + namespace Microsoft::ReactNative { namespace { + +// Implements an awaiter for Mso::DispatchQueue +auto resume_in_dispatcher(const IReactDispatcher &dispatcher) noexcept { + struct awaitable { + awaitable(const IReactDispatcher &dispatcher) noexcept : dispatcher_(dispatcher) {} + + bool await_ready() const noexcept { + return false; + } + + void await_resume() const noexcept {} + + void await_suspend(std::experimental::coroutine_handle<> resume) noexcept { + callback_ = [context = resume.address()]() noexcept { + std::experimental::coroutine_handle<>::from_address(context)(); + }; + dispatcher_.Post(std::move(callback_)); + } + + private: + IReactDispatcher dispatcher_; + ReactDispatcherCallback callback_; + }; + + return awaitable{dispatcher}; +} + std::future getTraceFilePath() noexcept { auto hermesDataPath = co_await Microsoft::React::getApplicationDataPath(L"Hermes"); std::ostringstream os; @@ -23,18 +56,27 @@ std::future getTraceFilePath() noexcept { os << hermesDataPath << "\\cpu_" << now << ".cpuprofile"; co_return os.str(); } + } // namespace -bool HermesSamplingProfiler::s_isStarted = false; +std::atomic_bool HermesSamplingProfiler::s_isStarted{false}; std::string HermesSamplingProfiler::s_lastTraceFilePath; std::string HermesSamplingProfiler::GetLastTraceFilePath() noexcept { return s_lastTraceFilePath; } -winrt::fire_and_forget HermesSamplingProfiler::Start() noexcept { - if (!s_isStarted) { - s_isStarted = true; +winrt::fire_and_forget HermesSamplingProfiler::Start( + Mso::CntPtr const &reactContext) noexcept { + bool expectedIsStarted = false; + if (s_isStarted.compare_exchange_strong(expectedIsStarted, true)) { + IReactDispatcher jsDispatcher = implementation::ReactDispatcher::GetJSDispatcher(reactContext->Properties()); + ReactPropertyBag propertyBag = ReactPropertyBag(reactContext->Properties()); + + co_await resume_in_dispatcher(jsDispatcher); + std::shared_ptr hermesRuntimeHolder = HermesRuntimeHolder::loadFrom(propertyBag); + hermesRuntimeHolder->addToProfiling(); + co_await winrt::resume_background(); HermesShim::enableSamplingProfiler(); } @@ -42,20 +84,31 @@ winrt::fire_and_forget HermesSamplingProfiler::Start() noexcept { co_return; } -std::future HermesSamplingProfiler::Stop() noexcept { - if (s_isStarted) { - s_isStarted = false; +std::future HermesSamplingProfiler::Stop( + Mso::CntPtr const &reactContext) noexcept { + bool expectedIsStarted = true; + if (s_isStarted.compare_exchange_strong(expectedIsStarted, false)) { + IReactDispatcher jsDispatcher = implementation::ReactDispatcher::GetJSDispatcher(reactContext->Properties()); + ReactPropertyBag propertyBag = ReactPropertyBag(reactContext->Properties()); + co_await winrt::resume_background(); HermesShim::disableSamplingProfiler(); + s_lastTraceFilePath = co_await getTraceFilePath(); HermesShim::dumpSampledTraceToFile(s_lastTraceFilePath); + + co_await resume_in_dispatcher(jsDispatcher); + std::shared_ptr hermesRuntimeHolder = HermesRuntimeHolder::loadFrom(propertyBag); + hermesRuntimeHolder->removeFromProfiling(); + + co_await winrt::resume_background(); } co_return s_lastTraceFilePath; } bool HermesSamplingProfiler::IsStarted() noexcept { - return s_isStarted; + return s_isStarted.load(); } } // namespace Microsoft::ReactNative diff --git a/vnext/Shared/HermesSamplingProfiler.h b/vnext/Shared/HermesSamplingProfiler.h index d6d06bfedaf..8b7dec02a1e 100644 --- a/vnext/Shared/HermesSamplingProfiler.h +++ b/vnext/Shared/HermesSamplingProfiler.h @@ -3,19 +3,21 @@ #pragma once +#include +#include #include namespace Microsoft::ReactNative { class HermesSamplingProfiler final { public: - static winrt::fire_and_forget Start() noexcept; - static std::future Stop() noexcept; + static winrt::fire_and_forget Start(Mso::CntPtr const &reactContext) noexcept; + static std::future Stop(Mso::CntPtr const &reactContext) noexcept; static std::string GetLastTraceFilePath() noexcept; static bool IsStarted() noexcept; private: - static bool s_isStarted; + static std::atomic_bool s_isStarted; static std::string s_lastTraceFilePath; }; diff --git a/vnext/Shared/HermesShim.cpp b/vnext/Shared/HermesShim.cpp index b33709e8c32..44a50f29092 100644 --- a/vnext/Shared/HermesShim.cpp +++ b/vnext/Shared/HermesShim.cpp @@ -4,115 +4,119 @@ #include "HermesShim.h" #include "Crash.h" -namespace Microsoft::ReactNative::HermesShim { - -static HMODULE s_hermesModule{nullptr}; -static decltype(&facebook::hermes::makeHermesRuntime) s_makeHermesRuntime{nullptr}; -static decltype(&facebook::hermes::HermesRuntime::enableSamplingProfiler) s_enableSamplingProfiler{nullptr}; -static decltype(&facebook::hermes::HermesRuntime::disableSamplingProfiler) s_disableSamplingProfiler{nullptr}; -static decltype(&facebook::hermes::HermesRuntime::dumpSampledTraceToFile) s_dumpSampledTraceToFile{nullptr}; -static decltype(&facebook::hermes::makeHermesRuntimeWithWER) s_makeHermesRuntimeWithWER{nullptr}; -static decltype(&facebook::hermes::hermesCrashHandler) s_hermesCrashHandler{nullptr}; - -#if _M_X64 -constexpr const char *makeHermesRuntimeSymbol = - "?makeHermesRuntime@hermes@facebook@@YA?AV?$unique_ptr@VHermesRuntime@hermes@facebook@@U?$default_delete@VHermesRuntime@hermes@facebook@@@std@@@std@@AEBVRuntimeConfig@vm@1@@Z"; -constexpr const char *enableSamlingProfilerSymbol = "?enableSamplingProfiler@HermesRuntime@hermes@facebook@@SAXXZ"; -constexpr const char *disableSamlingProfilerSymbol = "?disableSamplingProfiler@HermesRuntime@hermes@facebook@@SAXXZ"; -constexpr const char *dumpSampledTraceToFileSymbol = - "?dumpSampledTraceToFile@HermesRuntime@hermes@facebook@@SAXAEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z"; -constexpr const char *makeHermesRuntimeWithWERSymbol = - "?makeHermesRuntimeWithWER@hermes@facebook@@YA?AV?$unique_ptr@VHermesRuntime@hermes@facebook@@U?$default_delete@VHermesRuntime@hermes@facebook@@@std@@@std@@XZ"; -constexpr const char *hermesCrashHandlerSymbol = "?hermesCrashHandler@hermes@facebook@@YAXAEAVHermesRuntime@12@H@Z"; -#endif - -#if _M_ARM64 -constexpr const char *makeHermesRuntimeSymbol = - "?makeHermesRuntime@hermes@facebook@@YA?AV?$unique_ptr@VHermesRuntime@hermes@facebook@@U?$default_delete@VHermesRuntime@hermes@facebook@@@std@@@std@@AEBVRuntimeConfig@vm@1@@Z"; -constexpr const char *enableSamlingProfilerSymbol = "?enableSamplingProfiler@HermesRuntime@hermes@facebook@@SAXXZ"; -constexpr const char *disableSamlingProfilerSymbol = "?disableSamplingProfiler@HermesRuntime@hermes@facebook@@SAXXZ"; -constexpr const char *dumpSampledTraceToFileSymbol = - "?dumpSampledTraceToFile@HermesRuntime@hermes@facebook@@SAXAEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z"; -constexpr const char *makeHermesRuntimeWithWERSymbol = - "?makeHermesRuntimeWithWER@hermes@facebook@@YA?AV?$unique_ptr@VHermesRuntime@hermes@facebook@@U?$default_delete@VHermesRuntime@hermes@facebook@@@std@@@std@@XZ"; -constexpr const char *hermesCrashHandlerSymbol = "?hermesCrashHandler@hermes@facebook@@YAXAEAVHermesRuntime@12@H@Z"; -#endif - -#if _M_IX86 -constexpr const char *makeHermesRuntimeSymbol = - "?makeHermesRuntime@hermes@facebook@@YA?AV?$unique_ptr@VHermesRuntime@hermes@facebook@@U?$default_delete@VHermesRuntime@hermes@facebook@@@std@@@std@@ABVRuntimeConfig@vm@1@@Z"; -constexpr const char *enableSamlingProfilerSymbol = "?enableSamplingProfiler@HermesRuntime@hermes@facebook@@SAXXZ"; -constexpr const char *disableSamlingProfilerSymbol = "?disableSamplingProfiler@HermesRuntime@hermes@facebook@@SAXXZ"; -constexpr const char *dumpSampledTraceToFileSymbol = - "?dumpSampledTraceToFile@HermesRuntime@hermes@facebook@@SAXABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z"; -constexpr const char *makeHermesRuntimeWithWERSymbol = - "?makeHermesRuntimeWithWER@hermes@facebook@@YA?AV?$unique_ptr@VHermesRuntime@hermes@facebook@@U?$default_delete@VHermesRuntime@hermes@facebook@@@std@@@std@@XZ"; -constexpr const char *hermesCrashHandlerSymbol = "?hermesCrashHandler@hermes@facebook@@YAXAAVHermesRuntime@12@H@Z"; -#endif - -static std::once_flag s_hermesLoading; - -static void EnsureHermesLoaded() noexcept { +#define DECLARE_SYMBOL(symbol, importedSymbol) \ + decltype(&importedSymbol) s_##symbol{}; \ + constexpr const char symbol##Symbol[] = #importedSymbol; + +#define INIT_SYMBOL(symbol) \ + s_##symbol = reinterpret_cast(GetProcAddress(s_hermesModule, symbol##Symbol)); \ + VerifyElseCrash(s_##symbol); + +#define CRASH_ON_ERROR(result) VerifyElseCrash(result == hermes_ok); + +namespace Microsoft::ReactNative { + +namespace { + +DECLARE_SYMBOL(createRuntime, hermes_create_runtime); +DECLARE_SYMBOL(createRuntimeWithWER, hermes_create_runtime_with_wer); +DECLARE_SYMBOL(deleteRuntime, hermes_delete_runtime); +DECLARE_SYMBOL(getNonAbiSafeRuntime, hermes_get_non_abi_safe_runtime); +DECLARE_SYMBOL(dumpCrashData, hermes_dump_crash_data); +DECLARE_SYMBOL(samplingProfilerEnable, hermes_sampling_profiler_enable); +DECLARE_SYMBOL(samplingProfilerDisable, hermes_sampling_profiler_disable); +DECLARE_SYMBOL(samplingProfilerAdd, hermes_sampling_profiler_add); +DECLARE_SYMBOL(samplingProfilerRemove, hermes_sampling_profiler_remove); +DECLARE_SYMBOL(samplingProfilerDumpToFile, hermes_sampling_profiler_dump_to_file); + +HMODULE s_hermesModule{}; +std::once_flag s_hermesLoading; + +void EnsureHermesLoaded() noexcept { std::call_once(s_hermesLoading, []() { VerifyElseCrashSz(!s_hermesModule, "Invalid state: \"hermes.dll\" being loaded again."); s_hermesModule = LoadLibrary(L"hermes.dll"); VerifyElseCrashSz(s_hermesModule, "Could not load \"hermes.dll\""); - s_makeHermesRuntime = - reinterpret_cast(GetProcAddress(s_hermesModule, makeHermesRuntimeSymbol)); - VerifyElseCrash(s_makeHermesRuntime); + INIT_SYMBOL(createRuntime); + INIT_SYMBOL(createRuntimeWithWER); + INIT_SYMBOL(deleteRuntime); + INIT_SYMBOL(getNonAbiSafeRuntime); + INIT_SYMBOL(dumpCrashData); + INIT_SYMBOL(samplingProfilerEnable); + INIT_SYMBOL(samplingProfilerDisable); + INIT_SYMBOL(samplingProfilerAdd); + INIT_SYMBOL(samplingProfilerRemove); + INIT_SYMBOL(samplingProfilerDumpToFile); + }); +} + +struct RuntimeDeleter { + RuntimeDeleter(std::shared_ptr &&hermesShimPtr) noexcept + : hermesShimPtr_(std::move(hermesShimPtr)) {} - s_enableSamplingProfiler = reinterpret_cast( - GetProcAddress(s_hermesModule, enableSamlingProfilerSymbol)); - VerifyElseCrash(s_enableSamplingProfiler); + void operator()(facebook::hermes::HermesRuntime * /*runtime*/) { + // Do nothing. Instead, we rely on the RuntimeDeleter destructor. + } - s_disableSamplingProfiler = reinterpret_cast( - GetProcAddress(s_hermesModule, disableSamlingProfilerSymbol)); - VerifyElseCrash(s_disableSamplingProfiler); + private: + std::shared_ptr hermesShimPtr_; +}; - s_dumpSampledTraceToFile = reinterpret_cast( - GetProcAddress(s_hermesModule, dumpSampledTraceToFileSymbol)); - VerifyElseCrash(s_dumpSampledTraceToFile); +} // namespace - s_makeHermesRuntimeWithWER = reinterpret_cast( - GetProcAddress(s_hermesModule, makeHermesRuntimeWithWERSymbol)); - VerifyElseCrash(s_makeHermesRuntimeWithWER); +HermesShim::HermesShim(hermes_runtime runtime) noexcept : runtime_(runtime) { + CRASH_ON_ERROR(s_getNonAbiSafeRuntime(runtime_, reinterpret_cast(&nonAbiSafeRuntime_))); +} - s_hermesCrashHandler = - reinterpret_cast(GetProcAddress(s_hermesModule, hermesCrashHandlerSymbol)); - VerifyElseCrash(s_hermesCrashHandler); - }); +HermesShim::~HermesShim() { + CRASH_ON_ERROR(s_deleteRuntime(runtime_)); } -std::unique_ptr makeHermesRuntime(const hermes::vm::RuntimeConfig &runtimeConfig) { +/*static*/ std::shared_ptr HermesShim::make() noexcept { EnsureHermesLoaded(); - return s_makeHermesRuntime(runtimeConfig); + hermes_runtime runtime{}; + CRASH_ON_ERROR(s_createRuntime(&runtime)); + return std::make_shared(runtime); } -void enableSamplingProfiler() { +/*static*/ +std::shared_ptr HermesShim::makeWithWER() noexcept { EnsureHermesLoaded(); - s_enableSamplingProfiler(); + hermes_runtime runtime{}; + CRASH_ON_ERROR(s_createRuntimeWithWER(&runtime)); + return std::make_shared(runtime); } -void disableSamplingProfiler() { - EnsureHermesLoaded(); - s_disableSamplingProfiler(); +std::shared_ptr HermesShim::getRuntime() const noexcept { + return std::shared_ptr(nonAbiSafeRuntime_, RuntimeDeleter(shared_from_this())); } -void dumpSampledTraceToFile(const std::string &fileName) { +void HermesShim::dumpCrashData(int fileDescriptor) const noexcept { + CRASH_ON_ERROR(s_dumpCrashData(runtime_, fileDescriptor)); +} + +/*static*/ void HermesShim::enableSamplingProfiler() noexcept { EnsureHermesLoaded(); - s_dumpSampledTraceToFile(fileName); + CRASH_ON_ERROR(s_samplingProfilerEnable()); } -std::unique_ptr makeHermesRuntimeWithWER() { +/*static*/ void HermesShim::disableSamplingProfiler() noexcept { EnsureHermesLoaded(); - return s_makeHermesRuntimeWithWER(); + CRASH_ON_ERROR(s_samplingProfilerDisable()); } -void hermesCrashHandler(facebook::hermes::HermesRuntime &runtime, int fileDescriptor) { +/*static*/ void HermesShim::dumpSampledTraceToFile(const std::string &fileName) noexcept { EnsureHermesLoaded(); - s_hermesCrashHandler(runtime, fileDescriptor); + CRASH_ON_ERROR(s_samplingProfilerDumpToFile(fileName.c_str())); +} +void HermesShim::addToProfiling() const noexcept { + CRASH_ON_ERROR(s_samplingProfilerAdd(runtime_)); +} + +void HermesShim::removeFromProfiling() const noexcept { + CRASH_ON_ERROR(s_samplingProfilerRemove(runtime_)); } -} // namespace Microsoft::ReactNative::HermesShim +} // namespace Microsoft::ReactNative diff --git a/vnext/Shared/HermesShim.h b/vnext/Shared/HermesShim.h index 2bd285024b4..04ca1ef72ae 100644 --- a/vnext/Shared/HermesShim.h +++ b/vnext/Shared/HermesShim.h @@ -9,13 +9,33 @@ //! use pure delay-loading to achieve this, since WACK will detect the //! non-present DLL. Functions in this namespace shim to the Hermes DLL via //! GetProcAddress. -namespace Microsoft::ReactNative::HermesShim { +namespace Microsoft::ReactNative { -std::unique_ptr makeHermesRuntime(const hermes::vm::RuntimeConfig &runtimeConfig); -void enableSamplingProfiler(); -void disableSamplingProfiler(); -void dumpSampledTraceToFile(const std::string &fileName); -std::unique_ptr makeHermesRuntimeWithWER(); -void hermesCrashHandler(facebook::hermes::HermesRuntime &runtime, int fileDescriptor); +class HermesShim : public std::enable_shared_from_this { + public: + HermesShim(hermes_runtime runtimeAbiPtr) noexcept; + ~HermesShim(); -} // namespace Microsoft::ReactNative::HermesShim + static std::shared_ptr make() noexcept; + static std::shared_ptr makeWithWER() noexcept; + + std::shared_ptr getRuntime() const noexcept; + + void dumpCrashData(int fileDescriptor) const noexcept; + + static void enableSamplingProfiler() noexcept; + static void disableSamplingProfiler() noexcept; + static void dumpSampledTraceToFile(const std::string &fileName) noexcept; + void addToProfiling() const noexcept; + void removeFromProfiling() const noexcept; + + HermesShim(const HermesShim &) = delete; + HermesShim &operator=(const HermesShim &) = delete; + + private: + // It must be a raw pointer to avoid circular reference. + facebook::hermes::HermesRuntime *nonAbiSafeRuntime_{}; + hermes_runtime runtime_{}; +}; + +} // namespace Microsoft::ReactNative diff --git a/vnext/Shared/JSI/RuntimeHolder.h b/vnext/Shared/JSI/RuntimeHolder.h index b3642fcfabe..69a95b5a1cd 100644 --- a/vnext/Shared/JSI/RuntimeHolder.h +++ b/vnext/Shared/JSI/RuntimeHolder.h @@ -11,7 +11,7 @@ namespace Microsoft::JSI { // a. lazily create a JSI Runtime on the first call to getRuntime // b. subsequent calls to getRuntime should return the Runtime created in (a) -// Note :: All calls to getRuntime() should happen on the same thread unless you are sure that +// Note: all calls to getRuntime() should happen on the same thread unless you are sure that // the underlying Runtime instance is thread safe. struct RuntimeHolderLazyInit { @@ -21,7 +21,7 @@ struct RuntimeHolderLazyInit { virtual void teardown() noexcept {}; // You can call this when a crash happens to attempt recording additional data - // The fd supplied is a raw file stream an implementation might write JSON to + // The fileDescriptor supplied is a raw file stream an implementation might write JSON to. virtual void crashHandler(int fileDescriptor) noexcept {}; };