Skip to content

Commit 2088dd5

Browse files
Merge branch 'main' into tosa_dialect_pad
2 parents 679c32e + 122fdef commit 2088dd5

77 files changed

Lines changed: 7421 additions & 219 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
---
2+
name: binary-size
3+
description: Analyze and reduce ExecuTorch binary size. Use when investigating binary size, running size tests, or optimizing the runtime for size-constrained deployments.
4+
---
5+
6+
# Binary Size
7+
8+
## Start from the `main` branch of executorch
9+
Ask the user where the executorch repo is.
10+
11+
```bash
12+
git checkout main && git pull
13+
```
14+
15+
## Build and measure baseline
16+
```bash
17+
conda activate executorch
18+
bash test/build_size_test.sh
19+
strip -o /tmp/size_test_stripped cmake-out/test/size_test
20+
strip -o /tmp/size_test_all_ops_stripped cmake-out/test/size_test_all_ops
21+
ls -la /tmp/size_test_stripped /tmp/size_test_all_ops_stripped
22+
```
23+
24+
Produces two binaries:
25+
- `cmake-out/test/size_test` — ExecuTorch runtime without operator implementations
26+
- `cmake-out/test/size_test_all_ops` — ExecuTorch runtime with portable ops
27+
28+
## Analyze with bloaty
29+
```bash
30+
bloaty cmake-out/test/size_test -d symbols -n 30 # by symbol
31+
bloaty cmake-out/test/size_test -d sections # by ELF section
32+
bloaty <after> -- <before> # diff two builds
33+
nm -S <binary> | sort -k2 -rn | head -30 # symbol sizes
34+
strings <binary> | less # string literals in .rodata
35+
```
36+
37+
Note: `bloaty -d compileunits` requires debug info (`-g`). The Release build does not include it.
38+
39+
## Key build flags
40+
Set by `test/build_size_test.sh`:
41+
- `CMAKE_BUILD_TYPE=Release`
42+
- `EXECUTORCH_OPTIMIZE_SIZE=ON` — enables `-Os`, `-fno-exceptions`, `-fno-rtti`, unwind table suppression
43+
- `CXXFLAGS="-fno-exceptions -fno-rtti -Wall -Werror"`
44+
45+
## Constraints
46+
- Use **CMake** to build (not Buck)
47+
- **C++17 minimum** language standard
48+
- Must build on **GCC 9** (CI uses `executorch-ubuntu-22.04-gcc9-nopytorch`) and **Clang 12** — avoid compiler-specific flags or pragmas without version guards
49+
- Do not regress existing functionality — run tests for modified files
50+
- Do not change build flags in `build_size_test.sh` for size reductions
51+
- Do not increase latency in the core runtime
52+
53+
## Where to look for size reductions
54+
- `.text`: look for large functions, template bloat, duplicate instantiations
55+
- `.rodata`: verbose error messages, format strings, embedded file paths (`__FILE__`)
56+
- `.eh_frame`: should already be suppressed when `EXECUTORCH_OPTIMIZE_SIZE=ON`
57+
- Static init functions (`nm -S <binary> | grep GLOBAL__sub_I`): use `constexpr` constructors to constant-initialize static arrays
58+
- Logging strings: `ET_LOG_ENABLED=0` in Release eliminates format strings; ensure it propagates to consumers via `PUBLIC` compile definitions on cmake targets
59+
- Inline header functions: watch for compile-define mismatches between library and consumer TUs (e.g. `ET_LOG_ENABLED` set in library but not in consumer)
60+
61+
## For each change
62+
1. Create a branch: `git checkout -b binary-size-<N>`
63+
2. Implement, rebuild, measure stripped sizes
64+
3. Create a separate PR — one logical change per PR
65+
4. Record results in `binary-size-<N>.md`:
66+
67+
| Binary | This change (N vs N-1) | Cumulative (N vs main) |
68+
|---|---|---|
69+
| `size_test` (stripped) | -X | -Y |
70+
| `size_test_all_ops` (stripped) | -X | -Y |
71+
72+
5. Update the CI size threshold in `.github/workflows/pull.yml` if sizes decrease

.flake8

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ exclude =
7474
./build,
7575
./configurations,
7676
./docs,
77+
./exir/_serialize/generated/executorch_flatbuffer,
7778
./third_party,
7879
*.pyi
7980

.github/workflows/pull.yml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -475,10 +475,8 @@ jobs:
475475
output=$(ls -la cmake-out/test/size_test)
476476
arr=($output)
477477
size=${arr[4]}
478-
# threshold=48120 on devserver with gcc9
479-
# todo(lfq): update once binary size is below 50kb.
480-
# Note: using gcc9-nopytorch container with pinned nightly PyTorch
481-
threshold="63785"
478+
# Current CI size: 48008 (gcc9-nopytorch, 2026-03-06)
479+
threshold="48500"
482480
if [[ "$size" -le "$threshold" ]]; then
483481
echo "Success $size <= $threshold"
484482
else
@@ -513,7 +511,8 @@ jobs:
513511
output=$(ls -la cmake-out/test/size_test)
514512
arr=($output)
515513
size=${arr[4]}
516-
threshold="51752"
514+
# Current CI size: 44160 (clang12, 2026-03-06)
515+
threshold="45000"
517516
if [[ "$size" -le "$threshold" ]]; then
518517
echo "Success $size <= $threshold"
519518
else

