Repository navigation
Conversation
4 tasks done
--select-queue-impl, from 253a2bc (feat/select-queue-impl), ported to the pin and to the analysis now being sound. For a cir.global whose verdict is SPSC it retargets the storage, the construction, the push and pop ops, and the destructor registration to the module's wtc::runtime::spsc_queue<T> for the same T. Changed on the way, each for a reason found at the pin: - The destructor is retargeted through this global's own __cxa_atexit registration. The earlier version rewrote every reference to the original destructor, and two globals of one type share one, so substituting @q1 also registered spsc_queue's destructor for @q2, still a wtc::queue. - Every use of the global is checked first. Its type changes, so a use the pass does not retarget -- the address passed elsewhere, an op that can also reach a non-SPSC queue -- would read the new storage as the old type. Such a global keeps its implementation, and a remark names the use. - The substitute is found by its wtc_impl_spsc_* annotations, not its record name, and its arguments are read from the signature rather than the entry block, which a declaration does not have. - Alignment comes from cir.record_layouts: the data layout of a CIR record type does not see alignas, and spsc_queue aligns to 64. - Pops that write through a result slot are only substituted when the substitute's try_pop takes one too; lowering restores sret. - Anchors (wtc_anchor) are erased afterwards, with their @llvm.used entries. Diagnostics are MLIR remarks, not stdout. Verified: 28 of 28 pass. On the prototype's example (q1 SPSC, q2 MPMC, one type) q1 is substituted and q2 is not, its destructor untouched; the result compiles to a native binary that runs to completion, and for x86-64 to LLVM IR whose substituted try_pop call carries sret and the new size. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A program had to instantiate wtc::spsc_queue<T, N> itself for the pass to find it, so a human still named the specialisation the compiler was meant to choose. queue<T>'s constructor now odr-uses wtc::detail::alternatives<T>, which instantiates wtc::runtime::spsc_queue<T>. Measured at the pin: it is [[gnu::used]], putting it in @llvm.used, that makes ClangIR emit it and everything it calls, at -O0 and -O1; the address taken in the constructor leaves nothing behind. --select-queue-impl erases it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From 253a2bc, with three changes. queue_bench.cpp names no substitute, since the shim now provides one. Its consumer checks the count, the sum and the order of what it receives, and the run fails if any is wrong, because a fast wrong queue is not a result. run.sh takes the pinned toolchain from LLVM_BUILD (default /opt/llvm, as in the image), uses xcrun only on Darwin, refuses to time an "optimized" build in which nothing was substituted, and reports the median of RUNS runs. mlir-translate still needs --allow-unregistered-dialect: cir-to-llvm leaves CIR types in attributes even for plain clang output. wtc-opt no longer does. Measured on an Apple M1 Pro, median of five: baseline 2.07M items/s, substituted 3.6x that, hand-written 20.9x. The remaining gap is one shared_ptr allocation per pop, which the substitute keeps to match try_pop's interface. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ZweZeya
force-pushed
the
feat/select-queue-impl-pinned
branch
from
October 18, 2026 05:20
74c71e6 to
57ea2a0
Compare
ZweZeya
force-pushed
the
feat/spsc-runtime
branch
from
October 18, 2026 05:20
b3ee0aa to
93a5287
Compare
run.sh confirmed the optimized build was substituted by matching "cir.global external @g_queue". On Linux the global is "cir.global external dso_local @g_queue", so the check would have called a correct build unsubstituted. It now matches only the name and the type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 tasks done
Collaborator
|
Closing as superseded by #10. The substitution there rewrites the topology tag at the queue's constructor rather than retargeting the global to another type, so it applies to locals as well as globals and needs no anchor or destructor retargeting; on the pipeline benchmark the compiler's build is byte for byte the hand-tagged one. Your M1 numbers are a useful independent replication: 3.6× substituted and 20.9× by hand line up with the 3.07× and the 10 to 41× of the inline ring measured on x86 for the gate, and the gap you name, one |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #6 (
feat/spsc-runtime).What this changes
--select-queue-impl, from 253a2bc (feat/select-queue-impl), ported to the pin: for acir.globalwhose verdict is SPSC it retargets the storage, construction, push/pop and destructor registration to the module'sspsc_queue<T>. Three commits:__cxa_atexitregistration. 253a2bc rewrote every reference to the original destructor, and two globals of one type share one, so substituting@q1also registeredspsc_queue's destructor for@q2.cir.record_layouts(the record type's data layout ignoresalignas; the substitute aligns to 64).try_poptakes one too.queue<T>'s constructor odr-uses a[[gnu::used]]anchor that instantiatesspsc_queue<T>, so no program has to name it; the pass erases the anchor and its@llvm.usedentry.run.shis portable and reports the median.Only globals are substituted.
How it was checked
29 of 29 pass, no warnings (
SelectQueueImplementation/global-spsc.mlircovers the shared-destructor case;refusals.mlirthe four ways a queue is left alone). On the prototype's example (q1SPSC,q2MPMC, same type) onlyq1is substituted andq2's destructor is untouched; the result runs as a native binary, and for x86-64 lowers to LLVM IR whose substitutedtry_popcall carriessretand the new size.Measured at the pin on an Apple M1 Pro, median of five, all runs checked:
wtc::queue<int>spsc_queueby handThe remaining gap is one
shared_ptrallocation per pop, kept to matchtry_pop's interface.Checklist
🤖 Generated with Claude Code