diff --git a/core/player/BUILD b/core/player/BUILD index c2a36eecb..932725e81 100644 --- a/core/player/BUILD +++ b/core/player/BUILD @@ -1,5 +1,6 @@ -load("@rules_player//javascript:defs.bzl", "js_pipeline") load("@npm//:defs.bzl", "npm_link_all_packages") +load("@rules_player//javascript:defs.bzl", "js_pipeline") +load("//jvm/hermes:hermesc.bzl", "hermes_compile") load("//tools:defs.bzl", "NATIVE_BUILD_DEPS", "tsup_config", "vitest_config") npm_link_all_packages(name = "node_modules") @@ -49,3 +50,16 @@ filegroup( ), visibility = ["//visibility:public"], ) + +genrule( + name = "native", + srcs = [":player_native_bundle"], + outs = ["Player.native.js"], + cmd = "echo $(SRCS) | tr ' ' '\\n' | grep %s$$ | xargs -I {} cp {} $(OUTS)" % "Player.native.js", +) + +hermes_compile( + name = "hbc", + js = ":native", + visibility = ["//visibility:public"], +) diff --git a/jvm/core/BUILD b/jvm/core/BUILD index 801031549..c18e1d2b1 100644 --- a/jvm/core/BUILD +++ b/jvm/core/BUILD @@ -17,6 +17,7 @@ main_deps = main_exports + [ main_resources = [ "//core/player:player_native_bundle", + "//core/player:hbc", ] # Test dependencies diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/runtime/Runtime.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/runtime/Runtime.kt index b06e9c2b4..6a2de2bf1 100644 --- a/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/runtime/Runtime.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/bridge/runtime/Runtime.kt @@ -63,4 +63,5 @@ public data class ScriptContext( val script: String, val id: String, val sourceMap: String? = null, + val preCompiledScript: ByteArray = byteArrayOf() ) diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/HeadlessPlayer.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/HeadlessPlayer.kt index bfa6cb92f..2beb5e744 100644 --- a/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/HeadlessPlayer.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/HeadlessPlayer.kt @@ -114,7 +114,8 @@ public class HeadlessPlayer @ExperimentalPlayerApi @JvmOverloads public construc if (runtime.config.debuggable) debugSource.readText() else source.readText(), BUNDLED_SOURCE_PATH, sourceMap.readText(), - ), + precompiledSource.readBytes() + ) ) /** 2. merge explicit [LoggerPlugin]s with ones created by service loader */ @@ -196,6 +197,8 @@ public class HeadlessPlayer @ExperimentalPlayerApi @JvmOverloads public construc private const val DEBUG_SOURCE_PATH = "core/player/dist/Player.native.js" + private const val hbcSourcePath = "core/player/Player.native.js.hbc" + /** Gets [URL] of the bundled source */ private val bundledSource get() = this::class.java .classLoader @@ -208,5 +211,8 @@ public class HeadlessPlayer @ExperimentalPlayerApi @JvmOverloads public construc private val sourceMap get() = this::class.java .classLoader .getResource("$BUNDLED_SOURCE_PATH.map") + + private val precompiledSource get() = this::class.java + .classLoader.getResource(hbcSourcePath) } } diff --git a/jvm/hermes/hermesc.bzl b/jvm/hermes/hermesc.bzl new file mode 100644 index 000000000..5173efc2d --- /dev/null +++ b/jvm/hermes/hermesc.bzl @@ -0,0 +1,33 @@ +def _hermes_compile_impl(context): + input = context.file.js + hbc = context.actions.declare_file("%s.hbc" % input.basename) + + args = context.actions.args() + args.add("-emit-binary") + args.add("-out", hbc) + args.add(input) + + context.actions.run( + mnemonic = "HermesC", + executable = context.executable._hermesc, + arguments = [args], + inputs = depset([input]), + outputs = [hbc], + ) + + return [DefaultInfo(files = depset([hbc]))] + +hermes_compile = rule( + implementation = _hermes_compile_impl, + attrs = { + "js": attr.label( + allow_single_file = True, + ), + "_hermesc": attr.label( + default = Label("@hermes//:hermesc"), + allow_single_file = True, + executable = True, + cfg = "exec", + ), + }, +) diff --git a/jvm/hermes/src/main/jni/JJSIValue.cpp b/jvm/hermes/src/main/jni/JJSIValue.cpp index c63bfbcf2..267c804de 100644 --- a/jvm/hermes/src/main/jni/JJSIValue.cpp +++ b/jvm/hermes/src/main/jni/JJSIValue.cpp @@ -2,9 +2,20 @@ #include "JJSIValue.h" #include +#include +#include namespace intuit::playerui { +using facebook::jsi::Runtime; +using facebook::jsi::Value; +using facebook::jsi::Object; +using facebook::jsi::Array; +using facebook::jsi::Function; +using facebook::jsi::String; +using facebook::jsi::Symbol; +using facebook::jsi::BigInt; + [[noreturn]] void throwNativeHandleReleasedException(std::string nativeHandle) { // TODO: create a new exception type for this to hook into PlayerRuntimeException auto throwableClass = findClassLocal("com/intuit/playerui/core/player/PlayerException"); @@ -32,6 +43,12 @@ local_ref JJSIRuntime::evaluateJavaScript(alias_refget_scope(), get_runtime().evaluateJavaScript(std::make_shared(script), sourceURL));; } +local_ref JJSIRuntime::evaluateHermesBytecode(alias_ref, alias_ref byteArray, std::string sourceURL) { + auto size = byteArray->size(); + auto region = byteArray->getRegion(0, size); + return JJSIValue::newObjectCxxArgs(this->get_scope(), get_runtime().evaluateJavaScript(std::make_shared(region.get(), size), sourceURL)); +} + local_ref JJSIRuntime::prepareJavaScript(alias_ref, std::string script, std::string sourceURL) { return JJSIPreparedJavaScript::newObjectCxxArgs(get_runtime().prepareJavaScript(std::make_shared(script), sourceURL)); } @@ -61,6 +78,7 @@ std::string JJSIRuntime::description(alias_ref) { void JJSIRuntime::registerNatives() { registerHybrid({ makeNativeMethod("evaluateJavaScript", JJSIRuntime::evaluateJavaScript), + makeNativeMethod("evaluateHermesBytecode", JJSIRuntime::evaluateHermesBytecode), makeNativeMethod("prepareJavaScript", JJSIRuntime::prepareJavaScript), makeNativeMethod("evaluatePreparedJavaScript", JJSIRuntime::evaluatePreparedJavaScript), #ifdef JSI_MICROTASK diff --git a/jvm/hermes/src/main/jni/JJSIValue.h b/jvm/hermes/src/main/jni/JJSIValue.h index c43c849c3..6e309c774 100644 --- a/jvm/hermes/src/main/jni/JJSIValue.h +++ b/jvm/hermes/src/main/jni/JJSIValue.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include #include #include @@ -14,6 +16,22 @@ namespace intuit::playerui { [[noreturn]] void throwNativeHandleReleasedException(std::string nativeHandle); +class ByteArrayBuffer : public Buffer { +public: + ByteArrayBuffer(const void* data, size_t size) : size_(size) { + // Copy the data to ensure ownership, otherwise leads to segfault + buffer_.resize(size); + std::memcpy(buffer_.data(), data, size); + } + + size_t size() const override { return size_; } + const uint8_t* data() const override { return buffer_.data(); } + +private: + std::vector buffer_; + size_t size_; +}; + class JHybridClass : public HybridClass { public: static constexpr auto kJavaDescriptor = "Lcom/intuit/playerui/jsi/HybridClass;"; @@ -68,6 +86,7 @@ class JJSIRuntime : public HybridClass { static void registerNatives(); local_ref evaluateJavaScript(alias_ref, std::string script, std::string sourceURL); + local_ref evaluateHermesBytecode(alias_ref, alias_ref byteArray,std::string sourceURL); local_ref prepareJavaScript(alias_ref, std::string script, std::string sourceURL); local_ref evaluatePreparedJavaScript(alias_ref, alias_ref js); diff --git a/jvm/hermes/src/main/kotlin/com/intuit/playerui/hermes/bridge/runtime/HermesRuntime.kt b/jvm/hermes/src/main/kotlin/com/intuit/playerui/hermes/bridge/runtime/HermesRuntime.kt index b6c1e48b2..13d090def 100644 --- a/jvm/hermes/src/main/kotlin/com/intuit/playerui/hermes/bridge/runtime/HermesRuntime.kt +++ b/jvm/hermes/src/main/kotlin/com/intuit/playerui/hermes/bridge/runtime/HermesRuntime.kt @@ -116,7 +116,11 @@ public class HermesRuntime private constructor( override fun load(scriptContext: ScriptContext): Any? = evaluateInJSThreadBlocking { val sourceMap = scriptContext.sourceMap - if (sourceMap != null) { + val hbc = scriptContext.preCompiledScript + if (hbc.isNotEmpty()) { + println("+++ loading precompiled hbc (${hbc.size} bytes)++") + evaluateHermesBytecode(hbc, scriptContext.id) + } else if (sourceMap != null) { evaluateJavaScriptWithSourceMap(scriptContext.script, sourceMap, scriptContext.id) } else { evaluateJavaScript(scriptContext.script, scriptContext.id) diff --git a/jvm/hermes/src/main/kotlin/com/intuit/playerui/jsi/Value.kt b/jvm/hermes/src/main/kotlin/com/intuit/playerui/jsi/Value.kt index ff7019b89..05e79ec7e 100644 --- a/jvm/hermes/src/main/kotlin/com/intuit/playerui/jsi/Value.kt +++ b/jvm/hermes/src/main/kotlin/com/intuit/playerui/jsi/Value.kt @@ -43,6 +43,9 @@ public open class Runtime( context(RuntimeThreadContext) public external fun evaluateJavaScript(script: String, sourceURL: String = "unknown"): Value + context(RuntimeThreadContext) + public external fun evaluateHermesBytecode(bytecode: ByteArray, sourceURL: String = "unknown"): Value + context(RuntimeThreadContext) public external fun prepareJavaScript(script: String, sourceURL: String = "unknown"): PreparedJavaScript diff --git a/jvm/utils/src/main/kotlin/com/intuit/playerui/utils/MakeFlow.kt b/jvm/utils/src/main/kotlin/com/intuit/playerui/utils/MakeFlow.kt index 4c66a5819..ab68f74f3 100644 --- a/jvm/utils/src/main/kotlin/com/intuit/playerui/utils/MakeFlow.kt +++ b/jvm/utils/src/main/kotlin/com/intuit/playerui/utils/MakeFlow.kt @@ -15,7 +15,11 @@ public open class MakeFlowModule internal constructor( ) : JSScriptPluginWrapper("MakeFlow", sourcePath = "core/make-flow/dist/MakeFlow.native.js") { override fun apply(runtime: Runtime<*>) { runtime.execute(script) - instance = runtime.buildInstance(name) + instance = runtime.execute(""" + ({ + makeFlow: typeof makeFlow !== 'undefined' ? makeFlow : MakeFlow.makeFlow + }) + """) as Node } public fun makeFlow(flow: Node): JsonElement { diff --git a/third_party/hermes/BUILD b/third_party/hermes/BUILD index 07e2f5729..70770e7bf 100644 --- a/third_party/hermes/BUILD +++ b/third_party/hermes/BUILD @@ -45,7 +45,7 @@ cmake( ## Hermes X-compile requires two steps: ## 1. Compile hermesc against host cmake( - name = "hermesc", + name = "hermesc_", env = { "CMAKE_BUILD_PARALLEL_LEVEL": "$(CMAKE_BUILD_PARALLEL_LEVEL)", }, @@ -53,16 +53,25 @@ cmake( install = False, lib_source = "working_directory", out_binaries = ["hermesc"], + out_include_dir = None, postfix_script = "cp -L $$BUILD_TMPDIR/bin/hermesc $$INSTALLDIR/bin", tags = ["no-sandbox"], targets = ["hermesc"], # Specifically build for host, always toolchains = [ - "@bazel_tools//tools/cpp:toolchain", "@player//:cmake_build_parallel_level", ], ) +genrule( + name = "hermesc", + srcs = [":hermesc_"], + outs = ["hermesc_bin"], + cmd = "echo $(SRCS) | tr ' ' '\\n' | grep hermesc$$ | xargs -I {} cp {} $(OUTS)", + executable = True, + visibility = ["//visibility:public"], +) + ## 2. X-compile hermes for Android referencing host built hermesc cmake( name = "android", diff --git a/third_party/rn/arm64-v8a/libhermes.so b/third_party/rn/arm64-v8a/libhermes.so index 889b68960..de3cc4dd9 100644 Binary files a/third_party/rn/arm64-v8a/libhermes.so and b/third_party/rn/arm64-v8a/libhermes.so differ diff --git a/third_party/rn/armeabi-v7a/libhermes.so b/third_party/rn/armeabi-v7a/libhermes.so index 580298534..ca6f52378 100644 Binary files a/third_party/rn/armeabi-v7a/libhermes.so and b/third_party/rn/armeabi-v7a/libhermes.so differ diff --git a/third_party/rn/x86/libhermes.so b/third_party/rn/x86/libhermes.so index 01c62a7eb..dd612a878 100644 Binary files a/third_party/rn/x86/libhermes.so and b/third_party/rn/x86/libhermes.so differ diff --git a/third_party/rn/x86_64/libhermes.so b/third_party/rn/x86_64/libhermes.so index dae37bd66..f3db75824 100644 Binary files a/third_party/rn/x86_64/libhermes.so and b/third_party/rn/x86_64/libhermes.so differ