Skip to content

perf(build): extend watch-set fingerprint fast-path to AVR orchestrator#136

Merged
zackees merged 1 commit intomainfrom
perf/avr-fast-path-check
Apr 19, 2026
Merged

perf(build): extend watch-set fingerprint fast-path to AVR orchestrator#136
zackees merged 1 commit intomainfrom
perf/avr-fast-path-check

Conversation

@zackees
Copy link
Copy Markdown
Member

@zackees zackees commented Apr 19, 2026

Summary

Refs #121.

Extract the ESP32 no-op build fast-path into build_fingerprint::fast_path and wire it into the AVR orchestrator. Warm builds of tests/platform/uno drop from ~119 ms → ~75 ms wall-clock (median of 8 consecutive runs on Windows 10, daemon fully warm) because the fast path returns before source scan, compile, and link.

The ESP32 side is a behaviour-preserving refactor — same metadata / artifact / zccache / watch-set-stamp checks in the same order; ESP32's compile-db-parity requirement rides the helper via extra_artifact_ok.

Warm-build timings: fbuild build tests/platform/uno -e uno --quick

Measured via a Python harness capturing wall-clock around 10 back-to-back subprocess.run calls (median of last 8 after 2 warm-up iterations). Daemon kept running across all samples.

Build Cold (full) Warm median Warm min Warm max
Before (main AVR orchestrator) 0.9 s 119.1 ms 117.8 ms 128.4 ms
After (fast-path extracted + wired) 1.0 s 74.7 ms 70.5 ms 99.7 ms

With FBUILD_PERF_LOG=1 on the warm path post-change:

[perf-log cli-build] daemon-handshake=2 ms, server-roundtrip=58 ms, total=61 ms

The --quick build log now contains No-op fingerprint matched; reusing existing AVR artifacts. on warm rebuilds, confirming the fast path fires.

The 50 ms target in the issue bakes in essentially zero CLI + daemon-handshake overhead; the pure server-side orchestrator time is ~50–60 ms here. The remainder is CLI startup / HTTP round-trip.

What changed

  • New module crates/fbuild-build/src/build_fingerprint/fast_path.rsFastPathInputs, FastPathHit, fast_path_check, plus the shared FAST_PATH_EXTENSIONS / FAST_PATH_EXCLUDES constants and fast_path_watch helper.
  • build_fingerprint.rsbuild_fingerprint/mod.rs — file-to-directory conversion for the new submodule.
  • ESP32 orchestrator — replaces its inline ~100-line fast-path block with a single fast_path_check call. ESP32's compile_db_is_current freshness requirement is threaded through the new extra_artifact_ok hook so behaviour is preserved bit-for-bit.
  • AVR orchestrator — gains AvrFingerprintMetadata, collect_fast_path_watches, and a post-build persistence step that writes build_fingerprint.json + marks zccache watches.
  • 6 new unit tests in fast_path::tests — cache hit, missing fingerprint, changed source file, mismatched metadata hash, missing artifact, and the extra_artifact_ok escape hatch.

Follow-up orchestrators

With the helper in place, wiring the fast path into the remaining platforms should be a small patch each:

  • Teensycrates/fbuild-build/src/teensy/orchestrator.rs
  • RP2040crates/fbuild-build/src/rp2040/orchestrator.rs
  • STM32crates/fbuild-build/src/stm32/orchestrator.rs

Each follows the AVR template: define a platform-specific FingerprintMetadata, collect project + resolved-lib watches, gate the fast_path_check on !compiledb_only && !symbol_analysis, and persist the fingerprint post-build. Scoped out of this PR per the issue.

Test plan

  • uv run cargo clippy --workspace --all-targets -- -D warnings — clean
  • uv run cargo test -p fbuild-build — 460 unit + 2 integration tests pass (including 6 new fast_path::tests)
  • Warm fbuild build tests/platform/uno -e uno --quick emits "No-op fingerprint matched" and returns in ~75 ms
  • Warm fbuild build tests/platform/esp32dev -e esp32dev --target compiledb succeeds — ESP32 refactor didn't break its compile-db code path
  • Full ESP32 cold build validated on CI (local cold build exceeds reasonable iteration time)

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • Refactor

    • Enhanced build system efficiency by consolidating incremental build validation logic across all platforms, enabling faster warm builds when source files remain unchanged.
  • Documentation

    • Added build fingerprint module documentation explaining caching mechanisms and optimization strategies for incremental compilation.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 19, 2026

