Linting Cache Lines from Source
I spent an afternoon debugging a false sharing problem that lived in a three-field struct. I could have found it in two seconds if something had just told me the layout was wrong, so I wrote that something.
What it does
snarf computes byte offsets for repr(C) struct fields and maps them to 64-byte cache lines. If two atomic fields land on the same line, it flags it.
$ cargo snarf
src/channel.rs:14 ChannelState
cache line 0:
status AtomicU8 offset 0 (1 byte)
rx_count AtomicU64 offset 8 (8 bytes)
tx_count AtomicU64 offset 16 (8 bytes)
warning: 3 atomic fields share cache line 0
That’s all it does. It doesn’t know which threads touch which fields and it can’t prove contention exists. It tells you the layout and you decide whether it’s a problem.
repr(C) layout is deterministic — field order matches declaration order and padding follows C ABI rules. Even without repr(C), if every field has the same alignment the layout ends up being deterministic. And if the struct fits in one cache line, field order doesn’t matter anyway.
--strict warn on all shared lines, not just atomics
--format github CI annotations on PR diffs
--line-size 128 Apple M-series performance cores
--warn-only annotate without failing
In CI
The real value is in CI: someone adds a field to a shared struct and the layout gets flagged before the PR merges.
# .github/workflows/ci.yml
- name: Cache-line lint
run: cargo install cargo-snarf && cargo snarf
Exits non-zero on findings. Or annotate without blocking:
# annotations only
- name: Cache-line annotations
run: cargo install cargo-snarf && cargo snarf --format github --warn-only
Most findings won’t be actual bugs — four u64 fields on one line, but each thread gets its own instance, no contention. The warning is a prompt to check, not a verdict.
Does the heuristic work
I built a validation harness to find out. Two structs — one with co-located atomics, one cache-line-aligned — hammered by two threads pinned to separate P-cores (CPU 0 and CPU 2 on an i9-12900K), 50M fetch_add operations each. I ran snarf on the source, then ran perf c2c record on both binaries.
// snarf warns
#[repr(C)]
struct SharedCounters {
counter_a: AtomicU64,
counter_b: AtomicU64,
}
// snarf clean
#[repr(C, align(64))]
struct PaddedAtomic {
value: AtomicU64,
}
Co-located: 1,195 HITM events. Padded: zero. Harness is in validation/.
cargo install cargo-snarf && cargo snarf
github.com/cong-or/snarf — MIT or Apache-2.0.