Two Compilers, One Cadence
"The Rails-blog-as-input demo is a great forcing function and a different shape of input from the small repros that drive most of the issue tracker today, so yes, a Spinel-backed Rails blog would be welcome alongside the existing demos."
— Matz, closing matz/spinel#83, April 30th
Two Compilers, One Subset made an architectural claim: the IR Roundhouse needs internally is, structurally, the Spinel subset. Round Trip made an empirical one: a working blog from make real-blog && make spinel-dev, on CRuby. The compile half of the round trip — Spinel ingesting Roundhouse's output — was the unfinished one.
The cadence to close that gap is what's been running since. Four days, seven issues, six closed.
The cadence
What happened, in order:
| Issue | Filed | Closed | Subject |
|---|---|---|---|
| #49 | Apr 27 | Apr 28 | Default-argument values not inserted at omitting call sites |
| #83 | Apr 28 | Apr 30 | Cadence agreement; four follow-ups requested |
| #126 | Apr 30 | Apr 30 | Module assigned to module-level attr_accessor reads back as 0 |
| #127 | Apr 30 | Apr 30 | Module def self.method with non-integer return is typed as Integer |
| #130 | Apr 30 | Apr 30 | Polymorphic @ivar slot collapses to one type |
| #133 | Apr 30 | May 1 | Hash builder via each fails on heterogeneous values |
| #176 | May 1 | open | Empty-Hash default parameter silently corrupts caller-supplied Hash |
(Plus matz/spinel#131, filed and closed by Matz on Apr 30 as a carve-out from #130 — variant 3, the ternary-RHS-with-mixed-types shape — fixed separately because it needed a different fix site than the variants 1 and 2.)
Each was filed as a minimal repro — six lines of Ruby for #49, thirty for #133 — extracted from a real failure surfaced when feeding Roundhouse-emitted output to Spinel. Each fix came back with a test pinning the behavior. The "live tracking surface" Matz described is exactly what's running.
#130 turned out richer than the framing I filed it under. I'd characterized it as a synthetic-only finding — the polymorphic-@ivar surface I tested against doesn't actually appear in any code Roundhouse emits today, so I gave Matz the explicit option of closing it as "won't support." He picked instead to fix the two variants whose fix-sites were tractable and to file the third as its own issue (#131) for the path that needs ternary-RHS boxing in codegen. The triage went finer-grained than my own characterization had.
What "different shape of input" means in practice
Most of Spinel's issue tracker is small repros — eight-line programs that exercise a single inference rule. They're easy to triage and easy to fix. They under-cover one specific case: combinatorial interaction between rules that all individually work.
matz/spinel#176, filed today, is the cleanest example. Each ingredient is fine on its own:
def init(s = ""); ... # empty-String default — works
def init(arr = []); ... # empty-Array default — works
def init(h = {a: 0}); ... # non-empty Hash default — works
def init(attrs); ... # no default, Hash arg — works
Combine them — def initialize(attrs = {}) with a caller passing a Hash — and the parameter silently reads as 0. No compile error. No runtime trap. Just a wrong number where the data should have been.
That shape doesn't surface in minimal-repro hunting because it isn't a single rule. It's the intersection of "default-empty-Hash literal" and "Hash-typed parameter," and the only way to find it is to feed a program where both naturally co-occur. def initialize(attrs = {}) is the canonical Rails ActiveRecord constructor; every model in real-blog uses it; one bug accounts for twelve of the nineteen visible errors after the previous fix landed.
This is the work-shape Matz named when he said "different shape of input." Not "harder programs," not "bigger tests" — interaction surfaces that minimal repros structurally can't expose.
Both sides moving
The Spinel-side fixes are visible above. On Roundhouse's side, parallel work landed in the same window:
- Reverted
runtime/ruby/active_record/base.rbfrom adef self.adapter; @adapter; endworkaround back toclass << self; attr_accessor :adapter; endonce #126's Stage 1 fix made the latter the cleaner shape. - Added
class << selfbody recognition in the framework typing sweep so the swap survived CI. - Collapsed
ActionController::Parameters's polymorphic method signatures (merge,symbolize_keys,require,permit) to monomorphic ones — Hash in, Hash or Parameters out, but never both. Easier for Spinel; also easier for the type-strict targets (Rust, Crystal, Go) that have no good sum-type story forHash | Parameters. - Lowerer rewrite:
params.permit(:a, :b)(variadic) emits asparams.permit([:a, :b])(Array). Parameter slot becomes monomorphic at every call site.
Each of these is a small focused refactor. Each closed a class of errors in the spinel-compile output. None changes the input language Rails authors write — the Rails idiom is preserved at the lowerer's input boundary, and the cleanup happens between the Rails source and the emitter.
The pattern that's emerged: when the spinel-compile attempt surfaces a polymorphic shape Spinel doesn't handle, the question isn't "wait for Spinel to add the support" — it's "is Roundhouse's emitted shape the right one anyway?" Often the answer is no, and the fix lands on Roundhouse's side. When it is the right shape, the issue goes to Spinel. The triage runs both ways.
The Claude Code observation
Worth naming because it's visible in the public artifacts: every commit Matz authors directly on matz/spinel includes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> in its trailer. The commits to Roundhouse use the same trailer. Two projects' principal authors openly attribute to the same assistant.
This is not the thesis of the post. The thesis is that the channel works. But the cadence — same-day fixes, seven issues in four days, parallel work on both sides — is the cadence of a workflow where the principals have an effective amplifier, and they're both willing to disclose it. Reasonable readers can draw their own inferences about what cross-project iteration looks like in 2026.
What's not in the trailer is the architectural decisions, the compatibility judgment calls, the per-issue triage of "Spinel-side fix" versus "Roundhouse-side refactor." Those are still the human authors. The amplifier doesn't change which problem to solve; it changes how fast the implementation of the chosen solution lands.
The honest gap
What's still incomplete:
Real-blog still doesn't compile cleanly under Spinel. Today's count: 19 errors, capped at clang's -ferror-limit=20. The actual count past the cap is unknown. Several remaining errors are gap-#2 family ("polymorphic method args") that would benefit from one more Spinel fix and one more Roundhouse-side refactor.
Compile is not running. Even when the C compiles, the binary needs to actually link and serve HTTP requests, render views, and persist to a database. Some of those layers haven't been touched yet — the sqlite3 gem doesn't compile under Spinel (no FFI), so production deployment needs the in-memory adapter or a shell-out path that isn't built yet.
Some shapes will land as "won't support" rather than "fix." Matz committed to saying so explicitly in the issue. Stage 3 of #126 (module values flowing across function boundaries) is shelved pending a Roundhouse-shaped reproducer. So far that's the only one — #130 looked like it might join, but Matz partial-fixed it instead and split out the harder variant. The contract is that we'll know which is which.
Where this points
The bet four days ago was that the architectural shape was right and the maturity gaps would close on both sides. The four days since:
- The architectural shape held under load. Real Rails-blog input continues to lower into the post-lowering Spinel-subset shape with no new architectural decisions.
- The cadence Matz committed to is running. Issue-to-fix is hours, not weeks.
- Both sides are moving — Spinel's inference, Roundhouse's emit normalization, in lockstep.
The remaining work is finite and characterizable: a list of inference gaps, a list of runtime-substrate pieces, a list of Roundhouse refactors. None of it is architectural. All of it is iteration.
What's offered, then, isn't a finished thing. It's a channel that runs, between two projects that have already shown they can move in tandem when the issue-shape is concrete. The cadence is the result, four days in.
Roundhouse is open source: dual-licensed MIT / Apache-2.0. Issues and discussion welcome.