mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Add Hermes support to React Native on Android (#25613)
Summary: Yesterday we shipped hermesengine.dev as part of the current 0.60 release. This PR brings those changes to master. ## Changelog [General] [Added] - Added support for Hermes Pull Request resolved: https://github.com/facebook/react-native/pull/25613 Test Plan: * CI is green both on GitHub and at FB * Creating a new app from source can use Hermes on Android Reviewed By: cpojer Differential Revision: D16221777 Pulled By: willholen fbshipit-source-id: aa6be10537863039cb666292465ba2e1d44b64ef
This commit is contained in:
committed by
Facebook Github Bot
parent
fee7f0617e
commit
d7f5153cd8
@@ -0,0 +1,9 @@
|
||||
load("//tools/build_defs/oss:rn_defs.bzl", "rn_android_library")
|
||||
|
||||
rn_android_library(
|
||||
name = "instrumentation",
|
||||
srcs = glob(["**/*.java"]),
|
||||
visibility = [
|
||||
"PUBLIC",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
#include <fb/fbjni.h>
|
||||
#include <string>
|
||||
|
||||
namespace facebook {
|
||||
namespace jsi {
|
||||
namespace jni {
|
||||
|
||||
namespace jni = ::facebook::jni;
|
||||
|
||||
class HermesMemoryDumper : public jni::JavaClass<HermesMemoryDumper> {
|
||||
public:
|
||||
constexpr static auto kJavaDescriptor =
|
||||
"Lcom/facebook/hermes/instrumentation/HermesMemoryDumper;";
|
||||
|
||||
bool shouldSaveSnapshot() {
|
||||
static auto shouldSaveSnapshotMethod =
|
||||
javaClassStatic()->getMethod<jboolean()>("shouldSaveSnapshot");
|
||||
return shouldSaveSnapshotMethod(self());
|
||||
}
|
||||
|
||||
std::string getInternalStorage() {
|
||||
static auto getInternalStorageMethod =
|
||||
javaClassStatic()->getMethod<jstring()>("getInternalStorage");
|
||||
return getInternalStorageMethod(self())->toStdString();
|
||||
}
|
||||
|
||||
std::string getId() {
|
||||
static auto getInternalStorageMethod =
|
||||
javaClassStatic()->getMethod<jstring()>("getId");
|
||||
return getInternalStorageMethod(self())->toStdString();
|
||||
}
|
||||
|
||||
void setMetaData(std::string crashId) {
|
||||
static auto getIdMethod =
|
||||
javaClassStatic()->getMethod<void(std::string)>("setMetaData");
|
||||
getIdMethod(self(), crashId);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace jni
|
||||
} // namespace jsi
|
||||
} // namespace facebook
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
|
||||
* directory of this source tree.
|
||||
*/
|
||||
package com.facebook.hermes.instrumentation;
|
||||
|
||||
public interface HermesMemoryDumper {
|
||||
boolean shouldSaveSnapshot();
|
||||
|
||||
String getInternalStorage();
|
||||
|
||||
String getId();
|
||||
|
||||
void setMetaData(String crashId);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
REACT_NATIVE := $(LOCAL_PATH)/../../../../../../../..
|
||||
|
||||
LOCAL_MODULE := hermes-executor-release
|
||||
|
||||
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
|
||||
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH) $(REACT_NATIVE)/ReactCommon/jsi $(REACT_NATIVE)/node_modules/hermesvm/android/include $(REACT_NATIVE)/../hermesvm/android/include $(REACT_NATIVE)/../node_modules/hermesvm/include
|
||||
|
||||
LOCAL_CPP_FEATURES := exceptions
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := libjsireact libjsi
|
||||
LOCAL_SHARED_LIBRARIES := libfolly_json libfb libreactnativejni libhermes
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
REACT_NATIVE := $(LOCAL_PATH)/../../../../../../../..
|
||||
|
||||
LOCAL_MODULE := hermes-executor-debug
|
||||
LOCAL_CFLAGS := -DHERMES_ENABLE_DEBUGGER=1
|
||||
|
||||
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
|
||||
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH) $(REACT_NATIVE)/ReactCommon/jsi $(REACT_NATIVE)/node_modules/hermesvm/android/include $(REACT_NATIVE)/../hermesvm/android/include $(REACT_NATIVE)/../node_modules/hermesvm/include
|
||||
|
||||
LOCAL_CPP_FEATURES := exceptions
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := libjsireact libjsi libhermes-inspector
|
||||
LOCAL_SHARED_LIBRARIES := libfolly_json libfb libreactnativejni libhermes
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
|
||||
* directory of this source tree.
|
||||
*/
|
||||
package com.facebook.hermes.reactexecutor;
|
||||
|
||||
import com.facebook.hermes.instrumentation.HermesMemoryDumper;
|
||||
import com.facebook.jni.HybridData;
|
||||
import com.facebook.react.bridge.JavaScriptExecutor;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class HermesExecutor extends JavaScriptExecutor {
|
||||
private static String mode_;
|
||||
|
||||
static {
|
||||
// libhermes must be loaded explicitly to invoke its JNI_OnLoad.
|
||||
SoLoader.loadLibrary("hermes");
|
||||
try {
|
||||
SoLoader.loadLibrary("hermes-executor-release");
|
||||
mode_ = "Release";
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
SoLoader.loadLibrary("hermes-executor-debug");
|
||||
mode_ = "Debug";
|
||||
}
|
||||
}
|
||||
|
||||
HermesExecutor(@Nullable RuntimeConfig config) {
|
||||
super(
|
||||
config == null
|
||||
? initHybridDefaultConfig()
|
||||
: initHybrid(
|
||||
config.heapSizeMB,
|
||||
config.es6Symbol,
|
||||
config.bytecodeWarmupPercent,
|
||||
config.tripWireEnabled,
|
||||
config.heapDumper,
|
||||
config.tripWireCooldownMS,
|
||||
config.tripWireLimitBytes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "HermesExecutor" + mode_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this class can load a file at the given path, based on a binary compatibility
|
||||
* check between the contents of the file and the Hermes VM.
|
||||
*
|
||||
* @param path the path containing the file to inspect.
|
||||
* @return whether the given file is compatible with the Hermes VM.
|
||||
*/
|
||||
public static native boolean canLoadFile(String path);
|
||||
|
||||
private static native HybridData initHybridDefaultConfig();
|
||||
|
||||
private static native HybridData initHybrid(
|
||||
long heapSizeMB,
|
||||
boolean es6Symbol,
|
||||
int bytecodeWarmupPercent,
|
||||
boolean tripWireEnabled,
|
||||
@Nullable HermesMemoryDumper heapDumper,
|
||||
long tripWireCooldownMS,
|
||||
long tripWireLimitBytes);
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include "HermesExecutorFactory.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <cxxreact/MessageQueueThread.h>
|
||||
#include <cxxreact/SystraceSection.h>
|
||||
#include <hermes/hermes_tracing.h>
|
||||
#include <jsi/decorator.h>
|
||||
|
||||
#ifdef HERMES_ENABLE_DEBUGGER
|
||||
#include <hermes/inspector/RuntimeAdapter.h>
|
||||
#include <hermes/inspector/chrome/Registration.h>
|
||||
#endif
|
||||
|
||||
#include "JSITracing.h"
|
||||
|
||||
using namespace facebook::hermes;
|
||||
using namespace facebook::jsi;
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
namespace {
|
||||
|
||||
std::unique_ptr<HermesRuntime> makeHermesRuntimeSystraced(
|
||||
const ::hermes::vm::RuntimeConfig &runtimeConfig) {
|
||||
SystraceSection s("HermesExecutorFactory::makeHermesRuntimeSystraced");
|
||||
return hermes::makeHermesRuntime(runtimeConfig);
|
||||
}
|
||||
|
||||
#ifdef HERMES_ENABLE_DEBUGGER
|
||||
|
||||
class HermesExecutorRuntimeAdapter
|
||||
: public facebook::hermes::inspector::RuntimeAdapter {
|
||||
public:
|
||||
HermesExecutorRuntimeAdapter(
|
||||
std::shared_ptr<Runtime> runtime,
|
||||
HermesRuntime &hermesRuntime,
|
||||
std::shared_ptr<MessageQueueThread> thread)
|
||||
: runtime_(runtime),
|
||||
hermesRuntime_(hermesRuntime),
|
||||
thread_(std::move(thread)) {}
|
||||
|
||||
virtual ~HermesExecutorRuntimeAdapter() = default;
|
||||
|
||||
HermesRuntime &getRuntime() override {
|
||||
return hermesRuntime_;
|
||||
}
|
||||
|
||||
void tickleJs() override {
|
||||
// The queue will ensure that runtime_ is still valid when this
|
||||
// gets invoked.
|
||||
// clang-format off
|
||||
thread_->runOnQueue([&runtime = hermesRuntime_]() {
|
||||
// clang-format on
|
||||
auto func = runtime.global().getPropertyAsFunction(runtime, "__tickleJs");
|
||||
func.call(runtime);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Runtime> runtime_;
|
||||
HermesRuntime &hermesRuntime_;
|
||||
|
||||
std::shared_ptr<MessageQueueThread> thread_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
struct ReentrancyCheck {
|
||||
// This is effectively a very subtle and complex assert, so only
|
||||
// include it in builds which would include asserts.
|
||||
#ifndef NDEBUG
|
||||
ReentrancyCheck() : tid(std::thread::id()), depth(0) {}
|
||||
|
||||
void before() {
|
||||
std::thread::id this_id = std::this_thread::get_id();
|
||||
std::thread::id expected = std::thread::id();
|
||||
|
||||
// A note on memory ordering: the main purpose of these checks is
|
||||
// to observe a before/before race, without an intervening after.
|
||||
// This will be detected by the compare_exchange_strong atomicity
|
||||
// properties, regardless of memory order.
|
||||
//
|
||||
// For everything else, it is easiest to think of 'depth' as a
|
||||
// proxy for any access made inside the VM. If access to depth
|
||||
// are reordered incorrectly, the same could be true of any other
|
||||
// operation made by the VM. In fact, using acquire/release
|
||||
// memory ordering could create barriers which mask a programmer
|
||||
// error. So, we use relaxed memory order, to avoid masking
|
||||
// actual ordering errors. Although, in practice, ordering errors
|
||||
// of this sort would be surprising, because the decorator would
|
||||
// need to call after() without before().
|
||||
|
||||
if (tid.compare_exchange_strong(
|
||||
expected, this_id, std::memory_order_relaxed)) {
|
||||
// Returns true if tid and expected were the same. If they
|
||||
// were, then the stored tid referred to no thread, and we
|
||||
// atomically saved this thread's tid. Now increment depth.
|
||||
assert(depth == 0 && "No thread id, but depth != 0");
|
||||
++depth;
|
||||
} else if (expected == this_id) {
|
||||
// If the stored tid referred to a thread, expected was set to
|
||||
// that value. If that value is this thread's tid, that's ok,
|
||||
// just increment depth again.
|
||||
assert(depth != 0 && "Thread id was set, but depth == 0");
|
||||
++depth;
|
||||
} else {
|
||||
// The stored tid was some other thread. This indicates a bad
|
||||
// programmer error, where VM methods were called on two
|
||||
// different threads unsafely. Fail fast (and hard) so the
|
||||
// crash can be analyzed.
|
||||
__builtin_trap();
|
||||
}
|
||||
}
|
||||
|
||||
void after() {
|
||||
assert(
|
||||
tid.load(std::memory_order_relaxed) == std::this_thread::get_id() &&
|
||||
"No thread id in after()");
|
||||
if (--depth == 0) {
|
||||
// If we decremented depth to zero, store no-thread into tid.
|
||||
std::thread::id expected = std::this_thread::get_id();
|
||||
bool didWrite = tid.compare_exchange_strong(
|
||||
expected, std::thread::id(), std::memory_order_relaxed);
|
||||
assert(didWrite && "Decremented to zero, but no tid write");
|
||||
}
|
||||
}
|
||||
|
||||
std::atomic<std::thread::id> tid;
|
||||
// This is not atomic, as it is only written or read from the owning
|
||||
// thread.
|
||||
unsigned int depth;
|
||||
#endif
|
||||
};
|
||||
|
||||
// This adds ReentrancyCheck and debugger enable/teardown to the given
|
||||
// Runtime.
|
||||
class DecoratedRuntime : public jsi::WithRuntimeDecorator<ReentrancyCheck> {
|
||||
public:
|
||||
// The first argument may be a tracing runtime which itself
|
||||
// decorates the real HermesRuntime, depending on the build config.
|
||||
// The second argument is the the real HermesRuntime as well to
|
||||
// manage the debugger registration.
|
||||
DecoratedRuntime(
|
||||
std::unique_ptr<Runtime> runtime,
|
||||
HermesRuntime &hermesRuntime,
|
||||
std::shared_ptr<MessageQueueThread> jsQueue)
|
||||
: jsi::WithRuntimeDecorator<ReentrancyCheck>(*runtime, reentrancyCheck_),
|
||||
runtime_(std::move(runtime)),
|
||||
hermesRuntime_(hermesRuntime) {
|
||||
#ifdef HERMES_ENABLE_DEBUGGER
|
||||
auto adapter = std::make_unique<HermesExecutorRuntimeAdapter>(
|
||||
runtime_, hermesRuntime_, jsQueue);
|
||||
facebook::hermes::inspector::chrome::enableDebugging(
|
||||
std::move(adapter), "Hermes React Native");
|
||||
#else
|
||||
(void)hermesRuntime_;
|
||||
#endif
|
||||
}
|
||||
|
||||
~DecoratedRuntime() {
|
||||
#ifdef HERMES_ENABLE_DEBUGGER
|
||||
facebook::hermes::inspector::chrome::disableDebugging(hermesRuntime_);
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
// runtime_ is a TracingRuntime, but we don't need to worry about
|
||||
// the details. hermesRuntime is a reference to the HermesRuntime
|
||||
// managed by the TracingRuntime.
|
||||
//
|
||||
// HermesExecutorRuntimeAdapter requirements are kept, because the
|
||||
// dtor will disable debugging on the HermesRuntime before the
|
||||
// member managing it is destroyed.
|
||||
|
||||
std::shared_ptr<Runtime> runtime_;
|
||||
ReentrancyCheck reentrancyCheck_;
|
||||
HermesRuntime &hermesRuntime_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<JSExecutor> HermesExecutorFactory::createJSExecutor(
|
||||
std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> jsQueue) {
|
||||
std::unique_ptr<HermesRuntime> hermesRuntime =
|
||||
makeHermesRuntimeSystraced(runtimeConfig_);
|
||||
HermesRuntime& hermesRuntimeRef = *hermesRuntime;
|
||||
auto decoratedRuntime = std::make_shared<DecoratedRuntime>(
|
||||
makeTracingHermesRuntime(std::move(hermesRuntime), runtimeConfig_),
|
||||
hermesRuntimeRef,
|
||||
jsQueue);
|
||||
|
||||
// So what do we have now?
|
||||
// DecoratedRuntime -> TracingRuntime -> HermesRuntime
|
||||
//
|
||||
// DecoratedRuntime is held by JSIExecutor. When it gets used, it
|
||||
// will check that it's on the right thread, do any necessary trace
|
||||
// logging, then call the real HermesRuntime. When it is destroyed,
|
||||
// it will shut down the debugger before the HermesRuntime is. In
|
||||
// the normal case where tracing and debugging are not compiled in,
|
||||
// all that's left is the thread checking.
|
||||
|
||||
// Add js engine information to Error.prototype so in error reporting we
|
||||
// can send this information.
|
||||
auto errorPrototype =
|
||||
decoratedRuntime->global()
|
||||
.getPropertyAsObject(*decoratedRuntime, "Error")
|
||||
.getPropertyAsObject(*decoratedRuntime, "prototype");
|
||||
errorPrototype.setProperty(*decoratedRuntime, "jsEngine", "hermes");
|
||||
|
||||
return std::make_unique<HermesExecutor>(
|
||||
decoratedRuntime, delegate, jsQueue, timeoutInvoker_, runtimeInstaller_);
|
||||
}
|
||||
|
||||
HermesExecutor::HermesExecutor(
|
||||
std::shared_ptr<jsi::Runtime> runtime,
|
||||
std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> jsQueue,
|
||||
const JSIScopedTimeoutInvoker &timeoutInvoker,
|
||||
RuntimeInstaller runtimeInstaller)
|
||||
: JSIExecutor(runtime, delegate, timeoutInvoker, runtimeInstaller) {
|
||||
jsi::addNativeTracingHooks(*runtime);
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <hermes/hermes.h>
|
||||
#include <jsireact/JSIExecutor.h>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
class HermesExecutorFactory : public JSExecutorFactory {
|
||||
public:
|
||||
explicit HermesExecutorFactory(
|
||||
JSIExecutor::RuntimeInstaller runtimeInstaller,
|
||||
const JSIScopedTimeoutInvoker& timeoutInvoker =
|
||||
JSIExecutor::defaultTimeoutInvoker,
|
||||
::hermes::vm::RuntimeConfig runtimeConfig = ::hermes::vm::RuntimeConfig())
|
||||
: runtimeInstaller_(runtimeInstaller),
|
||||
timeoutInvoker_(timeoutInvoker),
|
||||
runtimeConfig_(std::move(runtimeConfig)) {
|
||||
assert(timeoutInvoker_ && "Should not have empty timeoutInvoker");
|
||||
}
|
||||
|
||||
std::unique_ptr<JSExecutor> createJSExecutor(
|
||||
std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> jsQueue) override;
|
||||
|
||||
private:
|
||||
JSIExecutor::RuntimeInstaller runtimeInstaller_;
|
||||
JSIScopedTimeoutInvoker timeoutInvoker_;
|
||||
::hermes::vm::RuntimeConfig runtimeConfig_;
|
||||
};
|
||||
|
||||
class HermesExecutor : public JSIExecutor {
|
||||
public:
|
||||
HermesExecutor(
|
||||
std::shared_ptr<jsi::Runtime> runtime,
|
||||
std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> jsQueue,
|
||||
const JSIScopedTimeoutInvoker& timeoutInvoker,
|
||||
RuntimeInstaller runtimeInstaller);
|
||||
|
||||
private:
|
||||
JSIScopedTimeoutInvoker timeoutInvoker_;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
|
||||
* directory of this source tree.
|
||||
*/
|
||||
package com.facebook.hermes.reactexecutor;
|
||||
|
||||
import com.facebook.react.bridge.JavaScriptExecutor;
|
||||
import com.facebook.react.bridge.JavaScriptExecutorFactory;
|
||||
|
||||
public class HermesExecutorFactory implements JavaScriptExecutorFactory {
|
||||
private static final String TAG = "Hermes";
|
||||
|
||||
private final RuntimeConfig mConfig;
|
||||
|
||||
public HermesExecutorFactory() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public HermesExecutorFactory(RuntimeConfig config) {
|
||||
mConfig = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaScriptExecutor create() {
|
||||
return new HermesExecutor(mConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startSamplingProfiler() {}
|
||||
|
||||
@Override
|
||||
public void stopSamplingProfiler(String filename) {}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JSIExecutor+HermesRuntime";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
#include "JSITracing.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace jsi {
|
||||
void addNativeTracingHooks(Runtime &rt) {
|
||||
assert(false && "unimplemented");
|
||||
}
|
||||
} // namespace jsi
|
||||
} // namespace facebook
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
#include <jsi/jsi.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace jsi {
|
||||
|
||||
void addNativeTracingHooks(Runtime &rt);
|
||||
|
||||
} // namespace jsi
|
||||
} // namespace facebook
|
||||
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
#include <../instrumentation/HermesMemoryDumper.h>
|
||||
#include <HermesExecutorFactory.h>
|
||||
#include <fb/fbjni.h>
|
||||
#include <folly/Memory.h>
|
||||
#include <hermes/Public/GCConfig.h>
|
||||
#include <hermes/Public/RuntimeConfig.h>
|
||||
#include <jni.h>
|
||||
#include <react/jni/JReactMarker.h>
|
||||
#include <react/jni/JSLogging.h>
|
||||
#include <react/jni/JavaScriptExecutorHolder.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
/// Converts a duration given as a long from Java, into a std::chrono duration.
|
||||
static constexpr std::chrono::hours msToHours(jlong ms) {
|
||||
using namespace std::chrono;
|
||||
return duration_cast<hours>(milliseconds(ms));
|
||||
}
|
||||
|
||||
static ::hermes::vm::RuntimeConfig makeRuntimeConfig(
|
||||
jlong heapSizeMB,
|
||||
bool es6Symbol,
|
||||
jint bytecodeWarmupPercent,
|
||||
bool tripWireEnabled,
|
||||
jni::alias_ref<jsi::jni::HermesMemoryDumper> heapDumper,
|
||||
jlong tripWireCooldownMS,
|
||||
jlong tripWireLimitBytes) {
|
||||
namespace vm = ::hermes::vm;
|
||||
auto gcConfigBuilder =
|
||||
vm::GCConfig::Builder()
|
||||
.withMaxHeapSize(heapSizeMB << 20)
|
||||
.withName("RN")
|
||||
// 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);
|
||||
|
||||
if (tripWireEnabled) {
|
||||
assert(
|
||||
heapDumper &&
|
||||
"Must provide a heap dumper instance if tripwire is enabled");
|
||||
|
||||
gcConfigBuilder.withTripwireConfig(
|
||||
vm::GCTripwireConfig::Builder()
|
||||
.withLimit(tripWireLimitBytes)
|
||||
.withCooldown(msToHours(tripWireCooldownMS))
|
||||
.withCallback([globalHeapDumper = jni::make_global(heapDumper)](
|
||||
vm::GCTripwireContext &ctx) mutable {
|
||||
if (!globalHeapDumper->shouldSaveSnapshot()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string crashId = globalHeapDumper->getId();
|
||||
std::string path = globalHeapDumper->getInternalStorage();
|
||||
path += "/dump_";
|
||||
path += crashId;
|
||||
path += ".hermes";
|
||||
|
||||
bool successful = ctx.createSnapshotToFile(path, true);
|
||||
if (!successful) {
|
||||
LOG(ERROR) << "Failed to write Hermes Memory Dump to " << path
|
||||
<< "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(INFO) << "Hermes Memory Dump saved on: " << path << "\n";
|
||||
globalHeapDumper->setMetaData(crashId);
|
||||
})
|
||||
.build());
|
||||
}
|
||||
|
||||
return vm::RuntimeConfig::Builder()
|
||||
.withGCConfig(gcConfigBuilder.build())
|
||||
.withES6Symbol(es6Symbol)
|
||||
.withBytecodeWarmupPercent(bytecodeWarmupPercent)
|
||||
.build();
|
||||
}
|
||||
|
||||
static void installBindings(jsi::Runtime &runtime) {
|
||||
react::Logger androidLogger =
|
||||
static_cast<void (*)(const std::string &, unsigned int)>(
|
||||
&reactAndroidLoggingHook);
|
||||
react::bindNativeLogger(runtime, androidLogger);
|
||||
}
|
||||
|
||||
class HermesExecutorHolder
|
||||
: public jni::HybridClass<HermesExecutorHolder, JavaScriptExecutorHolder> {
|
||||
public:
|
||||
static constexpr auto kJavaDescriptor =
|
||||
"Lcom/facebook/hermes/reactexecutor/HermesExecutor;";
|
||||
|
||||
static jni::local_ref<jhybriddata> initHybridDefaultConfig(
|
||||
jni::alias_ref<jclass>) {
|
||||
JReactMarker::setLogPerfMarkerIfNeeded();
|
||||
|
||||
return makeCxxInstance(
|
||||
folly::make_unique<HermesExecutorFactory>(installBindings));
|
||||
}
|
||||
|
||||
static jni::local_ref<jhybriddata> initHybrid(
|
||||
jni::alias_ref<jclass>,
|
||||
jlong heapSizeMB,
|
||||
bool es6Symbol,
|
||||
jint bytecodeWarmupPercent,
|
||||
bool tripWireEnabled,
|
||||
jni::alias_ref<jsi::jni::HermesMemoryDumper> heapDumper,
|
||||
jlong tripWireCooldownMS,
|
||||
jlong tripWireLimitBytes) {
|
||||
JReactMarker::setLogPerfMarkerIfNeeded();
|
||||
auto runtimeConfig = makeRuntimeConfig(
|
||||
heapSizeMB,
|
||||
es6Symbol,
|
||||
bytecodeWarmupPercent,
|
||||
tripWireEnabled,
|
||||
heapDumper,
|
||||
tripWireCooldownMS,
|
||||
tripWireLimitBytes);
|
||||
return makeCxxInstance(folly::make_unique<HermesExecutorFactory>(
|
||||
installBindings, JSIExecutor::defaultTimeoutInvoker, runtimeConfig));
|
||||
}
|
||||
|
||||
static bool canLoadFile(jni::alias_ref<jclass>, const std::string &path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void registerNatives() {
|
||||
registerHybrid(
|
||||
{makeNativeMethod("initHybrid", HermesExecutorHolder::initHybrid),
|
||||
makeNativeMethod(
|
||||
"initHybridDefaultConfig",
|
||||
HermesExecutorHolder::initHybridDefaultConfig),
|
||||
makeNativeMethod("canLoadFile", HermesExecutorHolder::canLoadFile)});
|
||||
}
|
||||
|
||||
private:
|
||||
friend HybridBase;
|
||||
using HybridBase::HybridBase;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
return facebook::jni::initialize(
|
||||
vm, [] { facebook::react::HermesExecutorHolder::registerNatives(); });
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
|
||||
* directory of this source tree.
|
||||
*/
|
||||
package com.facebook.hermes.reactexecutor;
|
||||
|
||||
import com.facebook.hermes.instrumentation.HermesMemoryDumper;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Holds runtime configuration for a Hermes VM instance (master or snapshot). */
|
||||
public final class RuntimeConfig {
|
||||
public long heapSizeMB;
|
||||
public boolean enableSampledStats;
|
||||
public boolean es6Symbol;
|
||||
public int bytecodeWarmupPercent;
|
||||
public boolean tripWireEnabled;
|
||||
@Nullable public HermesMemoryDumper heapDumper;
|
||||
public long tripWireCooldownMS;
|
||||
public long tripWireLimitBytes;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
# For common use cases for the hybrid pattern, keep symbols which may
|
||||
# be referenced only from C++.
|
||||
|
||||
-keepclassmembers class * {
|
||||
com.facebook.jni.HybridData *;
|
||||
<init>(com.facebook.jni.HybridData);
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
com.facebook.jni.HybridData *;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
|
||||
* directory of this source tree.
|
||||
*/
|
||||
package com.facebook.hermes.unicode;
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import java.text.Collator;
|
||||
import java.text.DateFormat;
|
||||
import java.text.Normalizer;
|
||||
import java.util.Locale;
|
||||
|
||||
// TODO: use com.facebook.common.locale.Locales.getApplicationLocale() as the current locale,
|
||||
// rather than the device locale. This is challenging because getApplicationLocale() is only
|
||||
// available via DI.
|
||||
@DoNotStrip
|
||||
public class AndroidUnicodeUtils {
|
||||
@DoNotStrip
|
||||
public static int localeCompare(String left, String right) {
|
||||
Collator collator = Collator.getInstance();
|
||||
return collator.compare(left, right);
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
public static String dateFormat(double unixtimeMs, boolean formatDate, boolean formatTime) {
|
||||
DateFormat format;
|
||||
if (formatDate && formatTime) {
|
||||
format = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
|
||||
} else if (formatDate) {
|
||||
format = DateFormat.getDateInstance(DateFormat.MEDIUM);
|
||||
} else if (formatTime) {
|
||||
format = DateFormat.getTimeInstance(DateFormat.MEDIUM);
|
||||
} else {
|
||||
throw new RuntimeException("Bad dateFormat configuration");
|
||||
}
|
||||
return format.format((long) unixtimeMs).toString();
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
public static String convertToCase(String input, int targetCase, boolean useCurrentLocale) {
|
||||
// These values must match CaseConversion in PlatformUnicode.h
|
||||
final int targetUppercase = 0;
|
||||
final int targetLowercase = 1;
|
||||
// Note Java's case conversions use the user's locale. For example "I".toLowerCase()
|
||||
// will produce a dotless i. From Java's docs: "To obtain correct results for locale
|
||||
// insensitive strings, use toLowerCase(Locale.ENGLISH)."
|
||||
Locale locale = useCurrentLocale ? Locale.getDefault() : Locale.ENGLISH;
|
||||
switch (targetCase) {
|
||||
case targetLowercase:
|
||||
return input.toLowerCase(locale);
|
||||
case targetUppercase:
|
||||
return input.toUpperCase(locale);
|
||||
default:
|
||||
throw new RuntimeException("Invalid target case");
|
||||
}
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
public static String normalize(String input, int form) {
|
||||
// Values must match NormalizationForm in PlatformUnicode.h.
|
||||
final int formC = 0;
|
||||
final int formD = 1;
|
||||
final int formKC = 2;
|
||||
final int formKD = 3;
|
||||
|
||||
switch (form) {
|
||||
case formC:
|
||||
return Normalizer.normalize(input, Normalizer.Form.NFC);
|
||||
case formD:
|
||||
return Normalizer.normalize(input, Normalizer.Form.NFD);
|
||||
case formKC:
|
||||
return Normalizer.normalize(input, Normalizer.Form.NFKC);
|
||||
case formKD:
|
||||
return Normalizer.normalize(input, Normalizer.Form.NFKD);
|
||||
default:
|
||||
throw new RuntimeException("Invalid form");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "rn_android_library")
|
||||
|
||||
rn_android_library(
|
||||
name = "unicode",
|
||||
srcs = glob(["**/*.java"]),
|
||||
visibility = [
|
||||
"PUBLIC",
|
||||
],
|
||||
deps = [
|
||||
react_native_dep("java/com/facebook/proguard/annotations:annotations"),
|
||||
],
|
||||
)
|
||||
@@ -12,8 +12,9 @@ import com.facebook.proguard.annotations.DoNotStrip;
|
||||
@DoNotStrip
|
||||
public class NativeRunnable implements Runnable {
|
||||
|
||||
private final HybridData mHybridData;
|
||||
@DoNotStrip private final HybridData mHybridData;
|
||||
|
||||
@DoNotStrip
|
||||
private NativeRunnable(HybridData hybridData) {
|
||||
mHybridData = hybridData;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ rn_android_library(
|
||||
react_native_dep("libraries/soloader/java/com/facebook/soloader:soloader"),
|
||||
react_native_dep("third-party/java/infer-annotations:infer-annotations"),
|
||||
react_native_dep("third-party/java/jsr-305:jsr-305"),
|
||||
react_native_dep("java/com/facebook/hermes/reactexecutor:reactexecutor"),
|
||||
react_native_target("java/com/facebook/debug/holder:holder"),
|
||||
react_native_target("java/com/facebook/debug/tags:tags"),
|
||||
react_native_target("java/com/facebook/react/bridge:bridge"),
|
||||
|
||||
@@ -10,6 +10,7 @@ import static com.facebook.react.modules.systeminfo.AndroidInfoHelpers.getFriend
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.hermes.reactexecutor.HermesExecutorFactory;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.JSBundleLoader;
|
||||
import com.facebook.react.bridge.JSIModulePackage;
|
||||
@@ -24,6 +25,7 @@ import com.facebook.react.jscexecutor.JSCExecutorFactory;
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
import com.facebook.react.packagerconnection.RequestHandler;
|
||||
import com.facebook.react.uimanager.UIImplementationProvider;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -260,7 +262,7 @@ public class ReactInstanceManagerBuilder {
|
||||
mCurrentActivity,
|
||||
mDefaultHardwareBackBtnHandler,
|
||||
mJavaScriptExecutorFactory == null
|
||||
? new JSCExecutorFactory(appName, deviceName)
|
||||
? getDefaultJSExecutorFactory(appName, deviceName)
|
||||
: mJavaScriptExecutorFactory,
|
||||
(mJSBundleLoader == null && mJSBundleAssetUrl != null)
|
||||
? JSBundleLoader.createAssetLoader(
|
||||
@@ -281,4 +283,15 @@ public class ReactInstanceManagerBuilder {
|
||||
mJSIModulesPackage,
|
||||
mCustomPackagerCommandHandlers);
|
||||
}
|
||||
|
||||
private JavaScriptExecutorFactory getDefaultJSExecutorFactory(String appName, String deviceName) {
|
||||
try {
|
||||
// If JSC is included, use it as normal
|
||||
SoLoader.loadLibrary("jscexecutor");
|
||||
return new JSCExecutorFactory(appName, deviceName);
|
||||
} catch (UnsatisfiedLinkError jscE) {
|
||||
// Otherwise use Hermes
|
||||
return new HermesExecutorFactory();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user