From 453713187815b023cbc32cd434580a452f3e97ac Mon Sep 17 00:00:00 2001 From: Keion Anvaripour Date: Wed, 16 Dec 2020 16:02:08 -0800 Subject: [PATCH] Bytecode client for Android (#30595) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/30595 Changelog: [Internal] Add support for loading HBC bundles from Metro in Twilight. * adds `runtimeBytecodeVersion` to the bundleURL * adds logic to check chunk hermes bytecode bundles * adds support for running the bytecode bundles Reviewed By: cpojer Differential Revision: D24966992 fbshipit-source-id: acdd03a2e9e2b3e4c29c99c35a7c9136a3a7ef01 --- React/Base/RCTJavaScriptLoader.mm | 1 + ReactAndroid/build.gradle | 1 + .../main/java/com/facebook/react/common/BUCK | 1 + .../react/common/build/ReactBuildConfig.java | 1 + .../react/devsupport/DevServerHelper.java | 10 ++++++-- .../jni/react/jni/CatalystInstanceImpl.cpp | 25 ++++++++++++++++++- ReactCommon/cxxreact/Instance.cpp | 12 +++++++++ ReactCommon/cxxreact/Instance.h | 1 + ReactCommon/cxxreact/JSBundleType.cpp | 5 ++++ ReactCommon/cxxreact/JSBundleType.h | 1 + 10 files changed, 55 insertions(+), 3 deletions(-) diff --git a/React/Base/RCTJavaScriptLoader.mm b/React/Base/RCTJavaScriptLoader.mm index d902f0c92d0..dfce0d5e93a 100755 --- a/React/Base/RCTJavaScriptLoader.mm +++ b/React/Base/RCTJavaScriptLoader.mm @@ -177,6 +177,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)init) facebook::react::ScriptTag tag = facebook::react::parseTypeFromHeader(header); switch (tag) { + case facebook::react::ScriptTag::HBCBundle: case facebook::react::ScriptTag::RAMBundle: break; diff --git a/ReactAndroid/build.gradle b/ReactAndroid/build.gradle index e651b5be40f..2b33e77be71 100644 --- a/ReactAndroid/build.gradle +++ b/ReactAndroid/build.gradle @@ -399,6 +399,7 @@ android { buildConfigField("boolean", "IS_INTERNAL_BUILD", "false") buildConfigField("int", "EXOPACKAGE_FLAGS", "0") + buildConfigField("int", "HERMES_BYTECODE_VERSION", "0") resValue "integer", "react_native_dev_server_port", reactNativeDevServerPort() resValue "integer", "react_native_inspector_proxy_port", reactNativeInspectorProxyPort() diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/BUCK b/ReactAndroid/src/main/java/com/facebook/react/common/BUCK index c8a8cb7d18e..596d953a230 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/common/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/common/BUCK @@ -40,6 +40,7 @@ rn_android_build_config( package = "com.facebook.react", values = [ "boolean IS_INTERNAL_BUILD = true", + "int HERMES_BYTECODE_VERSION = 0", ], visibility = [ "PUBLIC", diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/build/ReactBuildConfig.java b/ReactAndroid/src/main/java/com/facebook/react/common/build/ReactBuildConfig.java index a90ec674e12..093bd88c580 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/common/build/ReactBuildConfig.java +++ b/ReactAndroid/src/main/java/com/facebook/react/common/build/ReactBuildConfig.java @@ -20,4 +20,5 @@ public class ReactBuildConfig { public static final boolean DEBUG = BuildConfig.DEBUG; public static final boolean IS_INTERNAL_BUILD = BuildConfig.IS_INTERNAL_BUILD; public static final int EXOPACKAGE_FLAGS = BuildConfig.EXOPACKAGE_FLAGS; + public static final int HERMES_BYTECODE_VERSION = BuildConfig.HERMES_BYTECODE_VERSION; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java index 83e7ca48cb1..88bb1764b90 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java @@ -14,6 +14,7 @@ import com.facebook.common.logging.FLog; import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.ReactContext; import com.facebook.react.common.ReactConstants; +import com.facebook.react.common.build.ReactBuildConfig; import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; import com.facebook.react.devsupport.interfaces.PackagerStatusCallback; import com.facebook.react.devsupport.interfaces.StackFrame; @@ -428,9 +429,13 @@ public class DevServerHelper { private String createBundleURL( String mainModuleID, BundleType type, String host, boolean modulesOnly, boolean runModule) { + String runtimeBytecodeVersion = + ReactBuildConfig.HERMES_BYTECODE_VERSION != 0 + ? "&runtimeBytecodeVersion=" + ReactBuildConfig.HERMES_BYTECODE_VERSION + : ""; return String.format( Locale.US, - "http://%s/%s.%s?platform=android&dev=%s&minify=%s&app=%s&modulesOnly=%s&runModule=%s", + "http://%s/%s.%s?platform=android&dev=%s&minify=%s&app=%s&modulesOnly=%s&runModule=%s%s", host, mainModuleID, type.typeID(), @@ -438,7 +443,8 @@ public class DevServerHelper { getJSMinifyMode(), mPackageName, modulesOnly ? "true" : "false", - runModule ? "true" : "false"); + runModule ? "true" : "false", + runtimeBytecodeVersion); } private String createBundleURL(String mainModuleID, BundleType type) { diff --git a/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp b/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp index 659c8235a64..2a33fa3d0f8 100644 --- a/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp +++ b/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp @@ -227,7 +227,30 @@ void CatalystInstanceImpl::jniLoadScriptFromFile( const std::string &fileName, const std::string &sourceURL, bool loadSynchronously) { - if (Instance::isIndexedRAMBundle(fileName.c_str())) { + auto reactInstance = instance_; + if (reactInstance && Instance::isHBCBundle(fileName.c_str())) { + std::unique_ptr script; + RecoverableError::runRethrowingAsRecoverable( + [&fileName, &script]() { + script = JSBigFileString::fromPath(fileName); + }); + const char *buffer = script->c_str(); + uint32_t bufferLength = (uint32_t)script->size(); + uint32_t offset = 8; + while (offset < bufferLength) { + uint32_t segment = offset + 4; + uint32_t moduleLength = + bufferLength < segment ? 0 : *(((uint32_t *)buffer) + offset / 4); + + reactInstance->loadScriptFromString( + std::make_unique( + std::string(buffer + segment, buffer + moduleLength + segment)), + sourceURL, + false); + + offset += ((moduleLength + 3) & ~3) + 4; + } + } else if (Instance::isIndexedRAMBundle(fileName.c_str())) { instance_->loadRAMBundleFromFile(fileName, sourceURL, loadSynchronously); } else { std::unique_ptr script; diff --git a/ReactCommon/cxxreact/Instance.cpp b/ReactCommon/cxxreact/Instance.cpp index 2f2dc5d448d..d809a87c737 100644 --- a/ReactCommon/cxxreact/Instance.cpp +++ b/ReactCommon/cxxreact/Instance.cpp @@ -108,6 +108,18 @@ void Instance::loadScriptFromString( } } +bool Instance::isHBCBundle(const char *sourcePath) { + std::ifstream bundle_stream(sourcePath, std::ios_base::in); + BundleHeader header; + + if (!bundle_stream || + !bundle_stream.read(reinterpret_cast(&header), sizeof(header))) { + return false; + } + + return parseTypeFromHeader(header) == ScriptTag::HBCBundle; +} + bool Instance::isIndexedRAMBundle(const char *sourcePath) { std::ifstream bundle_stream(sourcePath, std::ios_base::in); BundleHeader header; diff --git a/ReactCommon/cxxreact/Instance.h b/ReactCommon/cxxreact/Instance.h index f475bf8a7fa..7cd11f9bdf5 100644 --- a/ReactCommon/cxxreact/Instance.h +++ b/ReactCommon/cxxreact/Instance.h @@ -56,6 +56,7 @@ class RN_EXPORT Instance { std::unique_ptr string, std::string sourceURL, bool loadSynchronously); + static bool isHBCBundle(const char *sourcePath); static bool isIndexedRAMBundle(const char *sourcePath); static bool isIndexedRAMBundle(std::unique_ptr *string); void loadRAMBundleFromString( diff --git a/ReactCommon/cxxreact/JSBundleType.cpp b/ReactCommon/cxxreact/JSBundleType.cpp index 05bacb4107c..f76610d461a 100644 --- a/ReactCommon/cxxreact/JSBundleType.cpp +++ b/ReactCommon/cxxreact/JSBundleType.cpp @@ -13,11 +13,14 @@ namespace facebook { namespace react { static uint32_t constexpr RAMBundleMagicNumber = 0xFB0BD1E5; +static uint32_t constexpr HBCBundleMagicNumber = 0xffe7c3c3; ScriptTag parseTypeFromHeader(const BundleHeader &header) { switch (folly::Endian::little(header.magic)) { case RAMBundleMagicNumber: return ScriptTag::RAMBundle; + case HBCBundleMagicNumber: + return ScriptTag::HBCBundle; default: return ScriptTag::String; } @@ -29,6 +32,8 @@ const char *stringForScriptTag(const ScriptTag &tag) { return "String"; case ScriptTag::RAMBundle: return "RAM Bundle"; + case ScriptTag::HBCBundle: + return "HBC Bundle"; } return ""; } diff --git a/ReactCommon/cxxreact/JSBundleType.h b/ReactCommon/cxxreact/JSBundleType.h index 45a90458188..be39711dd8d 100644 --- a/ReactCommon/cxxreact/JSBundleType.h +++ b/ReactCommon/cxxreact/JSBundleType.h @@ -27,6 +27,7 @@ namespace react { enum struct ScriptTag { String = 0, RAMBundle, + HBCBundle, }; /**