Skip to content

ci(no_std): matrix over 8 embedded targets, add critical-section feature - #20

Merged
ddsha441981 merged 2 commits into
mainfrom
ci/embedded-target-matrix
Sep 5, 2026
Merged

ddsha441981 merged 2 commits into
mainfrom
ci/embedded-target-matrix

Conversation

@ddsha441981

Copy link
Copy Markdown
Owner

What this found

The no_std job checked exactly one target, thumbv7m-none-eabi. Extending it to one job per atomic capability class — the only axis that can actually break a bare-metal build here — turned up two targets where the crate did not compile at all:

Target Chips Before After
thumbv6m-none-eabi Cortex-M0 / M0+ — both RP2040 cores ❌ ✅ with critical-section
riscv32imc-unknown-none-elf ESP32-C3 ❌ ✅ with critical-section

Both failed identically:

error[E0432]: unresolved import `portable_atomic::AtomicU64`
  --> src/engine/meta.rs:14:23
   |
14 | use portable_atomic::{AtomicU64, Ordering};
   |                       ^^^^^^^^^ no `AtomicU64` in the root

MetaWord is an AtomicU64. ARMv6-M has no LDREX/STREX, and RISC-V without the A extension has no atomic instructions at all — so portable-atomic's fallback spinlock has no CAS to build itself out of, and doesn't define AtomicU64. The crate's no_std support was real, just narrower than advertised, and CI had no way to notice.

The fix

One new opt-in feature, off by default:

critical-section = ["portable-atomic/critical-section"]

portable-atomic then performs the CAS with interrupts masked. Default-off is deliberate — it is only sound on single-core targets, and the impl belongs to the binary rather than the library: cortex-m with critical-section-single-core, or esp-hal on the ESP32-C3.

Coverage now

fail-fast: false, so one unsupported target can't mask the other seven.

Target Chips Needs
thumbv7m-none-eabi Cortex-M3 —
thumbv7em-none-eabihf Cortex-M4F / M7F (STM32F4, F7) —
thumbv8m.main-none-eabi Cortex-M33 —
thumbv6m-none-eabi Cortex-M0 / M0+ (RP2040) critical-section
riscv32imac-unknown-none-elf RISC-V with the A extension —
riscv32imc-unknown-none-elf ESP32-C3 critical-section
aarch64-unknown-none 64-bit bare metal —
wasm32-unknown-unknown WASM —

All 8 verified locally before this PR; the failures above are reproducible by reverting the Cargo.toml feature and dropping --features critical-section.

Scope

These are compile checks. Nothing was executed on real silicon or under QEMU, and cargo check does not link — a downstream thumbv6m / riscv32imc binary still has to supply the critical-section impl or it fails at link time. src/ is unchanged; the only non-CI change is the feature line in Cargo.toml.

The second commit documents all of this in the README (When std Isn't Available), together with the measured no_std comparison against lru.

The no_std job checked one target, thumbv7m-none-eabi. Extending it per
atomic capability class found two targets that did not compile at all:

  thumbv6m-none-eabi           Cortex-M0 / M0+, both RP2040 cores
  riscv32imc-unknown-none-elf  ESP32-C3

Both fail with `unresolved import portable_atomic::AtomicU64`. ARMv6-M has
no LDREX/STREX and RISC-V without the A extension has no atomics at all, so
portable-atomic's `fallback` spinlock has no CAS to build itself out of and
never defines AtomicU64. MetaWord is an AtomicU64, so the crate simply does
not build there.

New opt-in `critical-section` feature forwards to
portable-atomic/critical-section, which does the CAS with interrupts masked.
Both targets check clean with it. Off by default: sound only on single-core
targets, and the impl belongs to the binary (cortex-m's
critical-section-single-core, or esp-hal).

Also newly covered, clean with no extra feature: thumbv7em-none-eabihf,
thumbv8m.main-none-eabi, riscv32imac-unknown-none-elf, aarch64-unknown-none,
wasm32-unknown-unknown.

These are compile checks. Nothing runs on silicon or under QEMU, and
cargo check does not link, so a downstream thumbv6m/riscv32imc binary still
has to supply the critical-section impl.
Adds a "When `std` Isn't Available" section: QuickCache and Moka both need
std, so the no_std field is PulseMap vs lru, and there PulseMap is 40.0B vs
83.1B per resident entry (measured as RSS delta, x86_64) and keeps 1000/1000
hot keys through a 200K-key scan where lru keeps 0/1000.

Includes the verified-target table from the new CI matrix, which targets need
the critical-section feature and why, the inline-mode window (6-byte key /
7-byte value, a u64 key misses it and costs 113.2B), and the 4-slots-no-
chaining caveat: inserting exactly `capacity` distinct keys leaves ~81%
resident, so size for 1.3-1.5x.

Feature Flags table gains the critical-section row.
@ddsha441981
ddsha441981 merged commit 22c37a5 into main Sep 5, 2026
21 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