📝 Walkthrough

Walkthrough

Extracted fast-path warm-build logic into a shared build_fingerprint module with fast_path_check() and fast_path_watch() APIs. Refactored AVR orchestrator to use the new module, updated fingerprint metadata fields, and modified persistence conditions. ESP32 orchestrator simplified to call the shared fast-path helper. Both platforms now reuse identical warm-build detection logic.

Changes

Cohort / File(s) Summary
New Fast-Path Module
crates/fbuild-build/src/build_fingerprint/fast_path.rs, crates/fbuild-build/src/build_fingerprint/mod.rs
Added shared warm-build fast-path module with fast_path_check() and fast_path_watch() APIs. Implements configurable watch extensions/excludes, persisted fingerprint validation, artifact verification, zccache integration, and stamp-based change detection via WatchSetStampCache.
AVR Orchestrator Refactor
crates/fbuild-build/src/avr/orchestrator.rs
Replaced local fast-path logic with calls to shared build_fingerprint module. Updated AvrFingerprintMetadata fields (added board/platform info, removed direct path hashing). Modified persistence to check build_result.success and excluded flags (compiledb_only, symbol_analysis). Integrated zccache mark_fingerprint_success callback. Renamed result to build_result.
ESP32 Orchestrator Simplification
crates/fbuild-build/src/esp32/orchestrator.rs
Removed local fast-path constants and helper; delegated to shared module. Replaced inline fingerprint loading/validation with single fast_path_check() call. Introduced FastPathInputs construction with platform-specific required_artifacts and compile_db_fresh callback. Removed manual zccache/hash checks.
Module Documentation
crates/fbuild-build/src/build_fingerprint/README.md
Added module-level README explaining fast-path purpose, structure (mod.rs for core types and stamping primitives; fast_path.rs for shared warm-build check), and the FastPathInputs / FastPathHit API contract.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

The changes involve substantial refactoring across multiple orchestrators, introduce a new architectural module with intricate fingerprint/watch/zccache logic, and alter persistence/validation mechanisms. Understanding the interaction between shared fast-path helpers, metadata hashing, artifact verification, and platform-specific callbacks across both AVR and ESP32 paths requires careful review.

Possibly related issues

Poem

A rabbit hops through tangled code,
Finds paths both old and new,
Extracts the fast one, builds a road,
Two orchestras now use! 🐰✨
Where fingerprints and watches dance,
Fast-paths advance, advance!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately describes the main change: extending the watch-set fingerprint fast-path mechanism to the AVR orchestrator, which is the core objective of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/avr-fast-path-check

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Extract the ESP32 no-op build fast-path check into a shared
`build_fingerprint::fast_path` module and wire it into the AVR
orchestrator. Warm `fbuild build tests/platform/uno -e uno --quick`
drops from ~119 ms to ~75 ms (median of 8 runs) because the fast
path returns before source scan, compile, and link.

Refs #121.

ESP32 side is a behaviour-preserving refactor:
- `fast_path_check()` performs the same metadata / artifact /
  zccache / watch-set-stamp checks in the same order.
- ESP32-specific compile-db-parity check rides the helper via a
  closure (`extra_artifact_ok`), so nothing about ESP32 fast-path
  semantics changes.
- `FAST_PATH_EXTENSIONS` / `FAST_PATH_EXCLUDES` move into
  `build_fingerprint::fast_path` so both orchestrators share a
  single source of truth.

AVR side adds the fast path end-to-end:
- `AvrFingerprintMetadata` captures every field `AvrCompiler` /
  `AvrLinker` reads off `BoardConfig` (mcu, f_cpu, extra_flags,
  upload_protocol, etc.) plus toolchain / framework install paths.
- Fast-path check lives after `ensure_installed` so the hashed
  paths reflect the real on-disk location.
- Post-build, the orchestrator persists
  `build_fingerprint.json` and marks each watch in zccache so
  subsequent warm builds hit the helper.

Follow-up PRs will extend this to Teensy / RP2040 / STM32 —
they're the obvious next candidates now that the helper is in
place.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zackees zackees force-pushed the perf/avr-fast-path-check branch from 761fd42 to 5666674 Compare April 19, 2026 06:27
@zackees
Copy link
Copy Markdown
Member Author

