Files
react-native/packages/react-native/ReactCommon/react/runtime/hermes/HermesInstance.cpp
T
Moti ZilbermanandFacebook GitHub Bot cc34acefab Create HermesRuntimeAgent (#42747)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/42747

Changelog: [Internal]

Implements a `RuntimeAgent` (D51231326) for Hermes for the modern CDP backend, based on the `CDPHandler` API that Hermes exposes currently.

## A note on `console`

We unfortunately have to disable `console` interception (D51234334 / equivalently D52971652) because `CDPHandler`'s current implementation is not aligned with the Agent concept:

* Agents are only created once a session has started, but the `console` interceptor needs to be injected at VM startup.
* Agents should not clobber each other's shared state (nor consume excessive resources per Agent), but each `CDPHandler` would install its own independent `console` interceptor if enabled.

We will enable CDP `console` support in the modern backend in future work. This will require either some additional plumbing in RN (e.g. to safely access JSI from an Agent/Target) or some additional work in Hermes.

## Conditional compilation based on `HERMES_ENABLE_DEBUGGER`

`HermesRuntimeAgent.cpp` compiles both with and without `-DHERMES_ENABLE_DEBUGGER`, which is the flag Hermes uses to control the availability of `CDPHandler` (and its containing Buck library).

If the debugger is not enabled, `HermesRuntimeAgent` reduces to a `FallbackRuntimeAgent`. In either case, no Hermes debugger headers leak into `HermesRuntimeAgent.h`, so callers don't need to check `#ifdef HERMES_ENABLE_DEBUGGER`, and the overall CDP backend infra is not gated on whether the Hermes debugger is compiled in.

Reviewed By: huntie

Differential Revision: D51234333

fbshipit-source-id: ccbca443560308c5edba4b9689501d01059fdd94
2024-02-02 10:24:10 -08:00

192 lines
6.6 KiB
C++

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "HermesInstance.h"
#include <hermes/inspector-modern/chrome/HermesRuntimeAgent.h>
#include <jsi/jsilib.h>
#include <jsinspector-modern/InspectorFlags.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#ifdef HERMES_ENABLE_DEBUGGER
#include <hermes/inspector-modern/chrome/Registration.h>
#include <hermes/inspector/RuntimeAdapter.h>
#include <jsi/decorator.h>
#endif
using namespace facebook::hermes;
using namespace facebook::jsi;
namespace facebook::react {
#ifdef HERMES_ENABLE_DEBUGGER
// Wrapper that strongly retains the HermesRuntime for on device debugging.
//
// HermesInstanceRuntimeAdapter needs to strongly retain the HermesRuntime. Why:
// - facebook::hermes::inspector_modern::chrome::Connection::Impl owns the
// Adapter
// - facebook::hermes::inspector_modern::chrome::Connection::Impl also owns
// jsi:: objects
// - jsi:: objects need to be deleted before the Runtime.
//
// If Adapter doesn't share ownership over jsi::Runtime, the runtime can be
// deleted before Connection::Impl cleans up all its jsi:: Objects. This will
// lead to a runtime crash.
class HermesInstanceRuntimeAdapter : public inspector_modern::RuntimeAdapter {
public:
HermesInstanceRuntimeAdapter(
std::shared_ptr<HermesRuntime> hermesRuntime,
std::shared_ptr<MessageQueueThread> msgQueueThread)
: hermesRuntime_(std::move(hermesRuntime)),
messageQueueThread_(std::move(msgQueueThread)) {}
virtual ~HermesInstanceRuntimeAdapter() = default;
HermesRuntime& getRuntime() override {
return *hermesRuntime_;
}
void tickleJs() override {
std::weak_ptr<HermesRuntime> weakRuntime(hermesRuntime_);
messageQueueThread_->runOnQueue([weakRuntime]() {
auto runtime = weakRuntime.lock();
if (!runtime) {
return;
}
jsi::Function func =
runtime->global().getPropertyAsFunction(*runtime, "__tickleJs");
func.call(*runtime);
});
}
private:
std::shared_ptr<HermesRuntime> hermesRuntime_;
std::shared_ptr<MessageQueueThread> messageQueueThread_;
};
class DecoratedRuntime : public jsi::RuntimeDecorator<jsi::Runtime> {
public:
DecoratedRuntime(
std::unique_ptr<HermesRuntime> runtime,
std::shared_ptr<MessageQueueThread> msgQueueThread)
: RuntimeDecorator<jsi::Runtime>(*runtime), runtime_(std::move(runtime)) {
auto adapter = std::make_unique<HermesInstanceRuntimeAdapter>(
runtime_, msgQueueThread);
debugToken_ = inspector_modern::chrome::enableDebugging(
std::move(adapter), "Hermes Bridgeless React Native");
}
~DecoratedRuntime() {
inspector_modern::chrome::disableDebugging(debugToken_);
}
private:
std::shared_ptr<HermesRuntime> runtime_;
inspector_modern::chrome::DebugSessionToken debugToken_;
};
#endif
class HermesJSRuntime : public JSRuntime {
public:
HermesJSRuntime(
std::unique_ptr<HermesRuntime> runtime,
std::shared_ptr<MessageQueueThread> msgQueueThread)
: runtime_(std::move(runtime)),
msgQueueThread_(std::move(msgQueueThread)) {}
jsi::Runtime& getRuntime() noexcept override {
return *runtime_;
}
std::unique_ptr<jsinspector_modern::RuntimeAgent> createInspectorAgent(
jsinspector_modern::FrontendChannel frontendChannel,
jsinspector_modern::SessionState& sessionState) override {
return std::unique_ptr<jsinspector_modern::RuntimeAgent>(
new jsinspector_modern::HermesRuntimeAgent(
frontendChannel,
sessionState,
runtime_,
[msgQueueThreadWeak = std::weak_ptr(msgQueueThread_),
runtimeWeak = std::weak_ptr(runtime_)](auto fn) {
auto msgQueueThread = msgQueueThreadWeak.lock();
if (!msgQueueThread) {
return;
}
msgQueueThread->runOnQueue([runtimeWeak, fn]() {
auto runtime = runtimeWeak.lock();
if (!runtime) {
return;
}
fn(*runtime);
});
}));
}
private:
std::shared_ptr<HermesRuntime> runtime_;
std::shared_ptr<MessageQueueThread> msgQueueThread_;
};
std::unique_ptr<JSRuntime> HermesInstance::createJSRuntime(
std::shared_ptr<const ReactNativeConfig> reactNativeConfig,
std::shared_ptr<::hermes::vm::CrashManager> cm,
std::shared_ptr<MessageQueueThread> msgQueueThread) noexcept {
assert(msgQueueThread != nullptr);
int64_t vmExperimentFlags = reactNativeConfig
? reactNativeConfig->getInt64("ios_hermes:vm_experiment_flags")
: 0;
int64_t heapSizeConfig = reactNativeConfig
? reactNativeConfig->getInt64("ios_hermes:rn_heap_size_mb")
: 0;
// Default to 3GB if MobileConfigs is not available
auto heapSizeMB = heapSizeConfig > 0
? static_cast<::hermes::vm::gcheapsize_t>(heapSizeConfig)
: 3072;
::hermes::vm::RuntimeConfig::Builder runtimeConfigBuilder =
::hermes::vm::RuntimeConfig::Builder()
.withGCConfig(::hermes::vm::GCConfig::Builder()
.withMaxHeapSize(heapSizeMB << 20)
.withName("RNBridgeless")
// For the next two arguments: avoid GC before TTI
// by initializing the runtime to allocate directly
// in the old generation, but revert to normal
// operation when we reach the (first) TTI point.
.withAllocInYoung(false)
.withRevertToYGAtTTI(true)
.build())
.withES6Proxy(false)
.withEnableSampleProfiling(true)
.withMicrotaskQueue(ReactNativeFeatureFlags::enableMicrotasks())
.withVMExperimentFlags(vmExperimentFlags);
if (cm) {
runtimeConfigBuilder.withCrashMgr(cm);
}
std::unique_ptr<HermesRuntime> hermesRuntime =
hermes::makeHermesRuntime(runtimeConfigBuilder.build());
#ifdef HERMES_ENABLE_DEBUGGER
auto& inspectorFlags = jsinspector_modern::InspectorFlags::getInstance();
if (!inspectorFlags.getEnableModernCDPRegistry()) {
std::unique_ptr<DecoratedRuntime> decoratedRuntime =
std::make_unique<DecoratedRuntime>(
std::move(hermesRuntime), msgQueueThread);
return std::make_unique<JSIRuntimeHolder>(std::move(decoratedRuntime));
}
#endif
return std::make_unique<HermesJSRuntime>(
std::move(hermesRuntime), std::move(msgQueueThread));
}
} // namespace facebook::react