Repository navigation
Releases: vyncint/reconverge
Release list
v0.8.0 — the first run works, and every verdict is earned
The first-run release. A deep review of 0.7.0 from a new user's seat found
that the README's own cargo reconverge check exited 2 in any project
without the pinned nightly, that a confirmed hang in a [[bin]] passed
with exit 0, that a barrier one function call away was never looked at, and
that the witness confirmed hangs on code where every thread arrives. This
release fixes those, and the twenty-two smaller things the same review
filed (#186–#211): the first run works from any project, every target's
findings gate, divergence crosses function boundaries, and a confirmed
verdict is one the replay earned under the declared launch.
Added
-
An install is verified again, through the first run, and the Action is
run by CI.install.ymlhad not run since 0.6.1: the release is
created withGITHUB_TOKEN, whose events start no workflow, so its
release: publishedtrigger never fired, and its weekly schedule was
commented out.release.ymlnow dispatches it after the publish, the
schedule is back, and it goes past--help:setupinto the CLI's own
install root, thencargo reconverge checkthrough cargo in a kernel
crate with no toolchain file, which must exit 1 on its RC001. Nothing in
this repository used./actioneither; a newactionjob in
required-greenruns it on the lint samples (must fail, on an RC001 in
its SARIF) and on a clean crate (must pass). action/README.md no longer
says "verified end to end" on the strength of a smoke repository that
last ran beforev0existed. (#208) -
check --package <name>and--exclude <name>, both repeatable, and
anargsinput on the Action to pass them. Upstream's single-source
layout links the CUDA host API, whosecuda-bindingsbuild script needs
the CUDA toolkit; a workspace can now check the crate that holds the
kernels and leave the host crate out. Both READMEs say so, and a name
that is not a workspace member is refused before anything is built.
(#197) -
check --require-kernelsexits 2 when no#[kernel]was analyzed,
for a CI gate that must not pass on an empty run. (#196) -
--cctakes the arch spellings cuda-oxide itself uses.sm_90a,
sm_100f,sm_120a,compute_86and9.0awere all refused, while
cargo-oxide's--archis spelled that way throughout and the README said
"the CUDA spellings work". Ana/fsuffix selects an instruction set on
the same device, so it means the base capability's limits; it is still
checked, sosm_86a— which names no architecture — is an error rather
than a quiet 8.6. The table gains 10.3, 11.0 and 12.1, all in the CUDA 13
guide's memory table (12.1 as the second member of12.x). (#200) -
SARIF rules carry what code scanning shows beside an alert: a
fullDescription(the explain page's opening paragraph) and the page
itself ashelp.markdown/help.text. A rule had only an id, a one-line
summary and a link. (#198) -
cargo reconverge help, and--version/-Vafter a subcommand
(check -V) are answered instead of being reported as unrecognized
arguments with the usage text. The usage text now lists--version, the
explainalias,RECONVERGE_DRIVER/RECONVERGE_TUI, and thatwatch
exits with its last run's code. (#199) -
reconverge-tui --version(and-V).cargo reconverge setup
installs three binaries that must be one release, and the TUI was the one
that could not say which it was:--versionopened the artifact shell on
a file called--version.--helpnow lists every view (#206).
Changed
-
check,inspectandlearnlook for the driver and the TUI on
PATHwhen they are not beside the CLI, afterRECONVERGE_DRIVER/
RECONVERGE_TUIand the sibling, which still win. AbsolutePATH
entries only: a relative one resolves against the project being analyzed,
and areconverge-drivercommitted there must not become the compiler
wrapper. (#203) -
A full-mask collective under a warp-uniform guard is no longer RC002.
warp_id() == 0,threadIdx_x() / 32 == kandthreadIdx_x() >> 5read
the same on every lane of a warp, so each warp takes the branch whole and
the mask names only lanes that are there. Uniformity now has a warp scope
below the block for this one question; the same guard over
sync_threads()is still RC001, because whole warps skipping a block
barrier is the hang.warp_id()isthreadIdx_x() / 32upstream, so this
holds only when the declared block lays its warps along x — 1-D, rows a
whole number of warps wide, or rows no wider than the divisor. Under
(48, 2, 1)warp 1 holds x 32..47 of one row and 0..15 of the next, and a
kernel with no contract could be launched that way: it keeps the warning,
with a note that declaring the block settles it. (#194) -
A collective site under a declared multi-warp block is no longer
promoted. It used to be confirmed from a one-warp(32, 1, 1)replay,
which is a launch the contract excludes; it stays atwarningwith a
replay:note, because a collective site is replayed only within one
warp. (#190) -
A run that analyzed no
#[kernel]says so:
reconverge: no #[kernel] functions found in 1 crate — nothing was analyzed, still exit 0, and the Action raises a::warning::for it. A
wrongworking-directory, a crate without the cuda-oxide dependency, or
kernels behind a disabled cfg used to print the exact summary of a clean
pass. (#196) -
checknames the layer that failed. A build script that failed (the
CUDA toolkit missing, typically), a driver that cannot start under the
toolchain, and ordinary compile errors used to share one message that
blamed the toolchain and recommendedsetup. cargo's own diagnostics now
pass throughcheckrather than straight to the terminal, coloured when
stderr is one; cargo's progress bar is not shown. (#197) -
witnessafter a check that confirmed nothing exits 0, saying there
is nothing to replay, instead of exit 2 and "runcargo reconverge check
first" right after one had run. A project wherecheckhas never run is
still exit 2. (#201) -
SARIF locations are relative to the repository root, and say so. Each
uriwas relative to the Cargo workspace root with no base, and GitHub
reads a relative URI from the repository root, so a workspace in a
subdirectory — the Action'sworking-directory: kernels— put every alert
onsrc/lib.rsinstead ofkernels/src/lib.rs. The report now finds the
repository around the workspace (a.gitdirectory or file), spells each
path from there withuriBaseId: "%SRCROOT%", and declares that directory
inoriginalUriBaseIds; a file outside the repository gets an absolute
file://URI. RC001's short description is now "an all-threads barrier
under thread-divergent control", notsync_threads()alone. (#198) -
A value-taking flag refuses an empty value and a following token that
begins with-.check --sarif=ran the whole analysis and then failed
withcannot write SARIF to : …;--baseline -hread a baseline named
-h, and--sarif -xwrote a report named-x. A path that begins with
-is still accepted inline:--sarif=-x. (#199) -
required-greennow waits for the macOS job and forcommit-policy
(#209). It is the ruleset's only required context, and neither fed it: a
red macOS PTY run did not block a merge while the README called the views
gated on macOS, and PR #84 merged with the DCO/attribution gate red.
ci.ymlcallscommit-policy.ymlas a reusable workflow, which keeps its
own triggers and is the one that posts the PR comment. -
reconverge-tuirefuses an unknown option with exit 2 and the usage,
instead of opening it as an artifact path — a misspelt--ascisurfaced
inside the view asNo such file or directory.--ends the options, for
a file whose name starts with a dash (#206). -
Ctrl-C in triage says what it threw away. It is still the exit that
always works, mid-reason or not, but with unsaved baseline edits it now
printsdiscarded N unsaved baseline editson stderr after the terminal
is restored and exits 130, where it used to exit 0 having dropped a
review pass without a word.cargo reconverge triagepasses the code on.
With nothing unsaved it is a plain quit, as before (#206). -
The Inspector's finding bar names the tier, spelt as
checkprints
it:finding 4/39 warning[RC005] …. A lint-samples run loads 39 findings
there of which 25 are warningscheckhides by default, and without the
word a hidden warning read exactly like a gating deny (#206). -
The witness view ranks its rows on a small terminal. The lane strip
and the current step come first, then the verdict, and the timeline gives
way before any of them; below the strip's own size the view says
terminal too small — the lane strip needs at least 47x10instead of
drawing the ruler over blank rows, which is what 40x10 showed. The header
and the current step's statement wrap rather than ending inblock (…and
"…without ever reaching the …" at 80 columns, so the 80x24 goldens move by
a row (#206). -
Lesson prose reflows when an authored line does not fit, instead of
losing the end of every line at 46 columns ("the mask promis…"), and a
page that still runs out of rows ends in… more below: enlarge the terminal. Where the authored breaks fit they are kept (#206). -
A call the analysis cannot follow is opaque, not barrier-free (#189).
A function pointer, adynmethod, a trait method on a generic kernel's
type parameter, anexterndevice function, and a function in another
crate of the workspace — each crate is compiled, and analyzed, on its
own — are counted incoverage, so the summary line says… opaque in N functionswhere it used to say nothing, and their results are div...
v0.7.0
The production-readiness release. It ships the correctness fix main had
been sitting on for twelve days, and closes the gaps between "the analysis is
correct and published" and "a team can depend on this in their CI": the
supply chain, the release, the platform coverage, and the semver rule that
decides what a field addition costs.
Added
-
Windows is gated in CI, and the README says which platforms are
supported. The word did not appear anywhere in this repository — not a
support statement, not an exclusion, not a job — while the README said the
analysis "runs anywhere". A Windows user had no way to tell whether a
failure was worth reporting.It mostly does run anywhere: the job found three bugs, two of which are
below and one of which madelearnpanic outright. The analysis,check,
the artifact round-trip, the schemas and the CLI contract are gated on
Windows now.The four terminal views are not, and that is stated rather than
skipped: under ConPTY the PTY suites time out waiting for a frame boundary
while the screen they print is the right screen, fully painted — two
different suites show it, so it is about how that console delivers what an
application wrote rather than about what these views render. #162 holds the
root cause, which needs a Windows machine to reduce..gitattributespins
the working tree to LF, which is the other half of thelearnfix.Components are named explicitly in that job rather than left to
rust-toolchain.toml: the channel installed on the runner and its
componentsdid not, and the failure surfaced three crates later as the
driver's build script reporting a missingrustc-dev— the right message
from the wrong layer.
Fixed
-
cargo reconverge learnpanicked on Windows, for all four lessons.
The prose arrives throughinclude_str!, which embeds the working tree's
bytes. A checkout withcore.autocrlf=true— the default on Windows —
gives\r\n---\r\n, so the page split found no separator, every lesson
collapsed to one page, and the page-metadata assertion fired. A surviving
\rwould have rendered as a glyph too. Line endings are normalized before
the split now, and the regression test is written against the bytes rather
than the platform, because a Linux clone of a repository whose files were
committed with CRLF has the same problem..gitattributespins the tree to
LF as well, for the golden files the PTY suites compare byte-for-byte. -
reconverge-driverrefused to build on Windows withrustc-dev
installed. The build script that turns a missing component into one
actionable line looked in<sysroot>/libforlibrustc_driver*, which is
the Unix layout — windows-msvc putsrustc_driver-<hash>.dllunder
<sysroot>/bin, so a correct toolchain was reported as missing the
component and the build stopped. That is exactly what the script's own
documentation promises not to do.The same check was also a false negative everywhere:
librustc_driver-<hash>.sois in<sysroot>/libon any toolchain, because
it is the shared library rustc itself links — measured against a stable
1.98.0 with norustc-dev, one match there and zero in
lib/rustlib/<host>/lib. It now looks for the component's.rmetain
<sysroot>/lib/rustlib/<host>/lib, which is whererustc-devinstalls on
every platform, so the guard fires when it should and only then.
Added
-
Releases carry binaries, and the action has a version to pin. Two gaps
that were really one:release.ymlcompiledcargo-reconverge,
reconverge-driverandreconverge-tuifor Linux and macOS on every
release and then left them in workflow artifacts, which expire and need a
GitHub login to reach —v0.6.1shipped with zero assets. So the only
supported install wascargo install, which compiles a rustc-driver, and
the GitHub Action pays that on every consumer's run because it deliberately
never caches.github-releasenow creates the release from this version'sCHANGELOG.md
section — not from generated commit subjects — and attaches one
reconverge-<tag>-<host-triple>.tar.gzper platform with a.sha256
beside it.float-major-tagthen repointsv0through the API, after the
publish, so the floating tag can never name a release that did not reach
crates.io.README.mdandaction/README.mddocument@v0instead of@main. That
was the sharper half of the problem: the ref decides which analyzer version
the action installs, so@mainre-decided it on every run of a consumer's
CI, and a merge here changed the gate in somebody else's pull request with
no notice and nothing to pin. -
--cctakes the CUDA spellings.sm_86is whatnvcc -archtakes,
whatptxasprints and what a PTX.targetdirective says;86is the
same thing without the prefix. Both now normalize to8.6, as dosm_120
and100— a capability is one or two major digits and exactly one minor
digit, so the last digit of a run is always the minor.launchboundhas accepted these two spellings since 2.0 and passes this
flag straight through to this parser, so the pair disagreed about the
spelling of their shared argument. Nothing else moved: normalization
rewrites the text and hands it to the same parser, so8.xis still
non-numeric and999.9still lands on the table message.One test changed its example rather than its assertion:
a_bad_value_answers_in_one_line_…used--cc 80, which now means 8.0.
Changed
-
termlens 0.11 in both PTY suites (
reconverge-tuiand
cargo-reconverge), and the vendored skill with it. 0.11 is termlens's
stability candidate: from it no promised item changes incompatibly before
its 1.0, so this requirement should hold for a while.Its one breaking change lands here as a simplification.
Screen::unsupported()returns a view instead of a slice ofArc<str>,
andunsupported_overflow()folds into it — so the pinned list and "the
record is not truncated" are now one assertion, because the view
compares equal to a slice only when the retained shapes match and
nothing overflowed the bound.tests/emulation.rsloses its
Vec<String>helper, and the three flow tests lose a separate overflow
assertion each.The comment in
tests/emulation.rswarning that termlens#320 reported
blink and strikethrough as unsupported when the shadow parser implements
them is gone: that was fixed upstream in 0.10.2, so an entry in this
list is now a real gap in every case. -
Uniformity is scoped now, and a barrier is checked against its own
participants (#133). A guard uniform within a block settles
sync_threadsand settles nothing wider:blockIdxand
cluster::block_rank()are constant on every thread of a block and
different on the next one, so#[cluster_launch(2, 1, 1)] pub fn only_rank_zero() { if cluster::block_rank() == 0 { cluster::cluster_sync(); } }
is entered by one block of the cluster and skipped by the other, and 0.6.x
reported nothing at all —exit 0, no findings, zero opaque. The same
shape one scope up (grid::sync()under ablockIdxguard) was equally
silent, which is how the report widened from a cluster quirk to the
general statement.The engine now carries a second lattice beside lane uniformity:
LaunchScope—Block ⊑ Cluster ⊑ Grid— recording how far each value is
actually constant, and the dialect answersbarrier_scopefor how far
each barrier reaches. A finding is raised when the guard's scope does not
cover the barrier's.Always
warning, never promoted. The witness interpreter replays the
lanes of one block and cannot execute the second block this needs, so
there is no concrete hang to confirm; calling itconfirmedwould be a
claim the evidence does not support.The precision half matters as much and is pinned by its own samples:
everything starts grid-constant and is narrowed only by what it reads, so
a kernel argument,blockDim, and arithmetic over either still decide a
grid-wide barrier, andcluster::cluster_idx()still decides a
cluster-wide one.blockDim/gridDim/nsmid/gridid/nwarpid/the
launch environment registers and the cluster's shape moved from
BlockUniformto the newGridUniform;smid,block_rankand the
cluster coordinates stayed block-scoped, which is what makes the check
fire where it should.
Breaking
-
reconverge-core:Callee,BarrierSiteandAnalysisgain fields
(Callee::scope,BarrierSite::{scope, cross_block_cause},
Analysis::{local_scopes, block_guard_scope}), andCallKindgains
ClusterUniformandGridUniformbesideBlockUniform.SimtDialect
gainsbarrier_scope, with aBlockdefault, so an existing dialect
keeps compiling and keeps the narrow answer. -
reconverge-artifacts: the four artifact roots,ToolInfoand
ReadErrorare#[non_exhaustive]. "Schemas are additive-only" and
"adding a field is not a breaking change" turned out to be different
promises, and only the first one held: a new key infindings.v1is
additive in JSON and a major break here, because a struct with public
fields can be built with a struct expression from anywhere. TheBreaking
entry above is that, twice, for threereconverge-corerecords.Which types close is decided by who builds the value, and the split was
measured rather than assumed. Marking all twenty-two records broke 42
construction sites inside this workspace alone, acrosscargo-reconverge,
reconverge-tuiand the driver — a downstream front-end that writes
artifacts would pay the same, so the leaf records (Finding,SourceSpan,
Step,Entryand the rest) stay plain deliberately. The roots do not:
a reader deserializes one rather than assembling it, a top-level ke...
v0.6.1 — the cluster arrival is not a barrier
Three issues reported against 0.6.0, all reproduced here before anything was
changed. Two are fixed; the third is a real recall gap that cannot be closed
in a patch, and is now stated in the tool's own output instead of being left
for a user to discover.
Fixed
-
A valid split cluster arrival is no longer reported as two deadlocks
(#132). The raw cluster barrier is a split barrier:
barrier.cluster.arrivesignals and returns,barrier.cluster.wait
blocks. 0.6.0 classified both halves asBarrier, on the reasoning that
"every thread of the cluster must reach both" — true of the pair, false of
each instruction. A kernel whose two warps arrive through different
instructions and then share one waitif warp::warp_id() == 0 { cluster::barrier_cluster_arrive_aligned(); } else { cluster::barrier_cluster_arrive(); } cluster::barrier_cluster_wait_aligned();
produced two
RC001/confirmedfindings and exit 1, with a witness
claiming 32 of 64 lanes never arrive — they arrive through the other
instruction. Onlybarrier_cluster_wait*is a barrier now; the four
arrival forms join the mbarrier family inconformance/SURFACE_ALLOW.What this costs is written down rather than glossed:
if c { arrive() } wait()is a real hang that is no longer reported, because deciding it
needs the phase counting RC001 already declines for mbarrier. The
divergent wait — the whole pair inside one branch — is still
confirmed, and a lint sample pins both directions. -
check-surface.shfails when it cannot read an included source
(#134). A module thatinclude!s a file that is not on disk had its
surface silently under-enumerated —cat ... 2>/dev/nullhid the missing
file, the process substitution keptset -efrom seeing it, and the
script printedPASS — every surface function is classified or allowlisted. Unreadable input is exit 2 now, with the path.A gate whose failure path is never exercised is how that survived, so
scripts/check-surface-selftest.shdrives the gate over synthetic trees
and asserts the exit code for each outcome — clean, unknown name,
allowlisted, classified, missing include, and an include that is
present with an unknown name inside it. It needs no checkout, no network
and no compiler, and it fails against the 0.6.0 script.
Documentation
-
RC001's two recall boundaries are now in
explain RC001and the
README, with the kernels that reproduce them.The split-barrier boundary above is one. The other is #133: uniformity
is block-scoped. A guard that is uniform within a block —blockIdx,
cluster::block_rank()— makes the barrier under it non-divergent, which
is right forsync_threadsand not the whole answer for a barrier whose
participant set is wider than a block:#[cluster_launch(2, 1, 1)] pub fn only_rank_zero() { if cluster::block_rank() == 0 { cluster::cluster_sync(); } }
reports clean, and so does
grid::sync()under ablockIdxguard — the
gap is not a cluster quirk. Closing it needs a uniformity lattice that
separates block- from cluster- and grid-uniformity, which reaches
reconverge-core's publicBarrierSiteand is therefore 0.7.0 work, not
a patch. It is a recall gap and never a precision one: nothing is
reported that is not there.
v0.6.0 — the current upstream, and nothing unclassified
Install
cargo install cargo-reconverge --locked
cargo reconverge setup # installs nightly-2026-08-28 and the matching driverChanged
-
SimtDialectgainsclassify_method_call(def_path, receiver)with a
default that defers toclassify_call, and the driver now hands the
dialect the callee'sSelftype for trait methods. One definition path,
cooperative_groups::ThreadGroup::sync, is five different barriers; the
receiver decides: on aThreadBlock,GridorClusterit is RC001's
scope-wide barrier, on aWarpTile<N>orCoalescedThreadsit is a
tile-scoped contract this analysis does not model and staysOther.
Additive for implementors (the default), and the first path-only
classification that was structurally unable to be right. -
FnModelgainsdeclared_cluster, read from the
__cluster_config::<X, Y, Z>()marker#[cluster_launch]plants; an
RC001 finding in such a kernel names the cluster in its notes, since a
divergentcluster_synchangs every block of it. Struct literals of
FnModeloutside the crate need the new field. -
The pinned nightly is
nightly-2026-08-28(rustc 1.100, LLVM 23),
matching the pin upstream cuda-oxide moved to on 2026-08-29. The driver's
rustc_publicuse is ported:Statement/Terminatorspans now live under
source_info,Rvalue::Usecarries a retag flag, and the new
Rvalue::Reborrowreads its place likeRef. Every pin site moved
together —rust-toolchain.toml,cargo reconverge setup, the README
badge and install lines, the driver README. A kernel crate tracking
current cuda-oxide could not be analyzed under the old pin at all. -
The conformance pin is cuda-oxide
26754ae5(2026-09-06, from
a766fc26of 2026-08-16, 173 commits). Conformance passes at the new pin
with the baseline unchanged: zero false positives at default confidence
over the extracted corpus. -
The PTY harness moves to termlens 0.10.1 (dev-dependency, both
reconverge-tuiandcargo-reconverge, which must stay on one version).
No call site changed — nothing here useddrag, aStyleliteral, the
snapshot macro or anErrorvariant — so the whole value is in what 0.10
made assertable. One new transitive dependency,unicode-width 0.2, which
both crates already depend on directly at the same requirement.
Fixed
-
Two
unreachable!()arms on library paths are gone. The witness's
collective-release step re-derived a lane's parked block from a stop it
had already matched on; it now carries the block from the filter. The
learn view's page state machine matched four replay actions and then
matched them again with a catch-all; it has one arm per action, so a new
KeyActionfails to compile instead of reaching an arm that "cannot
happen". -
A driver replaced in place forces a re-lint on cargo 1.100. cargo
moved fingerprints from<profile>/.fingerprint/to
<profile>/build/<pkg>/<hash>/fingerprint, so the sweep that keys
re-linting to the driver's identity had become a no-op: an upgraded
driver reprinted the previous build's verdict. The wrapped check now pins
CARGO_BUILD_BUILD_DIRas well asCARGO_TARGET_DIR, so its state never
lands in the workspace's owntarget/, and the sweep covers both layouts.
Added
-
Hardware session #1 has a result.
docs/hardware/results/t4-cc75-2026-09-09.tsv:
on a Tesla T4 (CC 7.5, cuda-oxide26754ae5) the two RC001 probes —
the canonical divergentsync_threadsand the new cooperative
this_thread_block().sync()— ran to completion rather than hanging,
and the RC002 probe returned0x55555555for a full-mask ballot half the
warp never reached. explain/RC001.md carries the measurement next to its
"usually hangs"; the verdict wording is unchanged pending a second part. -
README and docs at 0.6.0: RC001's row and coverage bullet name the
cooperative-groups and cluster barriers and the tile-scoped boundary; a
"Not in scope, by decision" entry says that register budgeting and
memory/DisjointSlicerace analysis are not this tool's, and which rules
cover the adjacent questions; the surface gate and pin watch are described
inconformance/README.mdanddocs/ARCHITECTURE.md;docs/RELEASING.md
has thebaseline-versionstep and thebreakinglabel. -
A
semvergate on every pull request —cargo-semver-checksover the
five published library crates against the last release on crates.io,
with the release type forced topatch: left to infer it from the
version number, the checker treats a 0.x minor bump as a major release
and skips every check, so a PR that bumps the version in the same change
as a break is green with zero checks run. A PR that must break a promised
item carries thebreakinglabel, which runs the gate as a 0.x minor —
visible, and per RELEASING.md's rule that a break is a minor release.
baseline-versionis a literal, bumped in each release PR. -
An
msrvgate building the five library crates on Rust 1.88, which
they now declare asrust-version; only the driver needs the pinned
nightly. -
#![warn(missing_docs)]on every library crate, and the 274 public
items it reported documented — every field, variant and method of the
four artifact documents, the analysis model and the TUI views. The
docsjob's-D warningsmakes it a gate. -
Twenty-three upstream names classified that were
Other— a coverage
note, never a finding — at the previous pin. The eightredux_sync_*_f32
collectives (min/max,_abs,_nan, and both) join RC002's masked
surface;cooperative_groups::block_reduceandblock_scan, which carry
async_threadsinside, join RC001's barriers, as does
ThreadGroup::syncon a block, grid or cluster; theclustermodule's
geometry reads (block_rank,cluster_idx,cluster_size,
num_clusters,cluster_ctaid{X,Y,Z},cluster_nctaid{X,Y,Z}) are
block-uniform;__cluster_configis a marker. The gate also follows each
module'sinclude!d generated files, which is wherewarp::warpid()
lives: the special registers (smid,nsmid,gridid,nwarpid,
envreg1/2) are block-uniform,warpidis a warp-level read like
warp_id, and the rawbarrier_cluster_arrive*/wait*pair is RC001's
barrier at cluster scope. Everything left — the counted CTA barrier
(barrier_cta_*, whose participants are the count the caller names), the
mbarrier family, the fences,nanosleep,setmaxnreg_*, the distributed
shared-memory helpers,warp_reduce,warp_scan,coalesced_threads—
is allowlisted with its reason inconformance/SURFACE_ALLOW, and
scripts/check-surface.shnow runs in theconformanceCI job, so the
next upstream primitive fails the build instead of silently degrading
recall. -
Two lint samples:
rc001_cooperative_block_sync(a divergent
this_thread_block().sync()— an RC001 finding, where 0.5.0 reported
nothing) andrc001_cluster_divergent_sync(a divergentcluster_sync
under#[cluster_launch(2, 1, 1)], whose finding names the cluster). -
explain/RC001.md states the cooperative-groups boundary next to the
mbarrier one; explain/RC002.md names thef32redux family. -
The mutation corpus's drift detector asks about
warp::calls only.
It counted a user helper namedreduce_all_formsin upstream's
redux_f32example as two "unclassified collectives"; qualified by the
call path'swarpsegment,skipped_unclassified_collectivesreads 0,
which is what it is meant to read when the dialect and the corpus agree.
skipped_sites_outside_kernelsis 8 at the new pin: collective calls in
upstream helper functions, which the mutator leaves alone by design. -
pins.yml: a weekly pin watch that opens one issue when upstream
cuda-oxide moves pastconformance/PIN, when itsrust-toolchain.toml
channel differs from ours, or when a public function in itswarp,
thread,barrier,grid,cooperative_groupsorclustermodule is
neither classified by the dialect nor allowlisted. The third check is
scripts/check-surface.shwithconformance/SURFACE_ALLOW: an
unrecognizedcuda_device::call isOther— a coverage note, never a
finding — so a new upstream primitive is a silent false negative until it
is named, and "unknown" has to be a written decision rather than a default. -
crates/reconverge-tui/tests/emulation.rs: what the emulator can and
cannot see. Every golden is a grid a VT emulator built from the shell's
bytes, and until now nothing said the emulator had implemented that stream.
Screen::unsupported()is pinned exactly — one entry,^[[59m, ratatui's
underline-colour reset, which changes no cell — across all four views,
besideinsert_mode,bells,visual_bells,mouse_modesand
row_wrapped. The DEC 2026 bracketing the suite'swait_framepolicy
depends on is not pinned by an assertion, deliberately:wait_frame
returns only a completed update, so any frame in hand already satisfies it,
and weresync_drawreverted the wait would time out — naming that exact
cause — before an assertion could run. -
crates/reconverge-tui/tests/cli.rs: the goldens throughtermlens-cli.
Every file undertests/golden/is already a saved screen in termlens'
snapshot text format, becauseassert_goldenwritesScreen::to_string()—
sotermlens diffandtermlens renderopen them with no conversion, and
termlens inspectof the real binary reproducesshell-80x24.txtexactly.
#[ignore]d, because both crates are published and acargo testshould
notcargo installanything unasked; CI runs it with--ignored. -
NO_COLOR is now asserted to strip styling, not merely to preserve the
grid. The three matrix legs compared two character grids, which a view
that hardcoded a colour ...
v0.5.0 — read what the tool wrote
The theme is reading what the tool wrote. Nine of these findings reduce
to the same shape: an artifact, a document or a page was produced correctly
and then consumed without ever being questioned — so the gate could pass on
a crate it never built, a baseline could be suppressed by a document that
said it was something else, and the debugger's evidence panel could argue
against a true finding.
Added
-
reconverge-driver --reconverge-version. The driver is a
rustc-driver and forwards argv by design, so--versionprints rustc's —
which meant the half that does the analysis and writes the artifact could
not be identified at all, while both shipped consumers stamped their
corpora with the CLI's version. Every other invocation still hands rustc
an untouched argv, cargo's-vVand--printprobes included. -
findings.v1gainstargetandcoverage, additively.targetis
the compiled target's crate types, so a package with a lib and a bin no
longer emits two documents that nothing can tell apart.coverageis the
run's own tally of what the engine could not read. -
witness.v1documents a multi-warp replay.lanesis now whole warps
up to 128 rather thanconst: 32,initial_lane_stateshas no upper
bound, andlane_changes[].lanereaches 127. This is recording reality:
the declared-block replay has written 64, 96 and 128 since 0.1.12, so the
schema had been unsatisfiable for exactly the artifacts that gate a build.
It also states, for the first time, the invariant its lane strip is drawn
from — at any step carrying awarp_op, the lanes present are exactly the
set bits ofwarp_op.active. -
Three new gates.
scripts/check-schemas.shvalidatesfixtures/and
what an end-to-endcheckemits againstschemas/(round-tripping
through serde is not validating: it tolerates anything additive and never
sees aconst).scripts/record-fixtures.shregenerates the witness
fixtures from a real run and diffs them, so the API tests test the
producer.scripts/check-plurals.shfails on a count printed with a
parenthesized plural.conformance/extractor— its own workspace, and
therefore outside every gate in the repository — is now covered by fmt and
clippy in CI and injust ci.
Changed
-
JSON mode's help names the filters it ignores.
--strictand
--show-suppressedaffect text output only; JSON remains the unfiltered
analysis record. The behavior was deliberate, but the help text previously
implied that both flags changed JSON output, and a run given either flag
in JSON mode now says so on stderr. Reported in #74. -
witness.v1'sstatementis documented as prose, which is what it has
always carried. It was described as a MIR statement or terminator; no
released driver has ever emitted one — the replay is built from the site
rather than by walking statements, and the model drops the printable form
at extraction. All three witness fixtures showed MIR, and all three were
stampedtool.version 0.0.0, so a front-end author who wrote a MIR parser
had it confirmed by the project's own API tests. The fixtures are now
recorded from a realcheck;reconverged-clean.jsonis the one that
cannot be (a witness is written only for a confirmed finding) and
fixtures/README.mdsays so. Reported in #92. -
Coverage is a property of the run, not a note on one rule. It was
built inline in the RC001 site and nowhere else, so four of the five codes
never carried it — and the case it exists for could not be reached from a
note at all: a kernel whose divergent barrier is spelled inasm!has no
finding to hang one on, and--strictexited 0 over it with a clean bill
of health. The tally now rides infindings.v1, reaches every code as a
note, and closes the summary line whenever anything was left unread.
Reported in #96. -
JSON mode's help names the filters it ignores.
--strictand
--show-suppressedaffect text output only; JSON remains the unfiltered
analysis record. The behavior was deliberate, but the help text previously
implied that both flags changed JSON output.
Fixed
-
The gate compiles every member it gates on. The wrapped build was a
barecargo check, so cargo's own package selection applied while the
report and the exit code came from every membercargo metadatalists.
Run the gate from inside a member directory — which the action's own
working-directoryinput exists to do — or add adefault-membersline,
and the sibling was never re-linted while its previous, clean artifact was
printed as this run's answer, with the right paths, in the right order,
exit 0. A deny-tier RC003 and a confirmed RC001 rendered as
0 deny, 0 confirmed. It is--workspacenow, and a member that still
produces nothing is named and exits 2 rather than passing by omission: the
self-heal already computed that set, forced a full rebuild with it, and
discarded it unread — which also cost two wrapped builds on every run
forever. Reported in #107. -
A findings artifact that is not one is refused.
checkread a
document out oftarget/reconverge/, rendered it, gated on it and
re-published it on stdout without once asking whether it was one of its
own; the only validation was the filename, under an error message that
already promised the check. Afindings.v99written by a driver this
build has never met was merged and printed while the SARIF from the same
invocation stamped a different producer. Reported in #109. -
A baseline is checked against its own schema tag.
schemaexists to
be checked and nothing checked it, so a document declaring itself
findings.v1, from a tool that is not this one, suppressed a deny-tier
finding as happily as a real baseline — and a futurebaseline.v2whose
entries mean something else would be read with v1 semantics, silently, in
CI. All four readers now go through onedeserialize_checked, so a fifth
cannot reintroduce the gap. Reported in #68. -
A driver replaced in place forces a re-lint. The driver goes in as
RUSTC_WORKSPACE_WRAPPERand cargo does not notice a same-path wrapper
whose contents changed, socargo install reconverge-driverover a warm
tree re-analyzed nothing and CI kept gating on whatever the old driver
concluded — including a clean verdict on a crate that now has a finding.
Its path, size and mtime live in thecc-markerbeside--cc. Reported
in #109. -
A package with a lib and a bin emits two documents that name their
targets. Both carried the samecrate, and the driver's own comment
told consumers to key on it — so a dictionary keyed bycratekeeps
whicheverread_dirhanded back last, which in the ordinary GPU-project
shape (kernels in the lib, a thin host binary beside them) can be the
empty one. The sort key is total now, so two projects of identical shape
stop emitting their documents in different orders. Reported in #93. -
checkinto a reader that closes early is not a crash. Rust sets
SIGPIPEtoSIG_IGN, soprintln!into a closed pipe panicked and the
process exited 101 — outside the documented set — reporting a rustc panic
notice as though the analyzer had crashed on the code under test.
check --strict | head -40is the most ordinary thing anyone does to a
long report. The verdict is computed before anything is rendered, so the
run keeps its exit code. Reported in #110. -
A comment in the analyzed source no longer erases the diagnostics above
it. Two realESCbytes in a source line repainted the terminal from
the analyzed file while the summary went on counting the findings it had
wiped. C0, DEL and C1 bytes render as Unicode control pictures, tabs
expand to a fixed stop, and nothing below0x20reaches stdout. Reported
in #108. -
The caret is measured in terminal cells. A tab or a wide character
before the span put it under the wrong column, and a four-character CJK
parameter got four carets for the eight cells it occupies. Spans are
untouched:findings.v1, SARIF and the baseline keep character columns.
Reported in #105. -
The diagnostic block is indented by its own line number's width,
the way rustc does it, rather than pinned at the width that suits a
two-digit line. And a source line past 120 cells is trimmed around its
span with an ellipsis marking the cut: an 830-column line wrapped ten
times on an 80-column terminal and scrolled its own header away. Reported
in #71. -
--helpon every subcommand prints usage and exits 0. It was reported
as an unrecognized argument, with exit 2 — a poor greeting for the first
thing anyone types.--baseline --helpis still the missing value it was.
Reported in #74. -
watchkeeps stdout clean in JSON mode. Its dashboard went to stdout,
so half the stream was not JSON. Reported in #89. -
The summary line stops recommending the flag the run was given. A
closing instruction to rerun with--show-suppressedin the run that
already passed it reads as though something were still being withheld,
which is exactly the doubt the flag exists to remove. The counts stay
unconditional. Reported in #103. -
SARIF carries the provenance walk, a
helpUri, and a stable rule
level. The chain back to the divergence source was dropped entirely
though SARIF hasrelatedLocationsfor its exact shape; the rules had
nothing behind "Learn more"; and a rule's default severity was sampled
from whichever result of that code came first, so swapping two kernels in
a source file flipped the published default.--sarifis how the action
delivers findings, so for a CI user this is the output. Reported in #70. -
A witness does not outlive the finding it replays. Witnesses were
created and never removed, so fixing a kernel, re-running to
0 confirmed...
v0.4.0 — a gate that could not read a number
Fixed
-
RC004 reads a shared-memory length given as a named
const. It could
only read a literal.SharedArray<f32, TILE>arrived as an unevaluated
anonymous const body,eval_target_usizerefused it, and the static was
dropped from the budget with no finding and no diagnostic — so a kernel
declaring 80 KiB came back clean.This was a false negative in a capacity gate, which is the worst shape a
bug in this tool can take, and the path it hid on is the one that matters
most: an autotuner rewrites named consts per candidate, so every tunable
shared-memory size took it. launchbound pruned an eight-candidate space
with an over-cap tile as 8/8 clean at--cc 7.5.Reported by a user gating a kernel crate (#65), with the cause correctly
diagnosed in the report. Both halves are now sample kernels: a named const
over the cap, and one under it, because resolving a const only counts if it
resolves to the right number. -
A size RC004 cannot read is reported, not skipped. A length that
depends on a generic parameter is not knowable before the kernel is
instantiated. It is now adenyfinding saying the budget is unchecked,
rather than a silent omission that looks exactly like a kernel with no
shared memory. Fires on none of the sixteen sample kernels. -
A bad flag value answers in one line.
--cc 80printed the right
message —`80` is not a compute capability; expected e.g. `8.6`— and
then forty-four lines of usage text after it. A caller reading the tail of
stderr, which is where a failing tool usually puts its reason, got the
exit-code legend instead; launchbound reported that legend as the cause of
the failure, once per candidate. Usage now accompanies an unrecognised
argument, where the reference is the answer, and not a recognised argument
with an unusable value, where the message already is.
Changed
-
Counts agree with their nouns.
1 finding(s),4 file(s) watched,
1 function(s), 3 value(s)— on the last line of every run, in the TUI
headers, in the driver's progress line and in the conformance scripts, which
is to say in every CI log anyone pastes into an issue. They now read
1 warning finding (1 hidden; rerun with --strict to see it), verb and
pronoun included. -
--baseline's help says what--baselinedoes. It read as though a
missing file were always an empty baseline. Only the default is; a path
named explicitly must exist, so a typo cannot pass for a clean run. The
behaviour is unchanged and was already tested — the documentation was
wrong, and the error now explains the asymmetry rather than stating it.
Corpus
- 387 warning-confidence findings, 8 RC001/RC002 chains complete, mutation
precision 1.0 — unchanged. - Found in the wild this cycle: one (#65, RC004 named consts).
Install
cargo install cargo-reconverge --locked
cargo reconverge setup # installs the matching driver and TUIreconverge-driver is a rustc-driver tool and must be built by the exact
pinned nightly (nightly-2026-04-03, with rustc-dev). setup handles
that; a bare cargo install reconverge-driver on stable will not work, and
says so.
v0.3.0 — the whole run, at a glance
The witness view shows the whole run rather than one step at a time, so the shape of a divergence bug is legible without stepping through it. Details in the changelog.
-
The whole run, at a glance. Below the event block, every step is now listed with the current one marked and a delta column counting how many lanes changed state and to what — the artifact's own
lane_changes, not a new inference. The entire bug reads without pressing a key: the modulo splits the warp at step 3, sixteen lanes wait at the barrier at step 4, sixteen leave at step 5.h/lpreviously answered "what does this run do?" only by paging blindly and remembering, and remembering is what a reader of a divergence bug has least to spare. -
The view fills the terminal it was given. The verdict block drew two or three lines into a region a dozen rows tall, so on an ordinary terminal half the screen said nothing. The timeline takes that space and the verdict follows it directly. On a terminal too short for both, the timeline collapses and the verdict survives — the conclusion is never the thing that scrolls away, and it is sized from its own message so a long verdict is never cut mid-sentence.
-
The lane glyphs are unchanged. The ASCII warp diagram a CI log prints is literally a frame of this view, and that equivalence is load-bearing.
-
The test harness moved to termlens 0.6, and now checks the keystrokes it sends. Sixty of them across the flow tests were discarding a
Result: a send that fails means the application stopped reading, which is exactly what a TUI test exists to catch, and it was being dropped on the floor to resurface later as a timeout somewhere else. -
Two workflows that did not exist. A stress hunt that runs the PTY suite many times over across five machines, each at a different
--test-threads— one thread is the fast end where nothing contends, sixteen the crowded end where PTYs open and close on top of each other. And a check that the published crate actually installs for somebody who has only cargo, including thatcargo reconvergefinds it on PATH, which a--versioncall would not show. Both run weekly as well as at release.
Install
cargo install cargo-reconverge
cargo reconverge setupv0.1.12 — declared-block witness replay
Fixes #14: whole-warp divergence is witnessed at the block the launch contract declares, so a kernel that is safe at one warp and undefined at two gates exactly when its contract says two. Details in the changelog.
- The witness replays the declared block. When the one-warp replay finds nothing and
#[launch_contract]declares a one-dimensional block of several whole warps (64, 96, or 128), barrier findings are replayed at that size —warp_id()becomes the warp of the thread index,lane_id()wraps per warp, and the lane diagram prints one row per warp. Barriers only: any warp collective on a lane's path aborts the multi-warp replay, since a collective synchronizes within each warp. - A latent false-confirmation hole closed. Every thread-index witness used to replay as the lane id, but
threadIdx_y/threadIdx_zare 0 under the replay's one-dimensional block — a barrier guarded on them (correct code) could have been falsely confirmed. Each witness name now maps to its cuda-device formula under the replayed launch; unrecognized names evaluate to unknown, never a guess.
Install
cargo install cargo-reconverge
cargo reconverge setupv0.1.11 — unreachable-barrier tier documentation
Fixes #13: documentation only — the behavior is deliberate and was undocumented. Details in the changelog.
A recognized construct that the declared launch cannot reach (for example a barrier behind mutually exclusive guards) is reported at warning tier and never witness-promoted. The split is intentional — a launch contract is a declaration, not a proof, so staying silent would lose the diagnostic for kernels launched outside their declared shape, while the replay honestly has nothing to confirm under the declared one. Such findings never gate. Now written down in the README Limitations and --explain RC001.
Install
cargo install cargo-reconverge
cargo reconverge setupv0.1.10 — lane-environment gap documentation
Fixes #12: documentation only — a report about a stated reason, not a behavior, and the reason is what changed. Details in the changelog.
The Limitations entry for the lane-environment gap named "truncating casts" as missing machinery, but casts on the thread index are evaluated today and such guards promote to confirmed. The entry now says the precise thing: casts are evaluated as the identity — exact for the small thread-index values replays traffic in, wrong for full-width masks — and integer ! is modeled for booleans only; width-typed evaluation of those unchecked operations is what lanemask_* promotion actually needs. Overflow-checked arithmetic has been width-typed since 0.1.8.
Install
cargo install cargo-reconverge
cargo reconverge setup