.github/workflows/trunk.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ jobs:
402402
setup_script_args=""
403403
if [[ ${{ matrix.os}} == "bare_metal" ]]; then
404404
toolchain_prefix=arm-none-eabi-
405-
threshold="110642" # 108 KiB
405+
threshold="111000" # 111 KiB
406406
toolchain_cmake=examples/arm/ethos-u-setup/arm-none-eabi-gcc.cmake
407407
elif [[ ${{ matrix.os}} == "zephyr-preset" ]]; then
408408
setup_script_args="--target-toolchain zephyr"
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Validate Flatbuffer Generation
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
paths:
7+
- "schema/**"
8+
- "exir/_serialize/generated/executorch_flatbuffer/**"
9+
10+
jobs:
11+
exir-flatbuffer:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v4
16+
17+
- name: setup python
18+
uses: actions/setup-python@v5
19+
with:
20+
python-version: "3.12"
21+
22+
- name: install executorch (with flatc)
23+
run: ./install_executorch.sh -e
24+
25+
- name: Generate flatbuffer Python
26+
run: python exir/_serialize/generate_program.py
27+
28+
- name: Validate executorch_flatbuffer is unchanged
29+
run: |
30+
git add -A exir/_serialize/generated/executorch_flatbuffer
31+
if ! git diff --cached --quiet -- exir/_serialize/generated/executorch_flatbuffer; then
32+
echo "Error: executorch_flatbuffer has uncommitted changes."
33+
echo "Please run 'python exir/_serialize/generate_program.py' to regenerate the files and commit the changes."
34+
exit 1
35+
fi

