feat(build): #243 #244 strip .eh_frame on release builds across all GCC platforms#245
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (19)
✅ Files skipped from review due to trivial changes (2)
🚧 Files skipped from review as they are similar to previous changes (16)
📝 WalkthroughWalkthroughThis PR implements an automatic ChangesEhFrame policy and cross-platform implementation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/fbuild-build/src/avr/orchestrator.rs`:
- Around line 93-97: The fast-path fingerprint metadata currently omits
eh_frame_policy so builds can be reused when
FBUILD_KEEP_EH_FRAME/FBUILD_STRIP_EH_FRAME change; update the metadata
fingerprint construction to include the local eh_frame_policy value wherever
metadata_hash or the fast-path fingerprint is computed (references: the
eh_frame_policy variable, metadata_hash/fingerprint construction code used in
this file and the other indicated blocks around lines 130-146 and 232-233),
i.e., add eh_frame_policy into the set of inputs hashed for the
metadata/fingerprint so changes to the policy invalidate cached artifacts.
In `@crates/fbuild-build/src/esp32/orchestrator.rs`:
- Around line 166-175: The fingerprint metadata for ESP32 currently omits the
computed eh_frame_policy, so add the value produced by
crate::eh_frame_policy_compute::compute_eh_frame_policy (the eh_frame_policy
variable computed from
fbuild_config::sdkconfig::SdkConfigSummary::from_project_dir) into the
Esp32FingerprintMetadata inputs used for hashing/ cache key; update the code
that constructs Esp32FingerprintMetadata (the struct/constructor where
platform-specific hash inputs are collected) to include eh_frame_policy so cache
hits respect strip/preserve mode changes.
In `@crates/fbuild-build/src/rp2040/orchestrator.rs`:
- Around line 72-75: The eh_frame_policy computed by
compute_eh_frame_policy(&ctx, params.profile, None) is not being encoded into
the fast-path fingerprint, so builds can be reused across different EH frame
strip/keep settings; update the fingerprint construction logic (the code that
builds the fast-path fingerprint/key in orchestrator.rs) to include the
eh_frame_policy value (use the eh_frame_policy variable) as part of the
fingerprint metadata so changes to FBUILD_STRIP_EH_FRAME/FBUILD_KEEP_EH_FRAME
produce distinct fingerprints and avoid stale artifact reuse.
In `@crates/fbuild-build/src/teensy/orchestrator.rs`:
- Around line 76-80: The no-op fingerprint currently misses the resolved
eh_frame_policy computed by compute_eh_frame_policy(&ctx, params.profile, None),
so include the eh_frame_policy value into the fingerprint metadata used for
cache/no-op decisions; locate where the no-op fingerprint is assembled and add
eh_frame_policy (or a serialization of it) as an input field so changes to
FBUILD_KEEP_EH_FRAME / FBUILD_STRIP_EH_FRAME (and the computed policy)
invalidate the cache.
In `@crates/fbuild-build/tests/eh_frame_strip_esp32.rs`:
- Around line 102-106: The test must require an explicit ELF path instead of
falling back to firmware_path: replace the chained
.or(preserve_result.firmware_path.clone()) usage when building preserve_elf so
it calls preserve_result.elf_path.clone().expect("preserve build should produce
ELF path") (and do the same change for the similar block around the second
occurrence that currently uses firmware_path fallback) so parsing .eh_frame
always operates on an actual ELF file.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 7c2aa215-0835-46ae-af98-e881234454a6
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (19)
crates/fbuild-build/Cargo.tomlcrates/fbuild-build/src/apollo3/orchestrator.rscrates/fbuild-build/src/avr/avr_compiler.rscrates/fbuild-build/src/avr/orchestrator.rscrates/fbuild-build/src/eh_frame_policy.rscrates/fbuild-build/src/eh_frame_policy_compute.rscrates/fbuild-build/src/esp32/esp32_compiler.rscrates/fbuild-build/src/esp32/orchestrator.rscrates/fbuild-build/src/esp8266/esp8266_compiler.rscrates/fbuild-build/src/esp8266/orchestrator.rscrates/fbuild-build/src/generic_arm/arm_compiler.rscrates/fbuild-build/src/lib.rscrates/fbuild-build/src/rp2040/orchestrator.rscrates/fbuild-build/src/stm32/orchestrator.rscrates/fbuild-build/src/teensy/orchestrator.rscrates/fbuild-build/src/teensy/teensy_compiler.rscrates/fbuild-build/tests/eh_frame_strip_esp32.rscrates/fbuild-config/src/lib.rscrates/fbuild-config/src/sdkconfig.rs
…CC platforms GCC emits .eh_frame unwinding tables by default. On a stock FastLED ESP32-S3 Blink build this is ~225 KB / 36% of the firmware -- dead metadata no _Unwind_* API ever consumes when exceptions and panic backtrace are both off. FastLED's library-side pragma workaround (FastLED/FastLED#2423) is a no-op on modern GCC (audited in FastLED/FastLED#2473); the only reliable fix is cc1-level flags, which is the build system's job. This PR adds a shared eh_frame strip policy that injects `-fno-asynchronous-unwind-tables -fno-unwind-tables` to every TU when appropriate. The decision respects user intent at every layer: explicit env override, debug builds, `-fexceptions` in build_flags, ESP-IDF panic-backtrace / gdbstub / ocdaware / optimization-debug sdkconfig keys. New modules - `crates/fbuild-build/src/eh_frame_policy.rs` -- pure `decide()` function over plain inputs, plus `STRIP_FLAGS` constant. 16 unit tests cover the full precedence matrix. - `crates/fbuild-build/src/eh_frame_policy_compute.rs` -- orchestrator- side helper that reads env vars + extracts BuildContext fields and calls `decide()`. - `crates/fbuild-config/src/sdkconfig.rs` -- minimal sdkconfig parser surfacing the 4 keys the policy reads. Defaults to ESP-IDF Arduino defaults (`panic_print_backtrace = true`) so the safe assumption for an unmodified project is Preserve. 14 unit tests. Per-platform wiring - ESP32, generic ARM (STM32/RP2040/NRF52), Teensy, ESP8266, AVR each get a `with_eh_frame_policy()` setter and a 3-line append in `common_flags()`. Mirrors the existing `with_build_unflags` pattern. - All orchestrators (esp32, generic_arm, teensy, esp8266, avr, stm32, rp2040, apollo3) compute the policy once per build and pass it to each compiler construction site. ESP32 also reads sdkconfig from the project dir. Other platforms pass `None`. - 10 new platform unit tests (2 per platform) assert STRIP_FLAGS presence under `Strip` and absence under default `Preserve`. Integration test (`#[ignore]`, toolchain-required) - `crates/fbuild-build/tests/eh_frame_strip_esp32.rs` builds a tempfile ESP32 project twice (FBUILD_KEEP_EH_FRAME=1 vs FBUILD_STRIP_EH_FRAME=1) and asserts firmware.bin delta >= 150 KB plus `.eh_frame` ELF section delta >= 50 KB via the `object` crate. User-facing overrides - `FBUILD_STRIP_EH_FRAME=1` -- force strip regardless of detection - `FBUILD_KEEP_EH_FRAME=1` -- force preserve Behavior under defaults - ESP32 with stock Arduino framework: Preserve (matches user expectation of esp32_exception_decoder showing readable backtraces). - Teensy / STM32 / RP2040 / NRF52 / ESP8266: Strip (their JSON configs already ship `-fno-exceptions`; nothing in their toolchain consumes eh_frame). Immediate flash savings on every release build. - AVR: Strip wired for uniformity; runtime is a no-op because AVR-gcc only emits eh_frame when `-fexceptions` is on, which AVR's JSON disables. - Debug builds (`build_type = debug`, or `-Og`/`-O0` flags) always Preserve, even on platforms that would otherwise strip. Test coverage - 16 eh_frame_policy unit tests (decision matrix) - 14 sdkconfig unit tests (parser + project-dir lookup) - 10 per-platform compiler tests (STRIP_FLAGS presence/absence) - 1 integration test (`#[ignore]`, runs locally with ESP32 toolchain) - Existing 534 fbuild-build + 120 fbuild-config lib tests stay green. Closes #243. Closes #244. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
42e0459 to
6ba0003
Compare
|
CodeRabbit review folded in (squashed force-push to 6ba0003):
Verification: workspace |
…atformIO) (#247) Add a new "PlatformIO compatibility: .eh_frame strip" section to README that explains why fbuild's release binaries on Teensy/STM32/RP2040/NRF52/ ESP8266 are smaller than PlatformIO's, summarizes the EhFramePolicy precedence introduced in #245, and shows the two opt-out paths (build_flags = -funwind-tables in platformio.ini, FBUILD_KEEP_EH_FRAME=1 env var). Also adds a one-line forward reference near the existing "platformio.ini compatible" tagline so users notice the deviation. Closes #246. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes #243.
Closes #244.
Summary
GCC emits
.eh_frameunwinding tables by default. On a stock FastLED ESP32-S3 Blink build this is ~225 KB / 36 % of the firmware — dead metadata no_Unwind_*API ever consumes when exceptions and panic backtrace are both off (audit: FastLED/FastLED#2473). FastLED's library-side pragma workaround (PR #2423) is a no-op on modern GCC; the only reliable fix iscc1-level flags, which is the build system's job.This PR adds a shared eh_frame strip policy that injects
-fno-asynchronous-unwind-tables -fno-unwind-tablesto every TU when appropriate. The decision respects user intent at every layer.Architecture
crates/fbuild-build/src/eh_frame_policy.rsdecide(&EhFrameInputs) -> EhFramePolicy. Pure function, 16 unit tests covering the precedence matrix.crates/fbuild-config/src/sdkconfig.rscrates/fbuild-build/src/eh_frame_policy_compute.rsdecide().*_compiler.rsfiles (esp32, generic_arm, teensy, esp8266, avr)with_eh_frame_policy()setter + 3-line append incommon_flags(). Mirrors existingwith_build_unflagspattern.Decision precedence
FBUILD_STRIP_EH_FRAME=1→ StripFBUILD_KEEP_EH_FRAME=1→ Preservebuild_type = debug(or-Og/-O0in build_flags) → Preserve-fexceptions/-funwind-tables/-fasynchronous-unwind-tablesin build_flags (not negated) → Preservepanic_print_backtrace,panic_gdbstub,debug_ocdaware, oroptimization_debugset → PreserveBehavior under defaults
CONFIG_ESP_SYSTEM_PANIC_PRINT_BACKTRACE=ysoesp32_exception_decoderkeeps producing readable crash backtraces.-fno-exceptions; nothing in their toolchain consumes eh_frame. Immediate flash savings on every release build.-fexceptionsis on, which AVR's JSON disables. Wired for uniformity.-Og/-O0builds always keep eh_frame for crash debugging.User-facing overrides
How to verify the binary shrank
FBUILD_KEEP_EH_FRAME=1 fbuild build -e esp32s3 examples/Blink cp .fbuild/build/esp32s3/release/firmware.bin /tmp/blink-preserve.bin FBUILD_STRIP_EH_FRAME=1 fbuild build -e esp32s3 examples/Blink cp .fbuild/build/esp32s3/release/firmware.bin /tmp/blink-strip.bin ls -la /tmp/blink-{preserve,strip}.bin # Expected: preserve ~657 KB, strip <= 510 KB (delta >= 150 KB). xtensa-esp32s3-elf-readelf --debug-dump=frames \ .fbuild/build/esp32s3/release/firmware.elf | grep -c "FDE cie" # Expected: preserve ~6500 FDEs, strip near zero from project TUs.The integration test (
crates/fbuild-build/tests/eh_frame_strip_esp32.rs,#[ignore]-marked) automates this: builds twice, assertsfirmware.bindelta ≥ 150 KB and.eh_framesection delta ≥ 50 KB via theobjectcrate.Test plan
uv run soldr cargo fmt --all -- --check(clean)uv run soldr cargo check --workspace --all-targets(clean)uv run soldr cargo clippy --workspace --all-targets -- -D warnings(clean, only pre-existing MSRV-mismatch warning)uv run soldr cargo test -p fbuild-build --lib→ 534 passed (+10 platform tests from this PR)uv run soldr cargo test -p fbuild-config→ 120 passed (+14 sdkconfig tests from this PR)uv run soldr cargo test -p fbuild-build eh_frame_policy→ 16 / 16 passeduv run soldr cargo build --tests→ all test crates compile, including the#[ignore]-marked integration test--ignoredon a host with the ESP32 toolchain to confirm the ≥ 150 KB savings claim end-to-end. Deferred to post-merge / a follow-up CI gate.TDD workflow used
Wave 1 (in parallel via subagents): two pure modules (
eh_frame_policy+sdkconfig) with full unit-test matrices. Wave 1 verified clean before Wave 2 started.Wave 2 (single subagent): platform wiring + integration test. Tests assert
STRIP_FLAGSpresence underEhFramePolicy::Stripand absence under defaultPreservefor every compiler. All 5 GCC compilers + 8 orchestrators wired uniformly.Out of scope
FL_NO_UNWIND_BEGIN/_ENDpragma macros. Harmless to leave in place.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Tests