Skip to content

phase 16: no-alloc CI gate#94

Draft
JustinKovacich wants to merge 2 commits into
feature/phase15_bare_metal_examplefrom
feature/phase16_no_alloc_ci
Draft

phase 16: no-alloc CI gate#94
JustinKovacich wants to merge 2 commits into
feature/phase15_bare_metal_examplefrom
feature/phase16_no_alloc_ci

Conversation

@JustinKovacich
Copy link
Copy Markdown
Contributor

@JustinKovacich JustinKovacich commented Apr 28, 2026

— StaticE2EHandle, AtomicInterfaceHandle panic-allocator witness

Add two no-alloc handle types in src/transport.rs (bare_metal feature):

  • StaticE2EHandle: wraps &'static embassy-sync critical-section Mutex<RefCell>; implements E2ERegistryHandle without any heap allocation on the hot path.
  • AtomicInterfaceHandle: wraps &'static AtomicU32; encodes IPv4 as u32; implements InterfaceHandle with single atomic load/store.

Both types are Clone+Copy via thin-pointer semantics and satisfy Clone+Send+Sync+'static without Arc or RwLock.

Add tests/no_alloc_witness.rs (harness=false) with a PanicAllocator #[global_allocator] that panics on any heap allocation while armed. Witnesses: AtomicInterfaceHandle get/set, StaticE2EHandle contains_key/ protect/check after construction-time register, and WitnessChannels oneshot warm claim+send — all verified alloc-free under the panic harness.

…panic-allocator witness

Add two no-alloc handle types in src/transport.rs (bare_metal feature):
- StaticE2EHandle: wraps &'static embassy-sync critical-section Mutex<RefCell<E2ERegistry>>;
  implements E2ERegistryHandle without any heap allocation on the hot path.
- AtomicInterfaceHandle: wraps &'static AtomicU32; encodes IPv4 as u32;
  implements InterfaceHandle with single atomic load/store.

Both types are Clone+Copy via thin-pointer semantics and satisfy
Clone+Send+Sync+'static without Arc or RwLock.

Add tests/no_alloc_witness.rs (harness=false) with a PanicAllocator
#[global_allocator] that panics on any heap allocation while armed.
Witnesses: AtomicInterfaceHandle get/set, StaticE2EHandle contains_key/
protect/check after construction-time register, and WitnessChannels oneshot
warm claim+send — all verified alloc-free under the panic harness.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@JustinKovacich JustinKovacich requested a review from Copilot April 28, 2026 14:42
@JustinKovacich JustinKovacich changed the title phase 16: no-alloc CI gate — StaticE2EHandle, AtomicInterfaceHandle, … phase 16: no-alloc CI gate Apr 28, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

drop unsafe Send/Sync, abort-on-alloc instead of panic

CI:
- Add explicit `cargo test --test no_alloc_witness` step in ci.yml so the
  no-alloc gate is visible in CI logs (independent of nextest's handling
  of harness=false binaries).

src/transport.rs:
- Remove the redundant `unsafe impl Send`/`unsafe impl Sync` blocks on
  StaticE2EHandle and AtomicInterfaceHandle. Auto-traits derive correctly
  through `&'static T` since BlockingMutex<CSRawMutex, RefCell<E2ERegistry>>
  is Sync and AtomicU32 is Sync.
- Document why AtomicInterfaceHandle uses Ordering::Relaxed (single
  synchronized datum, no happens-before to establish).
- Add a "No-allocator targets" note pointing at StaticCell::init for the
  eventual const-friendly E2ERegistry::new path.

tests/no_alloc_witness.rs:
- Replace `panic!` in PanicAllocator with `diagnose_and_abort`, which
  disarms first then aborts via process::abort() — keeps the diagnostic
  off the panic-unwind path (whose machinery also allocates).
- Add witness_static_channels_first_claim using a fresh `(u64, 4)` oneshot
  variant — proves first-claim seeds the free-list alloc-free, the case
  that runs once at boot on a real bare-metal target.
- Add witness_static_channels_oneshot_recv — polls the recv future once
  via Waker::noop so the channel's poll path is measured without an
  executor.
- Add Profile5 protect/check round-trip witness (data_length matched to
  payload length to avoid the tracing::warn! mismatch path).
- Rewrite the `harness = false` comment to explain the actual reason
  (libtest TLS, worker pool, per-test bookkeeping all allocate before
  main() runs).
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/transport.rs
/// `static`-cell pattern (e.g. `static_cell::StaticCell::init`) once the
/// registry constructor becomes `const`-friendly. The handle layer itself
/// never allocates — only the one-time storage materialization does.
#[cfg(feature = "bare_metal")]
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

bare_metal_handle_impls depends on crate::e2e::E2ERegistry, but E2ERegistry is only exported when feature = "std" (see src/e2e/mod.rs). With --no-default-features --features bare_metal, this module will fail to compile even if these handles aren’t used. Consider gating this module (and its re-exports) behind cfg(all(feature = "bare_metal", feature = "std")), or redesigning the storage alias/handle to not require the std-only E2ERegistry type.

Suggested change
#[cfg(feature = "bare_metal")]
#[cfg(all(feature = "bare_metal", feature = "std"))]

Copilot uses AI. Check for mistakes.
Comment thread src/lib.rs
OneshotCancelled, OneshotRecv, OneshotSend, ReceivedDatagram, SocketOptions, Spawner, Timer,
TransportError, TransportFactory, TransportSocket, UnboundedRecv, UnboundedSend,
};
#[cfg(feature = "bare_metal")]
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

These re-exports are gated only on feature = "bare_metal", but the newly added handle implementations currently rely on std-only E2ERegistry. If the implementation is updated to cfg(all(feature = "bare_metal", feature = "std")) (or otherwise made std-free), this pub use should match to avoid build failures under --no-default-features --features bare_metal.

Suggested change
#[cfg(feature = "bare_metal")]
#[cfg(all(feature = "bare_metal", feature = "std"))]

Copilot uses AI. Check for mistakes.
Comment thread src/transport.rs
Comment on lines +895 to +897
/// # Memory ordering
///
/// Both `get` and `set` use [`Ordering::Relaxed`]. The address is the
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

unsafe impl Send/Sync here looks unnecessary and weakens the compiler’s thread-safety checks. AtomicInterfaceHandle contains only a &'static AtomicU32, which is already Send + Sync, so the auto-traits should apply without unsafe. Prefer removing these unsafe impls (or, if you keep them, add a stronger safety justification than “&'static … is already Send + Sync”).

Copilot uses AI. Check for mistakes.
Comment thread src/transport.rs
Comment on lines +837 to +840

impl StaticE2EHandle {
/// Wraps a static reference to the backing mutex.
pub const fn new(storage: &'static StaticE2EStorage) -> Self {
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

unsafe impl Send/Sync is a significant safety escape hatch. It would be safer to rely on auto-traits (if StaticE2EStorage is actually Send + Sync) rather than forcing Send/Sync manually. If the underlying embassy_sync::blocking_mutex::Mutex<..., RefCell<_>> is intentionally not Send/Sync on some targets, these unsafe impls could be unsound; consider removing them or switching to a backing type that is explicitly Send + Sync.

Copilot uses AI. Check for mistakes.
Comment thread tests/no_alloc_witness.rs
Comment on lines +113 to +118
// SAFETY: forwarding to System; invariants upheld by caller.
unsafe { System.realloc(ptr, layout, new_size) }
}
}

#[global_allocator]
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

assert_no_alloc leaves the allocator armed if f() panics (including the expected “allocation forbidden” panic). That can trigger cascading panics during unwinding/panic-hook printing and make failures harder to interpret. Consider disarming via an RAII guard (Drop) or wrapping f in std::panic::catch_unwind to ensure ARMED is always reset before the panic propagates.

Copilot uses AI. Check for mistakes.
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.

2 participants