Skip to content

Add an unbounded SPSC queue to substitute for a queue proven SPSC - #6

Closed
ZweZeya wants to merge 1 commit into
feat/dialect-verificationfrom
feat/spsc-runtime
Closed

ZweZeya wants to merge 1 commit into
feat/dialect-verificationfrom
feat/spsc-runtime

Conversation

@ZweZeya

@ZweZeya ZweZeya commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #5 (feat/dialect-verification).

What this changes

Adds runtime/spsc_queue.hpp, the substitute for a wtc::queue<T> proven SPSC. It is unbounded, like the queue it replaces, so the substitution is exact: a bounded ring would have to drop an item or wait when full, and waiting deadlocks a program where one thread both produces and consumes, which an SPSC verdict allows. Blocks of fixed size are linked; the consumer frees a block only after draining it and acquiring its successor, and publishing the successor is the producer's last access to it. Signatures match wtc::queue<T>'s; its annotations are its own (wtc_impl_spsc_*), so lifting never mistakes the substitute for the shim.

CI's shim job now also compiles the runtime headers standalone and runs runtime/test/spsc_stress.cpp under ThreadSanitizer.

How it was checked

The stress test checks order, count and sum at block sizes 1, 4 and 1024, and that items left at destruction are destroyed exactly once. It passes:

  • under ThreadSanitizer in this PR's CI job ("Stress the runtime queues under ThreadSanitizer"), with no race reported;
  • unsanitized with Apple clang on arm64. Sanitizers are broken in that toolchain (an empty program built with -fsanitize=thread segfaults), so CI is the only TSan run.

Checklist

  • CI is green
  • New behaviour has a test; a fixed bug has a test that failed before
  • No file moves or renames mixed in with substantive changes
  • The pinned LLVM revision is untouched, or this PR does nothing else

🤖 Generated with Claude Code

wtc::queue<T> is unbounded and its push never fails, so a substitute has
to be too. A fixed ring must either drop an item when full or wait for the
consumer, and waiting deadlocks a program in which one thread is both the
producer and the consumer, which an SPSC verdict allows.

This one is a linked list of fixed-size blocks. Each block counts the
slots the producer has published, with release; the consumer frees a
block only after draining it and acquiring its successor, and publishing
the successor is the producer's last access to the block.

Its push and try_pop have the same signatures as wtc::queue<T>'s, so the
lowering does not change shape. Its annotations are its own, so lifting
never mistakes calls to the substitute for calls to the shim.

The stress test checks order, count and sum at block sizes 1, 4 and 1024,
and that items left at destruction are destroyed exactly once. CI builds
the runtime headers standalone and runs the test under ThreadSanitizer.

Verified: the stress test passes, unsanitized, with Apple clang on arm64.
Not yet verified under TSan: sanitizers are broken in that toolchain (an
empty program built with -fsanitize=thread segfaults), so the Linux CI job
is the first TSan run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ZweZeya
ZweZeya force-pushed the feat/dialect-verification branch from 99d62be to ad2187d Compare October 18, 2026 05:20
@apaznikov

Copy link
Copy Markdown
Collaborator

Closing as superseded by #10, which already had an unbounded block ring (runtime/spsc_ring.hpp) for the same substitution. Two ideas from this PR are taken there: elements now live in raw storage, so the element type needs no default constructor and a block constructs nothing per slot (runtime: Hold elements in raw storage and lend the last one popped), and CI now runs the runtime's tests under ThreadSanitizer and with its contract checks, including the test that two producers are caught (ci: Exercise the runtime under ThreadSanitizer and its checks). The try_pop_optional path you measured by hand is what that branch makes automatic: a pop that only borrows its element, chosen by the compiler when it can prove the consumer keeps nothing.

@apaznikov apaznikov closed this Oct 19, 2026
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