Skip to content

Releases: vyncint/reconverge

v0.8.0 — the first run works, and every verdict is earned

Choose a tag to compare

@github-actions github-actions released this 31 Oct 04:31
33781b3

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.yml had not run since 0.6.1: the release is
    created with GITHUB_TOKEN, whose events start no workflow, so its
    release: published trigger never fired, and its weekly schedule was
    commented out. release.yml now dispatches it after the publish, the
    schedule is back, and it goes past --help: setup into the CLI's own
    install root, then cargo reconverge check through cargo in a kernel
    crate with no toolchain file, which must exit 1 on its RC001. Nothing in
    this repository used ./action either; a new action job in
    required-green runs 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 before v0 existed. (#208)

  • check --package <name> and --exclude <name>, both repeatable, and
    an args input on the Action to pass them. Upstream's single-source
    layout links the CUDA host API, whose cuda-bindings build 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-kernels exits 2 when no #[kernel] was analyzed,
    for a CI gate that must not pass on an empty run. (#196)

  • --cc takes the arch spellings cuda-oxide itself uses. sm_90a,
    sm_100f, sm_120a, compute_86 and 9.0a were all refused, while
    cargo-oxide's --arch is spelled that way throughout and the README said
    "the CUDA spellings work". An a/f suffix selects an instruction set on
    the same device, so it means the base capability's limits; it is still
    checked, so sm_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 of 12.x). (#200)

  • SARIF rules carry what code scanning shows beside an alert: a
    fullDescription (the explain page's opening paragraph) and the page
    itself as help.markdown/help.text. A rule had only an id, a one-line
    summary and a link. (#198)

  • cargo reconverge help, and --version/-V after a subcommand
    (check -V) are answered instead of being reported as unrecognized
    arguments with the usage text. The usage text now lists --version, the
    explain alias, RECONVERGE_DRIVER/RECONVERGE_TUI, and that watch
    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: --version opened the artifact shell on
    a file called --version. --help now lists every view (#206).

Changed

  • check, inspect and learn look for the driver and the TUI on
    PATH
    when they are not beside the CLI, after RECONVERGE_DRIVER /
    RECONVERGE_TUI and the sibling, which still win. Absolute PATH
    entries only: a relative one resolves against the project being analyzed,
    and a reconverge-driver committed 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 == k and threadIdx_x() >> 5 read
    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() is threadIdx_x() / 32 upstream, 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 at warning with 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
    wrong working-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)

  • check names 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 recommended setup. cargo's own diagnostics now
    pass through check rather than straight to the terminal, coloured when
    stderr is one; cargo's progress bar is not shown. (#197)

  • witness after a check that confirmed nothing exits 0, saying there
    is nothing to replay, instead of exit 2 and "run cargo reconverge check
    first" right after one had run. A project where check has never run is
    still exit 2. (#201)

  • SARIF locations are relative to the repository root, and say so. Each
    uri was 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's working-directory: kernels — put every alert
    on src/lib.rs instead of kernels/src/lib.rs. The report now finds the
    repository around the workspace (a .git directory or file), spells each
    path from there with uriBaseId: "%SRCROOT%", and declares that directory
    in originalUriBaseIds; a file outside the repository gets an absolute
    file:// URI. RC001's short description is now "an all-threads barrier
    under thread-divergent control", not sync_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
    with cannot write SARIF to : …; --baseline -h read a baseline named
    -h, and --sarif -x wrote a report named -x. A path that begins with
    - is still accepted inline: --sarif=-x. (#199)

  • required-green now waits for the macOS job and for commit-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.yml calls commit-policy.yml as a reusable workflow, which keeps its
    own triggers and is the one that posts the PR comment.

  • reconverge-tui refuses an unknown option with exit 2 and the usage,
    instead of opening it as an artifact path — a misspelt --asci surfaced
    inside the view as No 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
    prints discarded N unsaved baseline edits on 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 triage passes the code on.
    With nothing unsaved it is a plain quit, as before (#206).

  • The Inspector's finding bar names the tier, spelt as check prints
    it: finding 4/39 warning[RC005] …. A lint-samples run loads 39 findings
    there of which 25 are warnings check hides 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 47x10 instead of
    drawing the ruler over blank rows, which is what 40x10 showed. The header
    and the current step's statement wrap rather than ending in block (… 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, a dyn method, a trait method on a generic kernel's
    type parameter, an extern device function, and a function in another
    crate of the workspace — each crate is compiled, and analyzed, on its
    own — are counted in coverage, so the summary line says … opaque in N functions where it used to say nothing, and their results are div...

Read more

v0.7.0

Choose a tag to compare

@github-actions github-actions released this 22 Sep 07:39
2f2f3a2

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 made learn panic 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. .gitattributes pins
    the working tree to LF, which is the other half of the learn fix.

    Components are named explicitly in that job rather than left to
    rust-toolchain.toml: the channel installed on the runner and its
    components did not, and the failure surfaced three crates later as the
    driver's build script reporting a missing rustc-dev — the right message
    from the wrong layer.

Fixed

  • cargo reconverge learn panicked on Windows, for all four lessons.
    The prose arrives through include_str!, which embeds the working tree's
    bytes. A checkout with core.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
    \r would 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. .gitattributes pins the tree to
    LF as well, for the golden files the PTY suites compare byte-for-byte.

  • reconverge-driver refused to build on Windows with rustc-dev
    installed.
    The build script that turns a missing component into one
    actionable line looked in <sysroot>/lib for librustc_driver*, which is
    the Unix layout — windows-msvc puts rustc_driver-<hash>.dll under
    <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>.so is in <sysroot>/lib on any toolchain, because
    it is the shared library rustc itself links — measured against a stable
    1.98.0 with no rustc-dev, one match there and zero in
    lib/rustlib/<host>/lib. It now looks for the component's .rmeta in
    <sysroot>/lib/rustlib/<host>/lib, which is where rustc-dev installs 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.yml compiled cargo-reconverge,
    reconverge-driver and reconverge-tui for Linux and macOS on every
    release and then left them in workflow artifacts, which expire and need a
    GitHub login to reach — v0.6.1 shipped with zero assets. So the only
    supported install was cargo install, which compiles a rustc-driver, and
    the GitHub Action pays that on every consumer's run because it deliberately
    never caches.

    github-release now creates the release from this version's CHANGELOG.md
    section — not from generated commit subjects — and attaches one
    reconverge-<tag>-<host-triple>.tar.gz per platform with a .sha256
    beside it. float-major-tag then repoints v0 through the API, after the
    publish, so the floating tag can never name a release that did not reach
    crates.io.

    README.md and action/README.md document @v0 instead of @main. That
    was the sharper half of the problem: the ref decides which analyzer version
    the action installs, so @main re-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.

  • --cc takes the CUDA spellings. sm_86 is what nvcc -arch takes,
    what ptxas prints and what a PTX .target directive says; 86 is the
    same thing without the prefix. Both now normalize to 8.6, as do sm_120
    and 100 — a capability is one or two major digits and exactly one minor
    digit, so the last digit of a run is always the minor.

    launchbound has 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, so 8.x is still
    non-numeric and 999.9 still 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-tui and
    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 of Arc<str>,
    and unsupported_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.rs loses its
    Vec<String> helper, and the three flow tests lose a separate overflow
    assertion each.

    The comment in tests/emulation.rs warning 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_threads and settles nothing wider: blockIdx and
    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 a blockIdx guard) 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 answers barrier_scope for 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 it confirmed would 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, and cluster::cluster_idx() still decides a
    cluster-wide one. blockDim/gridDim/nsmid/gridid/nwarpid/the
    launch environment registers and the cluster's shape moved from
    BlockUniform to the new GridUniform; smid, block_rank and the
    cluster coordinates stayed block-scoped, which is what makes the check
    fire where it should.

Breaking

  • reconverge-core: Callee, BarrierSite and Analysis gain fields
    (Callee::scope, BarrierSite::{scope, cross_block_cause},
    Analysis::{local_scopes, block_guard_scope}), and CallKind gains
    ClusterUniform and GridUniform beside BlockUniform. SimtDialect
    gains barrier_scope, with a Block default, so an existing dialect
    keeps compiling and keeps the narrow answer.

  • reconverge-artifacts: the four artifact roots, ToolInfo and
    ReadError are #[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 in findings.v1 is
    additive in JSON and a major break here, because a struct with public
    fields can be built with a struct expression from anywhere. The Breaking
    entry above is that, twice, for three reconverge-core records.

    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, across cargo-reconverge,
    reconverge-tui and the driver — a downstream front-end that writes
    artifacts would pay the same, so the leaf records (Finding, SourceSpan,
    Step, Entry and the rest) stay plain deliberately. The roots do not:
    a reader deserializes one rather than assembling it, a top-level ke...

Read more

v0.6.1 — the cluster arrival is not a barrier

Choose a tag to compare

@vyncint vyncint released this 09 Sep 11:45
c717ba6

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.arrive signals and returns, barrier.cluster.wait
    blocks. 0.6.0 classified both halves as Barrier, 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 wait

    if warp::warp_id() == 0 { cluster::barrier_cluster_arrive_aligned(); }
    else                    { cluster::barrier_cluster_arrive(); }
    cluster::barrier_cluster_wait_aligned();

    produced two RC001/confirmed findings and exit 1, with a witness
    claiming 32 of 64 lanes never arrive — they arrive through the other
    instruction. Only barrier_cluster_wait* is a barrier now; the four
    arrival forms join the mbarrier family in conformance/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.sh fails when it cannot read an included source
    (#134). A module that include!s a file that is not on disk had its
    surface silently under-enumerated — cat ... 2>/dev/null hid the missing
    file, the process substitution kept set -e from seeing it, and the
    script printed PASS — 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.sh drives 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 RC001 and 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 for sync_threads and 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 a blockIdx guard — 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 public BarrierSite and 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

Choose a tag to compare

@vyncint vyncint released this 09 Sep 09:05
7dba0e2

Install

cargo install cargo-reconverge --locked
cargo reconverge setup     # installs nightly-2026-08-28 and the matching driver

Changed

  • SimtDialect gains classify_method_call(def_path, receiver) with a
    default that defers to classify_call, and the driver now hands the
    dialect the callee's Self type for trait methods. One definition path,
    cooperative_groups::ThreadGroup::sync, is five different barriers; the
    receiver decides: on a ThreadBlock, Grid or Cluster it is RC001's
    scope-wide barrier, on a WarpTile<N> or CoalescedThreads it is a
    tile-scoped contract this analysis does not model and stays Other.
    Additive for implementors (the default), and the first path-only
    classification that was structurally unable to be right.

  • FnModel gains declared_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
    divergent cluster_sync hangs every block of it. Struct literals of
    FnModel outside 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_public use is ported: Statement/Terminator spans now live under
    source_info, Rvalue::Use carries a retag flag, and the new
    Rvalue::Reborrow reads its place like Ref. 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
    a766fc26 of 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-tui and cargo-reconverge, which must stay on one version).
    No call site changed — nothing here used drag, a Style literal, the
    snapshot macro or an Error variant — 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
    KeyAction fails 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_DIR as well as CARGO_TARGET_DIR, so its state never
    lands in the workspace's own target/, 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-oxide 26754ae5) the two RC001 probes —
    the canonical divergent sync_threads and the new cooperative
    this_thread_block().sync() — ran to completion rather than hanging,
    and the RC002 probe returned 0x55555555 for 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/DisjointSlice race analysis are not this tool's, and which rules
    cover the adjacent questions; the surface gate and pin watch are described
    in conformance/README.md and docs/ARCHITECTURE.md; docs/RELEASING.md
    has the baseline-version step and the breaking label.

  • A semver gate on every pull request — cargo-semver-checks over the
    five published library crates against the last release on crates.io,
    with the release type forced to patch: 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 the breaking label, which runs the gate as a 0.x minor —
    visible, and per RELEASING.md's rule that a break is a minor release.
    baseline-version is a literal, bumped in each release PR.

  • An msrv gate building the five library crates on Rust 1.88, which
    they now declare as rust-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
    docs job's -D warnings makes it a gate.

  • Twenty-three upstream names classified that were Other — a coverage
    note, never a finding — at the previous pin. The eight redux_sync_*_f32
    collectives (min/max, _abs, _nan, and both) join RC002's masked
    surface; cooperative_groups::block_reduce and block_scan, which carry
    a sync_threads inside, join RC001's barriers, as does
    ThreadGroup::sync on a block, grid or cluster; the cluster module'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_config is a marker. The gate also follows each
    module's include!d generated files, which is where warp::warpid()
    lives: the special registers (smid, nsmid, gridid, nwarpid,
    envreg1/2) are block-uniform, warpid is a warp-level read like
    warp_id, and the raw barrier_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 in conformance/SURFACE_ALLOW, and
    scripts/check-surface.sh now runs in the conformance CI 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) and rc001_cluster_divergent_sync (a divergent cluster_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 the f32 redux family.

  • The mutation corpus's drift detector asks about warp:: calls only.
    It counted a user helper named reduce_all_forms in upstream's
    redux_f32 example as two "unclassified collectives"; qualified by the
    call path's warp segment, skipped_unclassified_collectives reads 0,
    which is what it is meant to read when the dialect and the corpus agree.
    skipped_sites_outside_kernels is 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 past conformance/PIN, when its rust-toolchain.toml
    channel differs from ours, or when a public function in its warp,
    thread, barrier, grid, cooperative_groups or cluster module is
    neither classified by the dialect nor allowlisted. The third check is
    scripts/check-surface.sh with conformance/SURFACE_ALLOW: an
    unrecognized cuda_device:: call is Other — 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,
    beside insert_mode, bells, visual_bells, mouse_modes and
    row_wrapped. The DEC 2026 bracketing the suite's wait_frame policy
    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 were sync_draw reverted the wait would time out — naming that exact
    cause — before an assertion could run.

  • crates/reconverge-tui/tests/cli.rs: the goldens through termlens-cli.
    Every file under tests/golden/ is already a saved screen in termlens'
    snapshot text format, because assert_golden writes Screen::to_string() —
    so termlens diff and termlens render open them with no conversion, and
    termlens inspect of the real binary reproduces shell-80x24.txt exactly.
    #[ignore]d, because both crates are published and a cargo test should
    not cargo install anything 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 ...

Read more

v0.5.0 — read what the tool wrote

Choose a tag to compare

@vyncint vyncint released this 05 Sep 13:56
a147f45

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 --version prints 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 -vV and --print probes included.

  • findings.v1 gains target and coverage, additively. target is
    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. coverage is the
    run's own tally of what the engine could not read.

  • witness.v1 documents a multi-warp replay. lanes is now whole warps
    up to 128 rather than const: 32, initial_lane_states has no upper
    bound, and lane_changes[].lane reaches 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 a warp_op, the lanes present are exactly the
    set bits of warp_op.active.

  • Three new gates. scripts/check-schemas.sh validates fixtures/ and
    what an end-to-end check emits against schemas/ (round-tripping
    through serde is not validating: it tolerates anything additive and never
    sees a const). scripts/record-fixtures.sh regenerates the witness
    fixtures from a real run and diffs them, so the API tests test the
    producer. scripts/check-plurals.sh fails 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 in just ci.

Changed

  • JSON mode's help names the filters it ignores. --strict and
    --show-suppressed affect 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's statement is 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
    stamped tool.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 real check; reconverged-clean.json is the one that
    cannot be (a witness is written only for a confirmed finding) and
    fixtures/README.md says 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 in asm! has no
    finding to hang one on, and --strict exited 0 over it with a clean bill
    of health. The tally now rides in findings.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. --strict and
    --show-suppressed affect 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
    bare cargo check, so cargo's own package selection applied while the
    report and the exit code came from every member cargo metadata lists.
    Run the gate from inside a member directory — which the action's own
    working-directory input exists to do — or add a default-members line,
    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 --workspace now, 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. check read a
    document out of target/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. A findings.v99 written 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. schema exists 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 future baseline.v2 whose
    entries mean something else would be read with v1 semantics, silently, in
    CI. All four readers now go through one deserialize_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_WRAPPER and cargo does not notice a same-path wrapper
    whose contents changed, so cargo install reconverge-driver over 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 the cc-marker beside --cc. Reported
    in #109.

  • A package with a lib and a bin emits two documents that name their
    targets.
    Both carried the same crate, and the driver's own comment
    told consumers to key on it — so a dictionary keyed by crate keeps
    whichever read_dir handed 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.

  • check into a reader that closes early is not a crash. Rust sets
    SIGPIPE to SIG_IGN, so println! 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 -40 is 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 real ESC bytes 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 below 0x20 reaches 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.

  • --help on 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 --help is still the missing value it was.
    Reported in #74.

  • watch keeps 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-suppressed in 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 has relatedLocations for 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. --sarif is 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 ...

Read more

v0.4.0 — a gate that could not read a number

Choose a tag to compare

@vyncint vyncint released this 26 Aug 11:27
50f638c

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_usize refused 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 a deny finding 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 80 printed 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 --baseline does. 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 TUI

reconverge-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

Choose a tag to compare

@vyncint vyncint released this 22 Aug 04:00
2c2ace7

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/l previously 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 that cargo reconverge finds it on PATH, which a --version call would not show. Both run weekly as well as at release.

Install

cargo install cargo-reconverge
cargo reconverge setup

v0.1.12 — declared-block witness replay

Choose a tag to compare

@vyncint vyncint released this 18 Aug 12:15
af63eed

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_z are 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 setup

v0.1.11 — unreachable-barrier tier documentation

Choose a tag to compare

@vyncint vyncint released this 18 Aug 11:50
43780b5

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 setup

v0.1.10 — lane-environment gap documentation

Choose a tag to compare

@vyncint vyncint released this 18 Aug 11:47
cfd0376

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