Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 83 additions & 13 deletions extension/android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -115,25 +115,83 @@ if(TARGET qnn_executorch_backend)
list(APPEND link_libraries qnn_executorch_backend)
endif()

# EXECUTORCH_BUILD_XNNPACK_BACKEND_SHARED /
# EXECUTORCH_BUILD_VULKAN_BACKEND_SHARED emit a backend as its own .so so a
# consumer can drop the ones it does not use. A split backend still links
# executorch_core statically, so it carries a second copy of the backend
# registry (registered_backends[] in runtime/backend/interface.cpp).
# Registration reaches the runtime's copy only through PLT interposition: the
# register_backend call in the backend .so is a JUMP_SLOT relocation, so the
# dynamic linker binds it to the first definition in the global lookup scope.
# Two things have to hold for that to be this library's copy:
#
# * libexecutorch_jni.so has to export register_backend. The default version
# script hides everything but JNI_OnLoad/Java_*, so jni/version_script.txt is
# swapped for jni/version_script_shared_backends.txt below whenever a backend
# is split out.
# * The consumer has to load libexecutorch_jni.so ahead of the backend .so in
# the same lookup scope. Naming both in the link line of the library that uses
# them, runtime first, does that: the runtime lands earlier in DT_NEEDED and
# wins the lookup. dlopen()ing a backend .so on its own does not, and the
# backend then registers into its own table while the runtime reports
# DelegateNotFound.
#
# The same interposition is what keeps a split XNNPACK on one thread pool: the
# pthreadpool symbols are exported from here too, so the backend .so binds to
# the pool extension_threadpool configures instead of a second, unconfigured
# copy of its own.
if(TARGET xnnpack_backend)
executorch_target_link_options_shared_lib(xnnpack_backend)
list(
APPEND
link_libraries
xnnpack_backend
XNNPACK
pthreadpool
cpuinfo
xnnpack-microkernels-prod
set(_xnnpack_so_link_libs xnnpack_backend XNNPACK pthreadpool cpuinfo
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)
# Whole-archive the backend so its register_backend static initializer
# fires, and take only the XNNPACK third-party libs with it. The kernel
# registration archives stay in libexecutorch_jni.so, so loading both
# libraries together does not register a kernel twice
# (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_gc_sections(xnnpack_executorch_backend)
target_link_libraries(
xnnpack_executorch_backend PRIVATE ${_xnnpack_so_link_libs}
executorch_core log
)
# 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)
# Same shape as the XNNPACK split above: whole-archive vulkan_backend and
# take only vulkan_schema with it, leaving the kernel registration archives
# in libexecutorch_jni.so.
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
)
else()
executorch_target_link_options_shared_lib(vulkan_backend)
list(APPEND link_libraries vulkan_backend)
endif()
endif()

if(EXECUTORCH_BUILD_KERNELS_LLM)
Expand Down Expand Up @@ -242,9 +300,21 @@ target_include_directories(

target_compile_options(executorch_jni PUBLIC ${_common_compile_options})

if(EXECUTORCH_BUILD_XNNPACK_BACKEND_SHARED
OR EXECUTORCH_BUILD_VULKAN_BACKEND_SHARED
)
set(_executorch_jni_version_script
"${CMAKE_CURRENT_SOURCE_DIR}/jni/version_script_shared_backends.txt"
)
else()
set(_executorch_jni_version_script
"${CMAKE_CURRENT_SOURCE_DIR}/jni/version_script.txt"
)
endif()

target_link_options(
executorch_jni PRIVATE
"LINKER:--version-script,${CMAKE_CURRENT_SOURCE_DIR}/jni/version_script.txt"
"LINKER:--version-script,${_executorch_jni_version_script}"
)
target_link_options_gc_sections(executorch_jni)

Expand Down
11 changes: 11 additions & 0 deletions extension/android/jni/jni_noop.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

// 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.
23 changes: 23 additions & 0 deletions extension/android/jni/version_script_shared_backends.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
global:
JNI_OnLoad;
Java_*;
# A backend built as its own .so (EXECUTORCH_BUILD_XNNPACK_BACKEND_SHARED /
# EXECUTORCH_BUILD_VULKAN_BACKEND_SHARED) carries its own statically linked
# copy of executorch_core, and therefore its own backend registry. Its call
# to register_backend goes through the PLT, so exporting the registry entry
# points here lets the dynamic linker bind that call to this library's
# registry - the one the runtime actually reads from.
extern "C++" {
executorch::runtime*::register_backend*;
executorch::runtime*::get_backend_class*;
executorch::runtime*::get_backend_name*;
executorch::runtime*::get_num_registered_backends*;
};
# Same reason, for the thread pool the split XNNPACK backend runs on: bind
# its pthreadpool calls to the pool extension_threadpool configures here
# rather than to a second, unconfigured copy inside the backend .so.
pthreadpool_*;
local:
*;
};
6 changes: 5 additions & 1 deletion extension/llm/custom_ops/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
17 changes: 16 additions & 1 deletion scripts/build_android_library.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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}" \
Expand All @@ -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 \
Expand All @@ -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
Expand Down
21 changes: 21 additions & 0 deletions tools/cmake/preset/default.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,18 @@ define_overridable_option(
define_overridable_option(
EXECUTORCH_BUILD_VULKAN "Build the Vulkan backend" BOOL OFF
)
define_overridable_option(
EXECUTORCH_BUILD_XNNPACK_BACKEND_SHARED
"Build the XNNPACK backend as its own shared library instead of linking it into the Android JNI library"
BOOL
OFF
)
define_overridable_option(
EXECUTORCH_BUILD_VULKAN_BACKEND_SHARED
"Build the Vulkan backend as its own shared library instead of linking it into the Android JNI library"
BOOL
OFF
)
define_overridable_option(
EXECUTORCH_BUILD_WEBGPU "Build the WebGPU backend" BOOL OFF
)
Expand Down Expand Up @@ -357,6 +369,15 @@ check_required_options_on(
IF_ON EXECUTORCH_BUILD_QNN REQUIRES EXECUTORCH_BUILD_EXTENSION_TENSOR
)

check_required_options_on(
IF_ON EXECUTORCH_BUILD_XNNPACK_BACKEND_SHARED REQUIRES
EXECUTORCH_BUILD_XNNPACK
)

check_required_options_on(
IF_ON EXECUTORCH_BUILD_VULKAN_BACKEND_SHARED REQUIRES EXECUTORCH_BUILD_VULKAN
)

check_required_options_on(
IF_ON EXECUTORCH_ENABLE_BUNDLE_IO REQUIRES EXECUTORCH_BUILD_DEVTOOLS
)
Expand Down
Loading