.lintrunner.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ exclude_patterns = [
88
'**/third-party/**',
99
'.github/scripts/**',
1010
'exir/serde/**',
11+
'exir/_serialize/generated/executorch_flatbuffer/**',
1112
]
1213
command = [
1314
'python',
@@ -39,6 +40,7 @@ exclude_patterns = [
3940
'third-party/**',
4041
'**/third-party/**',
4142
'exir/serde/**',
43+
'exir/_serialize/generated/executorch_flatbuffer/**',
4244
]
4345
command = [
4446
'python',

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- `/building` - Build runners or C++ libs
77
- `/profile` - Profile execution
88
- `/cortex-m` - Build, test, or develop the Cortex-M backend
9+
- `/binary-size` - Analyze and reduce binary size
910

1011
Reference docs in `.claude/`: backends, runtime-api, quantization, llm-export, faq, tokenizers
1112

CMakeLists.txt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,23 @@ endif()
221221

222222
add_subdirectory(third-party)
223223

224+
# Size-optimized builds disable exceptions, RTTI, and unwind tables.
225+
# ExecuTorch's runtime uses Result<T>/Error instead of exceptions, so
226+
# -fno-exceptions is safe. Suppressing .eh_frame via -fno-unwind-tables is safe
227+
# because exceptions are disabled. Placed after add_subdirectory(third-party) to
228+
# avoid leaking into third-party configure checks (e.g. gflags).
229+
if(EXECUTORCH_OPTIMIZE_SIZE
230+
AND NOT WIN32
231+
AND NOT MSVC
232+
)
233+
set(CMAKE_CXX_FLAGS_RELEASE
234+
"${CMAKE_CXX_FLAGS_RELEASE} -fno-exceptions -fno-rtti -fno-asynchronous-unwind-tables -fno-unwind-tables"
235+
)
236+
set(CMAKE_C_FLAGS_RELEASE
237+
"${CMAKE_C_FLAGS_RELEASE} -fno-asynchronous-unwind-tables -fno-unwind-tables"
238+
)
239+
endif()
240+
224241
if(NOT DEFINED FXDIV_SOURCE_DIR)
225242
set(ORIGINAL_CMAKE_POSITION_INDEPENDENT_CODE_FLAG
226243
${CMAKE_POSITION_INDEPENDENT_CODE}
@@ -477,6 +494,9 @@ target_include_directories(
477494
target_compile_definitions(
478495
executorch_core PUBLIC C10_USING_CUSTOM_GENERATED_MACROS
479496
)
497+
if(NOT EXECUTORCH_ENABLE_LOGGING)
498+
target_compile_definitions(executorch_core PUBLIC ET_LOG_ENABLED=0)
499+
endif()
480500
target_compile_options(executorch_core PUBLIC ${_common_compile_options})
481501
if(MAX_KERNEL_NUM)
482502
target_compile_definitions(
@@ -525,6 +545,9 @@ add_library(executorch ${_executorch__srcs})
525545
target_link_libraries(executorch PRIVATE executorch_core)
526546
target_include_directories(executorch PUBLIC ${_common_include_directories})
527547
target_compile_definitions(executorch PUBLIC C10_USING_CUSTOM_GENERATED_MACROS)
548+
if(NOT EXECUTORCH_ENABLE_LOGGING)
549+
target_compile_definitions(executorch PUBLIC ET_LOG_ENABLED=0)
550+
endif()
528551
target_compile_options(executorch PUBLIC ${_common_compile_options})
529552
executorch_target_link_options_shared_lib(executorch)
530553

backends/arm/scripts/pre-commit

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/bin/bash
2-
# Copyright 2025 Arm Limited and/or its affiliates.
2+
# Copyright 2025-2026 Arm Limited and/or its affiliates.
33
#
44
# This source code is licensed under the BSD-style license found in the
55
# LICENSE file in the root directory of this source tree.
@@ -9,5 +9,14 @@ git rev-list --format=%s --max-count=1 HEAD | grep -q WIP && exit 0
99

1010
# Check 2: lintunner on latest patch.
1111
lintrunner -a --revision 'HEAD^' --skip MYPY
12-
commit_files=$(git diff-tree --no-commit-id --name-only --diff-filter=M HEAD -r)
13-
git add $commit_files || true
12+
13+
# Re-stage only paths that were already staged before lintrunner ran.
14+
# This preserves lint fixes for files the user intended to commit, and avoids
15+
# accidentally staging unrelated working-tree changes during commit --amend.
16+
staged_files=$(git diff --cached --name-only --diff-filter=ACMR)
17+
while IFS= read -r path; do
18+
[ -z "${path}" ] && continue
19+
if [ -f "${path}" ]; then
20+
git add -- "${path}" || true
21+
fi
22+
done <<< "${staged_files}"

backends/arm/scripts/pre-push

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,34 +99,52 @@ for COMMIT in ${COMMITS}; do
9999
# most of the time.
100100
echo -e "${INFO} License check"
101101

102+
# Check only files with content changes.
103+
# Skip pure moves/renames (R100) where file content is unchanged.
104+
license_files=()
105+
while IFS=$'\t' read -r status path1 path2; do
106+
case "$status" in
107+
A|M)
108+
license_files+=("$path1")
109+
;;
110+
R100)
111+
;;
112+
R*)
113+
license_files+=("$path2")
114+
;;
115+
esac
116+
done < <(git diff-tree --no-commit-id --name-status --diff-filter=AMR -r -M ${COMMIT})
117+
102118
current_year=$(date +%Y)
103119
failed_license_check=false
104-
for commited_file in $commit_files; do
120+
for committed_file in "${license_files[@]}"; do
105121
# Skip files with certain extensions
106-
case "$commited_file" in
122+
case "$committed_file" in
107123
*.md|*.md.in|*.json|*.yml|*.yaml|*.cmake|*.patch|.gitignore)
108-
echo -e "${INFO} Skipping license check for ${commited_file} (excluded extension)"
124+
echo -e "${INFO} Skipping license check for ${committed_file} (excluded extension)"
109125
continue
110126
;;
111127
esac
112128

113-
file_header=$(head "$commited_file")
129+
file_header=$(head "$committed_file")
114130
if ! echo "$file_header" | grep -qi "Arm"; then
115-
echo -e "${WARNING} No Arm copyright header in ${commited_file}"\
131+
echo -e "${WARNING} No Arm copyright header in ${committed_file}"\
116132
" (skipping license year check)"
117133
continue
118134
fi
119135

120136
if ! echo "$file_header" | grep -q "$current_year Arm"; then
121-
echo -e "${ERROR} Header in $commited_file did not contain"\
137+
echo -e "${ERROR} Header in $committed_file did not contain"\
122138
"'$current_year Arm'"
123139
failed_license_check=true
124140
else
125-
echo -e "${SUCCESS} $commited_file passed license check"
141+
echo -e "${SUCCESS} $committed_file passed license check"
126142
fi
127143
done
128144

129-
if [[ $failed_license_check == true ]]; then
145+
if [[ ${#license_files[@]} -eq 0 ]]; then
146+
echo -e "${INFO} No files with content changes to check"
147+
elif [[ $failed_license_check == true ]]; then
130148
FAILED=1
131149
else
132150
echo -e "${SUCCESS} All files passed license check"
@@ -181,8 +199,8 @@ for COMMIT in ${COMMITS}; do
181199

182200
# Determine whether all modifications are under backends/arm or examples/arm
183201
only_arm_changes=true
184-
for commited_file in $commit_files; do
185-
if [[ $commited_file != backends/arm/* && $commited_file != examples/arm/* ]]; then
202+
for committed_file in $commit_files; do
203+
if [[ $committed_file != backends/arm/* && $committed_file != examples/arm/* ]]; then
186204
only_arm_changes=false
187205
break
188206
fi

0 commit comments

Comments
 (0)