diff --git a/extension/android/check_exported_symbols.sh b/extension/android/check_exported_symbols.sh new file mode 100755 index 00000000000..4c594a43a97 --- /dev/null +++ b/extension/android/check_exported_symbols.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# 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. + +# Verifies that the Android JNI shared library exports the curated +# EXECUTORCH_1_0 ABI defined in extension/android/jni/version_script.txt. +# +# External backend/kernel .so files rely on these symbols being visible in the +# core .so so that ELF symbol interposition gives them access to the +# process-wide backend/kernel registries at load time. If any of these symbols +# go missing (e.g. the version script is accidentally narrowed or stops being +# applied), split backends would silently register into a private registry and +# delegation would fail with "backend not found" at runtime. Fail the build +# instead. +# +# Usage: check_exported_symbols.sh [path-to-llvm-nm] + +set -euo pipefail + +SO_PATH="${1:?usage: check_exported_symbols.sh [llvm-nm]}" +NM="${2:-llvm-nm}" + +# Demangled symbol fragments that must be exported by the core .so. +REQUIRED_SYMBOLS=( + # Backend registration (runtime/backend/interface.h) + "executorch::runtime::register_backend" + "executorch::runtime::get_backend_class" + # BackendInterface RTTI/vtable; external backends subclass it and the key + # function (the destructor) is emitted in the core .so. + "typeinfo for executorch::runtime::BackendInterface" + "vtable for executorch::runtime::BackendInterface" + # Kernel registration (runtime/kernel/operator_registry.h) + "executorch::runtime::register_kernels" + # exec_aten data model used by backend implementations (the portable Tensor + # is executorch::runtime::etensor::Tensor; executorch::aten::Tensor is a + # using-alias, so symbols carry the real namespace). + "executorch::runtime::etensor::Tensor" + "executorch::runtime::EValue" + # PAL hooks so backend .so files share process-wide logging + "et_pal_emit_log_message" + # JNI entry points (pre-existing export contract) + "JNI_OnLoad" +) + +EXPORTED="$("${NM}" -D --defined-only -C "${SO_PATH}")" + +missing=0 +for sym in "${REQUIRED_SYMBOLS[@]}"; do + if ! grep -qF -- "${sym}" <<<"${EXPORTED}"; then + echo "ERROR: ${SO_PATH} does not export required ABI symbol: ${sym}" >&2 + missing=1 + fi +done + +if [[ "${missing}" -ne 0 ]]; then + echo "The EXECUTORCH_1_0 ABI surface is incomplete." >&2 + echo "See extension/android/jni/version_script.txt and" >&2 + echo "https://github.com/pytorch/executorch/issues/10457 (milestone 1)." >&2 + exit 1 +fi + +echo "OK: ${SO_PATH} exports the EXECUTORCH_1_0 ABI surface." diff --git a/extension/android/jni/version_script.txt b/extension/android/jni/version_script.txt index a8b7672c1eb..5a277d934b3 100644 --- a/extension/android/jni/version_script.txt +++ b/extension/android/jni/version_script.txt @@ -1,7 +1,51 @@ -{ +# Export map for the Android JNI shared library (libexecutorch_jni.so, +# repackaged as libexecutorch.so in the AAR). +# +# In addition to the JNI entry points, the EXECUTORCH_1_0 node exports the +# curated C++ runtime API surface that an external backend or kernel .so needs +# in order to self-register into this process's registries when it is loaded. +# This is milestone 1 of the hot-pluggable backends plan: +# https://github.com/pytorch/executorch/issues/10457 +# +# The export boundary is namespace-scoped rather than symbol-by-symbol so that +# it stays maintainable as the public headers evolve: +# - executorch::runtime::* registration entry points (register_backend, +# get_backend_class, register_kernels), BackendInterface, EValue, +# MemoryManager / MemoryAllocator, FreeableBuffer, logging, and the rest of +# the public runtime API declared under runtime/. +# - executorch::aten::* the exec_aten data model (Tensor, EValue, +# Scalar, ...) that backend implementations are written against in +# portable (non-ATen) builds. +# - et_pal_* the platform abstraction layer C API, so backend +# .so files share the process-wide logging / allocation / time hooks. +# +# Everything else — runtime internals outside the public namespaces, backend +# implementation details, and kernel implementations — stays hidden, which +# preserves the linker's ability to strip and optimize them. +# +# Additive changes must go into a new version node (e.g. EXECUTORCH_1_1) so +# that external .so files can depend on a stable ABI tag. Never remove or +# narrow symbols from an existing node. +EXECUTORCH_1_0 { global: + # JNI entry points. JNI_OnLoad; Java_*; + + # Platform abstraction layer (extern "C" API). + et_pal_*; + + extern "C++" { + "executorch::runtime::*"; + "executorch::aten::*"; + # RTTI and vtables for polymorphic public types (e.g. BackendInterface) + # whose key functions are emitted in this library; external .so files + # subclass these and need the symbols at link/load time. + "typeinfo for executorch::runtime::*"; + "typeinfo for executorch::aten::*"; + "vtable for executorch::runtime::*"; + "vtable for executorch::aten::*"; + }; local: *; }; diff --git a/scripts/build_android_library.sh b/scripts/build_android_library.sh index 5363c64b87c..d93e4196f72 100755 --- a/scripts/build_android_library.sh +++ b/scripts/build_android_library.sh @@ -70,6 +70,12 @@ build_android_native_library() { mkdir -p ${SO_STAGE_DIR} cp "${CMAKE_OUT}"/extension/android/*.so "${SO_STAGE_DIR}/libexecutorch.so" + # Verify the curated EXECUTORCH_1_0 ABI surface is exported (milestone 1 of + # https://github.com/pytorch/executorch/issues/10457). Backend .so files + # loaded alongside the core .so depend on these symbols for self-registration. + NM_BIN=$(echo "${ANDROID_NDK}"/toolchains/llvm/prebuilt/*/bin/llvm-nm) + bash extension/android/check_exported_symbols.sh "${SO_STAGE_DIR}/libexecutorch.so" "${NM_BIN}" + # Copy QNN related so library if [ -n "$QNN_SDK_ROOT" ] && [ "$ANDROID_ABI" == "arm64-v8a" ]; then cp "${CMAKE_OUT}"/lib/executorch/backends/qualcomm/libqnn_executorch_backend.so ${SO_STAGE_DIR}