zackees commented Apr 19, 2026

Rebased onto main to resolve conflicts with #128 (perf(build): add fast-path fingerprint check to AVR orchestrator), which landed on main with an inline AVR fast-path while this PR was open.

All 6 conflict regions in crates/fbuild-build/src/avr/orchestrator.rs were in the fast-path wiring (imports, AvrFingerprintMetadata, fingerprint_watches, the fast-path check block, the build-result variable name, and the post-build persist block). Resolved by keeping this branch's shared-helper approach — crate::build_fingerprint::fast_path_check(&FastPathInputs { .. }) plus the zccache mark_fingerprint_success wiring — which is exactly the refactor this PR is meant to deliver. Main's inline implementation from #128 is superseded by the shared helper.

No overlap with #134's P0 containment fix (that change lives in fbuild-core/containment.rs, not AVR orchestrator).

Verified locally:

  • cargo fmt --all --check clean
  • cargo clippy --workspace --all-targets -- -D warnings clean
  • cargo test -p fbuild-build — 460 lib tests + integration tests pass, including the 6 new build_fingerprint::fast_path::tests and all 19 AVR tests
  • Smoke: fbuild build tests/platform/uno -e uno --quick — cold build 0.9s, warm rebuild 0.1s with No-op fingerprint matched; reusing existing AVR artifacts.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/fbuild-build/src/esp32/orchestrator.rs (1)

287-291: ⚠️ Potential issue | 🟡 Minor

Recompute the watch set before persisting the fingerprint.

fingerprint_watches is collected before dependency libraries can materialize build_dir/libs, but the same early list is later saved and marked in zccache. On the first build where libs/ is created, the persisted hash can omit dep_libs, causing the next warm build to miss unnecessarily.

♻️ Proposed adjustment
         // 15. Size reporting + result assembly
