From c09340e73ee52ee10964b0bdf9d1dbde85733746 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Thu, 1 May 2025 00:22:44 -0700 Subject: [PATCH] Use std::variant, remove heap allocations (#50847) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/50847 # Changelog: [Internal] We have different types of call frames, right now we defined 4 of them. Previously, they would descend from `ProfileSampleCallStackFrame`, which has `kind_` field that can be used for determining the type of the call frame. We were using polymorphism later on React Native side to cast from base type to derived. Instead of this, and instead of doing heap allocations for potentially tens of thousands of objects, we will use std::variant for storing frames in a single container and them distinguishing them. This fixes memory leaks caused by new-ing objects and not clearing out when Profile is destroyed. Reviewed By: dannysu Differential Revision: D72803934 fbshipit-source-id: a279c6d68c7c2628f0eab585f33137222ad9e3f1 --- ...HermesRuntimeSamplingProfileSerializer.cpp | 201 +++++++++--------- 1 file changed, 98 insertions(+), 103 deletions(-) diff --git a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeSamplingProfileSerializer.cpp b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeSamplingProfileSerializer.cpp index a7375112f0c..0c654a303a5 100644 --- a/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeSamplingProfileSerializer.cpp +++ b/packages/react-native/ReactCommon/hermes/inspector-modern/chrome/HermesRuntimeSamplingProfileSerializer.cpp @@ -5,12 +5,16 @@ * LICENSE file in the root directory of this source tree. */ +#include + #include "HermesRuntimeSamplingProfileSerializer.h" namespace facebook::react::jsinspector_modern::tracing { namespace { +namespace fhsp = facebook::hermes::sampling_profiler; + /// Fallback script ID for call frames, when Hermes didn't provide one or when /// this frame is part of the VM, like native functions, used for parity with /// Chromium + V8. @@ -21,123 +25,114 @@ const std::string GARBAGE_COLLECTOR_FRAME_NAME = "(garbage collector)"; /// Filters out Hermes Suspend frames related to Debugger. /// Even though Debugger domain is expected to be disabled, Hermes might run /// Debugger loop while recording sampling profile. We only allow GC frames. -bool shouldIgnoreHermesFrame( - hermes::sampling_profiler::ProfileSampleCallStackFrame* hermesFrame) { - if (hermesFrame->getKind() != - hermes::sampling_profiler::ProfileSampleCallStackFrame::Kind::Suspend) { - return false; - } - - auto* suspendFrame = static_cast< - hermes::sampling_profiler::ProfileSampleCallStackSuspendFrame*>( - hermesFrame); - auto suspendFrameKind = suspendFrame->getSuspendFrameKind(); - return suspendFrameKind != - hermes::sampling_profiler::ProfileSampleCallStackSuspendFrame:: - SuspendFrameKind::GC; +inline bool shouldIgnoreHermesFrame( + const fhsp::ProfileSampleCallStackSuspendFrame& suspendFrame) { + return suspendFrame.getSuspendFrameKind() != + fhsp::ProfileSampleCallStackSuspendFrame::SuspendFrameKind::GC; } -RuntimeSamplingProfile::SampleCallStackFrame convertHermesFrameToTracingFrame( - hermes::sampling_profiler::ProfileSampleCallStackFrame* hermesFrame) { - switch (hermesFrame->getKind()) { - case hermes::sampling_profiler::ProfileSampleCallStackFrame::Kind:: - JSFunction: { - auto* jsFunctionFrame = static_cast< - hermes::sampling_profiler::ProfileSampleCallStackJSFunctionFrame*>( - hermesFrame); - return RuntimeSamplingProfile::SampleCallStackFrame{ - RuntimeSamplingProfile::SampleCallStackFrame::Kind::JSFunction, - jsFunctionFrame->hasScriptId() ? jsFunctionFrame->getScriptId() - : FALLBACK_SCRIPT_ID, - jsFunctionFrame->getFunctionName(), - jsFunctionFrame->hasUrl() - ? std::optional{jsFunctionFrame->getUrl()} - : std::nullopt, - jsFunctionFrame->hasLineNumber() - ? std::optional{jsFunctionFrame->getLineNumber() - 1} - // Hermes VM keeps line numbers as 1-based. Convert to - // 0-based. - : std::nullopt, - jsFunctionFrame->hasColumnNumber() - ? std::optional{jsFunctionFrame->getColumnNumber() - 1} - // Hermes VM keeps column numbers as 1-based. Convert to - // 0-based. - : std::nullopt, - }; - } - case hermes::sampling_profiler::ProfileSampleCallStackFrame::Kind:: - NativeFunction: { - auto* nativeFunctionFrame = - static_cast( - hermesFrame); +RuntimeSamplingProfile::SampleCallStackFrame convertNativeHermesFrame( + const fhsp::ProfileSampleCallStackNativeFunctionFrame& frame) { + return RuntimeSamplingProfile::SampleCallStackFrame{ + RuntimeSamplingProfile::SampleCallStackFrame::Kind::NativeFunction, + FALLBACK_SCRIPT_ID, // JavaScript Runtime defines the implementation + // for native function, no script ID to reference. + frame.getFunctionName(), + }; +} - return RuntimeSamplingProfile::SampleCallStackFrame{ - RuntimeSamplingProfile::SampleCallStackFrame::Kind::NativeFunction, - FALLBACK_SCRIPT_ID, // JavaScript Runtime defines the implementation - // for native function, no script ID to reference. - nativeFunctionFrame->getFunctionName(), - }; - } - case hermes::sampling_profiler::ProfileSampleCallStackFrame::Kind:: - HostFunction: { - auto* hostFunctionFrame = static_cast< - hermes::sampling_profiler::ProfileSampleCallStackHostFunctionFrame*>( - hermesFrame); +RuntimeSamplingProfile::SampleCallStackFrame convertHostFunctionHermesFrame( + const fhsp::ProfileSampleCallStackHostFunctionFrame& frame) { + return RuntimeSamplingProfile::SampleCallStackFrame{ + RuntimeSamplingProfile::SampleCallStackFrame::Kind::HostFunction, + FALLBACK_SCRIPT_ID, // JavaScript Runtime defines the implementation + // for host function, no script ID to reference. + frame.getFunctionName(), + }; +} - return RuntimeSamplingProfile::SampleCallStackFrame{ - RuntimeSamplingProfile::SampleCallStackFrame::Kind::HostFunction, - FALLBACK_SCRIPT_ID, // JavaScript Runtime defines the implementation - // for host function, no script ID to reference. - hostFunctionFrame->getFunctionName(), - }; - } - case hermes::sampling_profiler::ProfileSampleCallStackFrame::Kind:: - Suspend: { - auto* suspendFrame = static_cast< - hermes::sampling_profiler::ProfileSampleCallStackSuspendFrame*>( - hermesFrame); - auto suspendFrameKind = suspendFrame->getSuspendFrameKind(); - if (suspendFrameKind == - hermes::sampling_profiler::ProfileSampleCallStackSuspendFrame:: - SuspendFrameKind::GC) { - return RuntimeSamplingProfile::SampleCallStackFrame{ - RuntimeSamplingProfile::SampleCallStackFrame::Kind:: - GarbageCollector, - FALLBACK_SCRIPT_ID, // GC frames are part of the VM, no script ID to - // reference. - GARBAGE_COLLECTOR_FRAME_NAME, - }; - } - - // We should have filtered out Debugger Suspend frames before in - // shouldFilterOutHermesFrame(). - throw std::logic_error{ - "Unexpected Suspend frame found in Hermes call stack"}; - } - - default: - throw std::logic_error{"Unknown Hermes stack frame kind"}; +RuntimeSamplingProfile::SampleCallStackFrame convertSuspendHermesFrame( + const fhsp::ProfileSampleCallStackSuspendFrame& frame) { + if (frame.getSuspendFrameKind() == + fhsp::ProfileSampleCallStackSuspendFrame::SuspendFrameKind::GC) { + return RuntimeSamplingProfile::SampleCallStackFrame{ + RuntimeSamplingProfile::SampleCallStackFrame::Kind::GarbageCollector, + FALLBACK_SCRIPT_ID, // GC frames are part of the VM, no script ID to + // reference. + GARBAGE_COLLECTOR_FRAME_NAME, + }; } + + // We should have filtered out Debugger Suspend frames before in + // shouldFilterOutHermesFrame(). + throw std::logic_error{"Unexpected Suspend frame found in Hermes call stack"}; +} + +RuntimeSamplingProfile::SampleCallStackFrame convertJSFunctionHermesFrame( + const fhsp::ProfileSampleCallStackJSFunctionFrame& frame) { + return RuntimeSamplingProfile::SampleCallStackFrame{ + RuntimeSamplingProfile::SampleCallStackFrame::Kind::JSFunction, + frame.hasScriptId() ? frame.getScriptId() : FALLBACK_SCRIPT_ID, + frame.getFunctionName(), + frame.hasUrl() ? std::optional{frame.getUrl()} + : std::nullopt, + frame.hasLineNumber() ? std::optional{frame.getLineNumber() - 1} + // Hermes VM keeps line numbers as 1-based. Convert + // to 0-based. + : std::nullopt, + frame.hasColumnNumber() + ? std::optional{frame.getColumnNumber() - 1} + // Hermes VM keeps column numbers as 1-based. Convert to + // 0-based. + : std::nullopt, + }; } RuntimeSamplingProfile::Sample convertHermesSampleToTracingSample( - const hermes::sampling_profiler::ProfileSample& hermesSample) { + const fhsp::ProfileSample& hermesSample) { uint64_t reconciledTimestamp = hermesSample.getTimestamp(); - std::vector - hermesSampleCallStack = hermesSample.getCallStack(); + const std::vector& hermesSampleCallStack = + hermesSample.getCallStack(); std::vector reconciledSampleCallStack; reconciledSampleCallStack.reserve(hermesSampleCallStack.size()); - for (auto* hermesFrame : hermesSampleCallStack) { - if (shouldIgnoreHermesFrame(hermesFrame)) { - continue; + for (const auto& hermesFrame : hermesSampleCallStack) { + if (std::holds_alternative( + hermesFrame)) { + const auto& suspendFrame = + std::get(hermesFrame); + if (shouldIgnoreHermesFrame(suspendFrame)) { + continue; + } + + reconciledSampleCallStack.emplace_back( + convertSuspendHermesFrame(suspendFrame)); + } else if (std::holds_alternative< + fhsp::ProfileSampleCallStackNativeFunctionFrame>( + hermesFrame)) { + const auto& nativeFunctionFrame = + std::get( + hermesFrame); + reconciledSampleCallStack.emplace_back( + convertNativeHermesFrame(nativeFunctionFrame)); + } else if (std::holds_alternative< + fhsp::ProfileSampleCallStackHostFunctionFrame>( + hermesFrame)) { + const auto& hostFunctionFrame = + std::get(hermesFrame); + reconciledSampleCallStack.emplace_back( + convertHostFunctionHermesFrame(hostFunctionFrame)); + } else if (std::holds_alternative< + fhsp::ProfileSampleCallStackJSFunctionFrame>(hermesFrame)) { + const auto& jsFunctionFrame = + std::get(hermesFrame); + reconciledSampleCallStack.emplace_back( + convertJSFunctionHermesFrame(jsFunctionFrame)); + } else { + throw std::logic_error{"Unknown Hermes stack frame kind"}; } - RuntimeSamplingProfile::SampleCallStackFrame reconciledFrame = - convertHermesFrameToTracingFrame(hermesFrame); - reconciledSampleCallStack.push_back(std::move(reconciledFrame)); } return RuntimeSamplingProfile::Sample{ @@ -156,7 +151,7 @@ HermesRuntimeSamplingProfileSerializer::serializeToTracingSamplingProfile( std::vector reconciledSamples; reconciledSamples.reserve(hermesSamples.size()); - for (auto& hermesSample : hermesSamples) { + for (const auto& hermesSample : hermesSamples) { RuntimeSamplingProfile::Sample reconciledSample = convertHermesSampleToTracingSample(hermesSample); reconciledSamples.push_back(std::move(reconciledSample));