diff --git a/extension/android/CMakeLists.txt b/extension/android/CMakeLists.txt index c94b95286d0..3da6c8f3a64 100644 --- a/extension/android/CMakeLists.txt +++ b/extension/android/CMakeLists.txt @@ -116,10 +116,7 @@ if(TARGET qnn_executorch_backend) endif() if(TARGET xnnpack_backend) - executorch_target_link_options_shared_lib(xnnpack_backend) - list( - APPEND - link_libraries + set(_xnnpack_so_link_libs xnnpack_backend XNNPACK pthreadpool @@ -127,13 +124,61 @@ if(TARGET xnnpack_backend) xnnpack-microkernels-prod ) if(TARGET kleidiai) - list(APPEND link_libraries kleidiai) + list(APPEND _xnnpack_so_link_libs kleidiai) + endif() + + if(EXECUTORCH_BUILD_XNNPACK_BACKEND_SHARED) + # Same rationale as EXECUTORCH_BUILD_VULKAN_BACKEND_SHARED below: ship + # xnnpack_backend as its own libxnnpack_executorch_backend.so so the + # consumer can opt out at install time. Link only the backend itself + # (whole-archived so the register_backend static initializer fires) and + # the XNNPACK third-party libs + executorch_core. Kernel registration + # archives stay in libexecutorch_jni.so so loading both shared libraries + # together does not trigger Error::RegistrationAlreadyRegistered (0x16). + add_library(xnnpack_executorch_backend SHARED + ${EXECUTORCH_ROOT}/extension/android/jni/jni_noop.cpp + ) + executorch_target_link_options_shared_lib(xnnpack_backend) + target_link_options(xnnpack_executorch_backend PRIVATE -fopenmp -static-openmp) + target_link_options_gc_sections(xnnpack_executorch_backend) + target_link_libraries(xnnpack_executorch_backend + PRIVATE ${_xnnpack_so_link_libs} executorch_core log + ) + set_target_properties( + xnnpack_executorch_backend PROPERTIES LINK_FLAGS "-Wl,-rpath='$ORIGIN'" + ) + # executorch_jni still needs cpuinfo headers / lib for thread count detection. + list(APPEND link_libraries cpuinfo) + else() + executorch_target_link_options_shared_lib(xnnpack_backend) + list(APPEND link_libraries ${_xnnpack_so_link_libs}) endif() endif() if(TARGET vulkan_backend) - executorch_target_link_options_shared_lib(vulkan_backend) - list(APPEND link_libraries vulkan_backend) + if(EXECUTORCH_BUILD_VULKAN_BACKEND_SHARED) + # Build vulkan_backend as a standalone shared library that React Native + # Executorch can ship as an opt-in artifact (libvulkan_executorch_backend.so). + # The .so links only vulkan_backend (with --whole-archive so its + # register_backend static initializer fires) plus executorch_core/vulkan_schema. + # Kernel registration libs (optimized_native_cpu_ops_lib, custom_ops, etc.) + # stay in libexecutorch_jni.so to avoid duplicate Kernel registration when + # both .so are loaded together (Error::RegistrationAlreadyRegistered, 0x16). + add_library(vulkan_executorch_backend SHARED + ${EXECUTORCH_ROOT}/extension/android/jni/jni_noop.cpp + ) + executorch_target_link_options_shared_lib(vulkan_backend) + target_link_options_gc_sections(vulkan_executorch_backend) + target_link_libraries(vulkan_executorch_backend + PRIVATE vulkan_backend vulkan_schema executorch_core log + ) + set_target_properties( + vulkan_executorch_backend PROPERTIES LINK_FLAGS "-Wl,-rpath='$ORIGIN'" + ) + else() + executorch_target_link_options_shared_lib(vulkan_backend) + list(APPEND link_libraries vulkan_backend) + endif() endif() if(EXECUTORCH_BUILD_KERNELS_LLM) diff --git a/extension/android/jni/jni_noop.cpp b/extension/android/jni/jni_noop.cpp new file mode 100644 index 00000000000..4084d021d89 --- /dev/null +++ b/extension/android/jni/jni_noop.cpp @@ -0,0 +1,3 @@ +// Empty translation unit so SHARED libraries that only need to whole-archive +// other static archives (e.g. libvulkan_executorch_backend.so) have at least +// one source file for CMake to invoke the linker on. diff --git a/extension/llm/custom_ops/CMakeLists.txt b/extension/llm/custom_ops/CMakeLists.txt index 8a43a5ddf5c..4d3050dc919 100644 --- a/extension/llm/custom_ops/CMakeLists.txt +++ b/extension/llm/custom_ops/CMakeLists.txt @@ -67,7 +67,11 @@ endif() list(TRANSFORM _custom_ops__srcs PREPEND "${EXECUTORCH_ROOT}/") -if(NOT EXECUTORCH_BUILD_XNNPACK) +if(NOT EXECUTORCH_BUILD_XNNPACK OR EXECUTORCH_BUILD_XNNPACK_BACKEND_SHARED) + # custom_ops doesn't actually call into xnnpack_backend (no XNNPACK refs in + # its sources). The transitive PUBLIC link is what was previously dragging + # XNNCompiler/XNNExecutor into libexecutorch_jni.so via the WHOLE_ARCHIVE on + # custom_ops, which defeats EXECUTORCH_BUILD_XNNPACK_BACKEND_SHARED. Drop it. list(APPEND custom_ops_libs extension_threadpool) else() list(APPEND custom_ops_libs extension_threadpool xnnpack_backend) diff --git a/scripts/build_android_library.sh b/scripts/build_android_library.sh index 5363c64b87c..02e7e3b8403 100755 --- a/scripts/build_android_library.sh +++ b/scripts/build_android_library.sh @@ -33,6 +33,8 @@ build_android_native_library() { fi EXECUTORCH_BUILD_VULKAN="${EXECUTORCH_BUILD_VULKAN:-OFF}" + EXECUTORCH_BUILD_VULKAN_BACKEND_SHARED="${EXECUTORCH_BUILD_VULKAN_BACKEND_SHARED:-OFF}" + EXECUTORCH_BUILD_XNNPACK_BACKEND_SHARED="${EXECUTORCH_BUILD_XNNPACK_BACKEND_SHARED:-OFF}" XNNPACK_ENABLE_ARM_SME2="${XNNPACK_ENABLE_ARM_SME2:-ON}" cmake . -DCMAKE_INSTALL_PREFIX="${CMAKE_OUT}" \ @@ -52,6 +54,8 @@ build_android_native_library() { -DEXECUTORCH_BUILD_QNN="${EXECUTORCH_BUILD_QNN}" \ -DQNN_SDK_ROOT="${QNN_SDK_ROOT}" \ -DEXECUTORCH_BUILD_VULKAN="${EXECUTORCH_BUILD_VULKAN}" \ + -DEXECUTORCH_BUILD_VULKAN_BACKEND_SHARED="${EXECUTORCH_BUILD_VULKAN_BACKEND_SHARED}" \ + -DEXECUTORCH_BUILD_XNNPACK_BACKEND_SHARED="${EXECUTORCH_BUILD_XNNPACK_BACKEND_SHARED}" \ -DXNNPACK_ENABLE_ARM_SME2="${XNNPACK_ENABLE_ARM_SME2}" \ -DFLATCC_ALLOW_WERROR=OFF \ -DSUPPORT_REGEX_LOOKAHEAD=ON \ @@ -68,7 +72,18 @@ build_android_native_library() { # Copy artifacts to ABI specific directory local SO_STAGE_DIR="cmake-out-android-so/${ANDROID_ABI}" mkdir -p ${SO_STAGE_DIR} - cp "${CMAKE_OUT}"/extension/android/*.so "${SO_STAGE_DIR}/libexecutorch.so" + cp "${CMAKE_OUT}"/extension/android/libexecutorch_jni.so "${SO_STAGE_DIR}/libexecutorch.so" + + # Copy standalone Vulkan backend shared library if built. Used by React + # Native Executorch as an opt-in artifact when the app enables Vulkan. + if [ -f "${CMAKE_OUT}"/extension/android/libvulkan_executorch_backend.so ]; then + cp "${CMAKE_OUT}"/extension/android/libvulkan_executorch_backend.so "${SO_STAGE_DIR}/" + fi + + # Same for XNNPACK as a standalone shared library. + if [ -f "${CMAKE_OUT}"/extension/android/libxnnpack_executorch_backend.so ]; then + cp "${CMAKE_OUT}"/extension/android/libxnnpack_executorch_backend.so "${SO_STAGE_DIR}/" + fi # Copy QNN related so library if [ -n "$QNN_SDK_ROOT" ] && [ "$ANDROID_ABI" == "arm64-v8a" ]; then