+        let fingerprint_watches = collect_fast_path_watches(build_dir, &params.project_dir);
         let persisted_fingerprint = PersistedBuildFingerprint {
             version: BUILD_FINGERPRINT_VERSION,
             metadata_hash: metadata_hash.clone(),
             file_set_hash: match hash_watch_set_stamps_cached(
                 &fingerprint_watches,

Also applies to: 1209-1234

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/fbuild-build/src/esp32/orchestrator.rs` around lines 287 - 291,
fingerprint_watches is being collected too early (before dependency libraries
materialize in build_dir/libs) which lets the persisted fingerprint omit
dep_libs; to fix, move/recompute the call to collect_fast_path_watches so it
runs immediately before you persist the fingerprint and mark it in zccache
(i.e., after the step that materializes dependency libs), replacing the earlier
use of the precomputed fingerprint_watches from build_fingerprint_path; update
references around build_fingerprint_path, collect_fast_path_watches, and the
zccache persist/mark code so the saved fingerprint uses the recomputed watch
set.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/fbuild-build/src/avr/orchestrator.rs`:
- Around line 177-180: The initial fingerprint_watches are collected too early
(before run_sequential_build_with_libs may create build_dir/libs) causing stale
watches; fix by cloning ctx.build_dir before moving ctx into the pipeline and
then recomputing fingerprint_watches via collect_fast_path_watches (re-run the
perf.phase("fp-watches-collect") block) after the build completes, and pass that
refreshed watch set into hash_watch_set_stamps_cached and
mark_fingerprint_success (also apply the same change in the other occurrence
around lines 324-352 where watches are used).

In `@crates/fbuild-build/src/build_fingerprint/fast_path.rs`:
- Around line 41-44: The FAST_PATH_EXTENSIONS constant in fast_path.rs is
missing "ini", so PlatformIO config changes aren't tracked; update the
FAST_PATH_EXTENSIONS slice (symbol: FAST_PATH_EXTENSIONS) to include "ini" among
the extensions so .ini files are treated as inputs and cause the fast-path to
invalidate when they change.

---

Outside diff comments:
In `@crates/fbuild-build/src/esp32/orchestrator.rs`:
- Around line 287-291: fingerprint_watches is being collected too early (before
dependency libraries materialize in build_dir/libs) which lets the persisted
fingerprint omit dep_libs; to fix, move/recompute the call to
collect_fast_path_watches so it runs immediately before you persist the
fingerprint and mark it in zccache (i.e., after the step that materializes
dependency libs), replacing the earlier use of the precomputed
fingerprint_watches from build_fingerprint_path; update references around
build_fingerprint_path, collect_fast_path_watches, and the zccache persist/mark
code so the saved fingerprint uses the recomputed watch set.
🪄 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: ff6494b3-f9e9-4e4a-99f1-81ac64f56229

📥 Commits

Reviewing files that changed from the base of the PR and between b38c003 and 5666674.

📒 Files selected for processing (5)
  • crates/fbuild-build/src/avr/orchestrator.rs
  • crates/fbuild-build/src/build_fingerprint/README.md
  • crates/fbuild-build/src/build_fingerprint/fast_path.rs
  • crates/fbuild-build/src/build_fingerprint/mod.rs
  • crates/fbuild-build/src/esp32/orchestrator.rs

Comment on lines +177 to +180
let fingerprint_watches = {
let _g = perf.phase("fp-watches-collect");
collect_fast_path_watches(build_dir, &params.project_dir)
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Refresh watches after the build before saving the AVR fingerprint.

build_dir/libs may not exist when fingerprint_watches is first collected, but it can be created by run_sequential_build_with_libs. Persisting the early watch set means the first warm build after dependency resolution can miss because it now sees an extra dep_libs watch.

If you recompute here after ctx is moved into the pipeline, clone ctx.build_dir before the move and use the refreshed watch set for both hash_watch_set_stamps_cached and mark_fingerprint_success.

Also applies to: 324-352

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/fbuild-build/src/avr/orchestrator.rs` around lines 177 - 180, The
initial fingerprint_watches are collected too early (before
run_sequential_build_with_libs may create build_dir/libs) causing stale watches;
fix by cloning ctx.build_dir before moving ctx into the pipeline and then
recomputing fingerprint_watches via collect_fast_path_watches (re-run the
perf.phase("fp-watches-collect") block) after the build completes, and pass that
refreshed watch set into hash_watch_set_stamps_cached and
mark_fingerprint_success (also apply the same change in the other occurrence
around lines 324-352 where watches are used).

Comment on lines +41 to +44
pub const FAST_PATH_EXTENSIONS: &[&str] = &[
"a", "bin", "c", "cc", "cpp", "csv", "elf", "h", "hh", "hpp", "ino", "json", "ld", "lds", "py",
"s", "S", "txt",
];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Include platformio.ini in the watched project inputs.

The shared project watch ignores .ini files, but PlatformIO env changes like build_flags, lib_deps, and filters can affect the produced firmware without being fully represented in the metadata hash. Add ini so config-only changes invalidate the fast path instead of reusing stale artifacts.

🐛 Proposed fix
 pub const FAST_PATH_EXTENSIONS: &[&str] = &[
-    "a", "bin", "c", "cc", "cpp", "csv", "elf", "h", "hh", "hpp", "ino", "json", "ld", "lds", "py",
-    "s", "S", "txt",
+    "a", "bin", "c", "cc", "cpp", "csv", "elf", "h", "hh", "hpp", "ini", "ino", "json", "ld",
+    "lds", "py", "s", "S", "txt",
 ];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub const FAST_PATH_EXTENSIONS: &[&str] = &[
"a", "bin", "c", "cc", "cpp", "csv", "elf", "h", "hh", "hpp", "ino", "json", "ld", "lds", "py",
"s", "S", "txt",
];
pub const FAST_PATH_EXTENSIONS: &[&str] = &[
"a", "bin", "c", "cc", "cpp", "csv", "elf", "h", "hh", "hpp", "ini", "ino", "json", "ld",
"lds", "py", "s", "S", "txt",
];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/fbuild-build/src/build_fingerprint/fast_path.rs` around lines 41 - 44,
The FAST_PATH_EXTENSIONS constant in fast_path.rs is missing "ini", so
PlatformIO config changes aren't tracked; update the FAST_PATH_EXTENSIONS slice
(symbol: FAST_PATH_EXTENSIONS) to include "ini" among the extensions so .ini
files are treated as inputs and cause the fast-path to invalidate when they
change.

@zackees zackees merged commit f8533d3 into main Apr 19, 2026
67 of 76 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant