Skip to content

Fix SeqLock cacheline layout and CACHELINE_BYTES on aarch64 (tyndall #18) - #1

Merged
johannesschrimpf merged 2 commits into
mainfrom
js/seqlock-cacheline-layout
Sep 4, 2026
Merged

johannesschrimpf merged 2 commits into
mainfrom
js/seqlock-cacheline-layout

Conversation

@johannesschrimpf

@johannesschrimpf johannesschrimpf commented Sep 3, 2026 •

Copy link
Copy Markdown
Collaborator

The port of tyndall#18 ("Fix seq_lock cacheline padding and CACHELINE_BYTES on aarch64", issue #16) to the Rust seqlock, so a Rust record and a C++ record are the same bytes again once the drone image's tyndall is bumped past c7fadae.

Changes

  • CACHELINE_BYTES: 64 on aarch64 (Jetson Orin, Cortex-A78AE — verified on target in the tyndall issue), as on x86_64; armv7 (i.MX, Cortex-A9) keeps 32. Other architectures are a compile_error! instead of a silent guess.
  • entry on its own cacheline: tyndall's alignas(CACHELINE_BYTES) STORAGE entry becomes a cacheline-aligned wrapper around the entry, so it sits at offset CACHELINE_BYTES on every ABI regardless of the storage type's alignment. The old [u8; CACHELINE - 4 - size_of::<usize>()] padding assumed seq and size pack back to back; on 64-bit targets the compiler inserts 4 bytes between them, and entry landed at 36/40 (aarch64) or 68/72 (x86_64).
  • Pinned at compile time: const assertions over 1-, 4- and 8-byte-aligned storage types (offset_of!(entry) == CACHELINE_BYTES, struct alignment, sizeof, the header offsets), plus a post-monomorphisation guard against an over-aligned storage type (tyndall's static_assert). cargo check for a target fails before a mismatching record can be written. The runtime layout test of tyndall's tests/ipc/seq_lock_layout.cpp is ported too, over the same storage types.
  • 0.3.0: breaking on the wire for aarch64 and x86_64 (armv7 is unchanged). A 0.2.x writer and a post-#18 C++ reader disagree, and vice versa, so this must ship together with the tyndall bump in meta-blueye. libblunux (which vendors this crate as a submodule for be-node-drone, libblunux#393) bumps its pointer in the same step.
ABI 0.2.x entry offset 0.3.0 CACHELINE_BYTES
armv7 (i.MX) 32 32 32
aarch64 (Orin) 36/40 64 64
x86_64 68/72 64 64

Verification

  • cargo test -p ipc_lib (aarch64 macOS, and x86_64 under Rosetta): the layout test over char/float/vec3/mixed/512-byte storage and a write/read round trip.

  • cargo check for armv7-unknown-linux-gnueabihf, x86_64-apple-darwin and aarch64-unknown-linux-gnu: the const layout assertions hold on all three ABIs. A scratch program over a 128-byte-aligned storage type fails to build on IPC::new alone (the guard), a normal one builds.

  • C++ ↔ Rust interop in Docker against tyndall origin/main (c83dab3), both directions, five storage types (float, double x,y,z, a mixed long/double/char/unsigned long struct, bool, a 512-byte array), run by verification agents on three containers:

    ABI C++ offsetof(entry) / CACHELINE_BYTES sizeof(seq_lock) small / 512-byte Rust segment size C++ writes → Rust reads Rust writes → C++ reads
    armv7 (linux/arm/v7) 32 / 32 64 / 544 64 / 544 all values match all values match
    aarch64 (linux/arm64) 64 / 64 128 / 576 128 / 576 all values match all values match
    x86_64 (linux/amd64) 64 / 64 128 / 576 128 / 576 all values match all values match

    C++-created segments are 4 bytes larger (its type-hash tailer), which C++ also adds when it opens a Rust-created segment; the Rust side ignores it. Shared-memory names agree on both sides. A second read of an unchanged record reports EAGAIN/EAgain on both sides.

  • Adversarial review (one reviewer, three refuters per finding): two findings confirmed and fixed in the second commit (the alignment guard was not evaluated by IPC::new; write() did not store size), three refuted.

Not in this PR

One other difference from tyndall's seq_lock that a review of the libblunux node surfaced, for a follow-up: the writer's first seq increment is Release where tyndall uses a store-store barrier (smp_wmb) — on ARM the entry stores may become visible before the odd sequence number; the fix is AcqRel there and an Acquire fence between the entry read and the second seq load in read(). Also not here: the C++ tailer (type hash) is neither written nor checked by the Rust side, as before.

🤖 Generated with Claude Code

…#18)

The port of BluEye-Robotics/tyndall#18 ("Fix seq_lock cacheline padding
and CACHELINE_BYTES on aarch64", issue #16), so a Rust record and a C++
record are the same bytes again once tyndall is bumped past c7fadae:

- CACHELINE_BYTES is 64 on aarch64 (Jetson Orin, Cortex-A78AE; verified
  on target in the tyndall issue), as on x86_64; armv7 (i.MX) keeps 32.
- `entry` is forced onto its own cacheline through a cacheline-aligned
  wrapper (tyndall's `alignas(CACHELINE_BYTES) STORAGE entry`), so it sits
  at offset CACHELINE_BYTES on every ABI regardless of the storage type's
  alignment. The old `[u8; CACHELINE - 4 - size_of::<usize>()]` padding
  assumed `seq` and `size` pack back to back; on 64-bit targets they do
  not, and `entry` landed at 36/40 (aarch64) or 68/72 (x86_64).
- The layout is pinned at compile time (`const` assertions over 1-, 4- and
  8-byte-aligned storage, and a post-monomorphisation guard against an
  over-aligned storage type, tyndall's static_assert), plus the runtime
  layout test of tyndall's tests/ipc/seq_lock_layout.cpp in Rust.

Breaking on the wire for aarch64 and x86_64 (armv7 is unchanged), hence
0.3.0: a 0.2.x writer and a post-#18 C++ reader disagree, and vice versa,
so this ships together with the tyndall bump on the drone image.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@johannesschrimpf
johannesschrimpf marked this pull request as ready for review September 3, 2026 21:00
…riter

Two review findings on the layout port:

- The storage-alignment guard (tyndall's static_assert) was referenced
  only from write and read, so a program that merely opened a record over
  an over-aligned storage type built fine and created a segment no C++
  instantiation can have. The segment size now comes from
  SeqLock::segment_size, which evaluates the guard, so opening trips it
  too; the module doc no longer claims `cargo check` catches it (a generic
  guard fires at build time, only the concrete layout assertions at check
  time).
- write() now stores sizeof(STORAGE) in the header's `size` field, as
  tyndall's writer has since Feb 2025; its ipc_read tool reads it, no
  reader depends on it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Existing shared-memory segments need an explicit migration or rejection mechanism to prevent incompatible reads after upgrades.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Ports Tyndall #18’s corrected SeqLock ABI to Rust.

Changes:

  • Corrects cacheline sizes and payload alignment.
  • Adds compile-time layout guards and runtime tests.
  • Bumps ipc_lib to 0.3.0.
File summaries
File Description
ipc_lib/src/seqlock.rs Implements aligned ABI layout and tests.
ipc_lib/src/lib.rs Uses guarded segment sizing.
ipc_lib/Cargo.toml Bumps package version.
Cargo.lock Updates locked package version.
Review details
  • Files reviewed: 3/4 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread ipc_lib/src/lib.rs
Comment on lines +109 to +111
// Through `segment_size`, not `size_of`: that is where the storage
// type's alignment guard is evaluated (see seqlock.rs).
let size = SeqLock::<T>::segment_size() as off_t;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not a concern here: we do not upgrade a live system. The tyndall change and this one ship together in an image, /dev/shm is tmpfs and is empty after the boot into that image, so no segment ever outlives the binaries that created it.

For the record, since I measured the layouts while checking this:

Target Old entry offset / size New entry offset / size
armv7 (the i.MX drones) 32 / 64 32 / 64
aarch64 (Jetson) 36 / 64 64 / 128
x86_64 (dev, CI) 68 / 128 64 / 128

Three things follow. The i.MX fleet is byte-identical before and after, so nothing changes there at all. On x86_64 the segment size does not change, only the payload offset, so a size-based migration check would never fire on the platform where the silent-mismatch risk is highest. And mapping the new size over a stale smaller object does not fault: /dev/shm is tmpfs, which allocates whole pages, so reads and writes past the recorded size still work and are visible across processes. I tested that. The only way to get wrong data is an old writer meeting a new reader, which is a mixed rollout.

The suggested remedy is also not available to us. The segment name and the header layout are the wire contract with tyndall's C++ side: IPC_SHMEM_PREFIX has to match id_rtid_prepare, and the header is the C++ struct. Versioning either one unilaterally would mean the Rust and C++ sides stop seeing each other's topics, which is the entire purpose of this crate.

Worth noting for anyone reading later: tyndall's C++ rounds each segment up to a page before truncating, while this crate truncates to the exact record size. Any segment the C++ side created is therefore already a full page and comfortably contains the new mapping, and a strict equality check on the size would reject every one of them.

@johannesschrimpf
johannesschrimpf merged commit ac9f3ec into main Sep 4, 2026
1 check passed
@johannesschrimpf
johannesschrimpf deleted the js/seqlock-cacheline-layout branch September 4, 2026 10:59
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.

3 participants