Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,34 @@ change measured timings are marked `bench:`.

## [Unreleased]

## [2.2.1] - 2026-09-10

### Fixed

- **Every relative link in the README was a 404 on all eleven crates.io
pages.** All eleven crates set `readme = "../../README.md"`, so this file
ships as each one's readme — and crates.io rewrites a *relative* link
against the crate's directory in the repository rather than the repository
root. `docs/SAFETY.md` was served as
`…/blob/HEAD/crates/launchbound-space/docs/SAFETY.md`, which does not
exist, and the same eight links were dead on every crate: `docs/SAFETY.md`,
`docs/ARCHITECTURE.md`, `docs/LIMITATIONS.md` (twice, once with an
anchor), the Action directory, `rust-toolchain.toml` and both licences.

Nothing here could see it. The same file renders correctly on GitHub,
where it really is at the root, so the only broken surface was the one
every reader arriving from crates.io meets first — and `docs/SAFETY.md`,
the document the README calls "the product", was among the dead.

The links are absolute now. `scripts/check-readme-links.sh` (in `just ci`)
refuses a relative one, checks every target still exists in the checkout,
and checks `blob` versus `tree` — GitHub *redirects* a `blob` URL for a
directory rather than failing, so a checker that only reads status codes
calls it fine.

Reaches future releases only: crates.io renders each version's readme as
published, so the 2.2.0 pages stay broken.

## [2.2.0] - 2026-09-09

The cuda-oxide pin was 133 commits and one toolchain behind, and the watch
Expand Down
22 changes: 11 additions & 11 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 9 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ members = [
exclude = ["corpus"]

[workspace.package]
version = "2.2.0"
version = "2.2.1"
edition = "2024"
# MSRV for crates that do not require the pinned nightly (CONTRIBUTING.md);
# set by ratatui 0.30. The analysis and compile paths require
Expand All @@ -38,14 +38,14 @@ categories = ["development-tools", "development-tools::profiling"]
# published crate is compatible with a release it was never built against --
# harmless for a path build, wrong as a record. They sat at 2.0.0 through the
# whole 2.1.0 line; `just versions` (and a test) now refuse the drift.
launchbound-space = { path = "crates/launchbound-space", version = "2.2.0" }
launchbound-prune = { path = "crates/launchbound-prune", version = "2.2.0" }
launchbound-build = { path = "crates/launchbound-build", version = "2.2.0" }
launchbound-bench = { path = "crates/launchbound-bench", version = "2.2.0" }
launchbound-report = { path = "crates/launchbound-report", version = "2.2.0" }
launchbound-search = { path = "crates/launchbound-search", version = "2.2.0" }
launchbound-model = { path = "crates/launchbound-model", version = "2.2.0" }
launchbound-metal = { path = "crates/launchbound-metal", version = "2.2.0" }
launchbound-space = { path = "crates/launchbound-space", version = "2.2.1" }
launchbound-prune = { path = "crates/launchbound-prune", version = "2.2.1" }
launchbound-build = { path = "crates/launchbound-build", version = "2.2.1" }
launchbound-bench = { path = "crates/launchbound-bench", version = "2.2.1" }
launchbound-report = { path = "crates/launchbound-report", version = "2.2.1" }
launchbound-search = { path = "crates/launchbound-search", version = "2.2.1" }
launchbound-model = { path = "crates/launchbound-model", version = "2.2.1" }
launchbound-metal = { path = "crates/launchbound-metal", version = "2.2.1" }
anyhow = "1"
clap = { version = "4", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
Expand Down
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# launchbound

[![license](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue)](LICENSE-MIT)
[![license](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue)](https://github.com/vyncint/launchbound/blob/main/LICENSE-MIT)
[![crates.io](https://img.shields.io/crates/v/launchbound-cli?label=crates.io&color=orange)](https://crates.io/crates/launchbound-cli)
[![toolchain](https://img.shields.io/badge/toolchain-nightly--2026--08--28-orange)](rust-toolchain.toml)
[![GPU](https://img.shields.io/badge/GPU-not%20required-brightgreen)](docs/ARCHITECTURE.md)
[![toolchain](https://img.shields.io/badge/toolchain-nightly--2026--08--28-orange)](https://github.com/vyncint/launchbound/blob/main/rust-toolchain.toml)
[![GPU](https://img.shields.io/badge/GPU-not%20required-brightgreen)](https://github.com/vyncint/launchbound/blob/main/docs/ARCHITECTURE.md)
[![CI](https://img.shields.io/github/actions/workflow/status/vyncint/launchbound/ci.yml?label=CI)](https://github.com/vyncint/launchbound/actions/workflows/ci.yml)

A convergence-safe autotuner for Rust GPU kernels.
Expand All @@ -15,7 +15,7 @@ NVIDIA it measures on real silicon. On Apple Silicon it measures on Metal.
With no GPU at all it falls back to an analytical model — and says so.

> Every design gate shipped with a measured result; the honest edges live
> in [docs/LIMITATIONS.md](docs/LIMITATIONS.md). Read that before trusting
> in [docs/LIMITATIONS.md](https://github.com/vyncint/launchbound/blob/main/docs/LIMITATIONS.md). Read that before trusting
> a result.

## Why this tool exists
Expand Down Expand Up @@ -80,7 +80,7 @@ either. The one `.maxntid` relationship the corpus does enforce
(`block_x <= lb_max` in `stencil-1d`) holds because the kernel author wrote
it as a constraint, not because launchbound knows what `.maxntid` means. A
reader arriving from cuda-oxide's docs will assume otherwise; see
[docs/LIMITATIONS.md](docs/LIMITATIONS.md#launch_bounds-and-registers-are-not-validated).
[docs/LIMITATIONS.md](https://github.com/vyncint/launchbound/blob/main/docs/LIMITATIONS.md#launch_bounds-and-registers-are-not-validated).

## The pipeline

Expand Down Expand Up @@ -113,7 +113,7 @@ For each candidate configuration at its own launch shape, `launchbound` runs

A `reconverge` tool error (exit 2) is a hard stop for that candidate, never a
pass by omission. The full rule, including why `--strict` is mandatory, lives
in [docs/SAFETY.md](docs/SAFETY.md) — that document is the product. A rejected
in [docs/SAFETY.md](https://github.com/vyncint/launchbound/blob/main/docs/SAFETY.md) — that document is the product. A rejected
configuration is a feature, not a failure: the headline output includes every
configuration that was *faster and refused*, with the rule ID and source span.

Expand Down Expand Up @@ -166,7 +166,7 @@ shape make a barrier or a collective non-convergent, and does the static
shared memory fit — and its verdict line says so. It has **no view of
instruction availability**: whether the device code can be lowered for that
part at all is `needs_cc` in `kernel.toml`, the author's claim, taken on
trust ([docs/LIMITATIONS.md](docs/LIMITATIONS.md)).
trust ([docs/LIMITATIONS.md](https://github.com/vyncint/launchbound/blob/main/docs/LIMITATIONS.md)).

`apply` re-verifies what it emits through the gate before printing anything.
`--no-verify` emits without it — for a machine that has the run directory
Expand All @@ -189,7 +189,7 @@ missing reason is a usage error, not a warning. It is never the default, and

`prune` needing no GPU is the reason it exists as its own verb: it is the only
part of the pipeline a developer on a laptop can run, and it is the part that
finds the bugs. It also ships as a [GitHub Action](action/) —
finds the bugs. It also ships as a [GitHub Action](https://github.com/vyncint/launchbound/tree/main/action) —
`uses: vyncint/launchbound/action@v2` puts the gate in your CI.

## Compared to the neighbours
Expand All @@ -214,7 +214,7 @@ That report is re-runnable: `launchbound report <run> --rejected`.

## Limitations

The honest list lives in [docs/LIMITATIONS.md](docs/LIMITATIONS.md) — read
The honest list lives in [docs/LIMITATIONS.md](https://github.com/vyncint/launchbound/blob/main/docs/LIMITATIONS.md) — read
it before trusting a result. Highlights: a clean gate is **not a proof of
correctness** (reconverge's documented limits are inherited wholesale, and
the launch-shape classifier recognizes the measured `warp_id()` family);
Expand All @@ -228,7 +228,7 @@ or not at all.

## License

Dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE), at
Dual-licensed under [MIT](https://github.com/vyncint/launchbound/blob/main/LICENSE-MIT) or [Apache-2.0](https://github.com/vyncint/launchbound/blob/main/LICENSE-APACHE), at
your option. Unless you explicitly state otherwise, any contribution
intentionally submitted for inclusion in the work by you, as defined in the
Apache-2.0 license, shall be dual licensed as above, without any additional
Expand Down
2 changes: 1 addition & 1 deletion action/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ inputs:
default: tool-error
version:
description: >-
Which launchbound release runs the gate, e.g. "2.2.0". The default,
Which launchbound release runs the gate, e.g. "2.2.1". The default,
`latest`, installs the newest release on crates.io each run. Pin a
number if you want the gate to change only when you say so.
required: false
Expand Down
10 changes: 9 additions & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ default: ci
# "all required jobs green" aggregator job in ci.yml — this recipe is the
# aggregator, and the `ci` job runs it verbatim on both OSes — so a new gate
# becomes required by being listed here.
ci: fmt-check clippy test docs deny schemas pins versions skill
ci: fmt-check clippy test docs deny schemas pins versions links skill

# Cargo errors on a memberless virtual workspace, so the cargo recipes no-op
# until the first crate lands in S1. `grep -c` prints 1 when packages is empty.
Expand Down Expand Up @@ -63,6 +63,14 @@ pins:
versions:
./scripts/check-versions.sh

# Every link in the README is absolute, names the right GitHub path kind,
# and points at something that exists. All eleven crates ship this file as
# their readme, and crates.io rewrites a relative link against the crate's
# directory -- so all eight were 404 on all eleven published 2.2.0 pages,
# and nothing here could see it.
links:
./scripts/check-readme-links.sh

# Golden + JSON Schema validation of report documents (S4).
schemas:
cargo test -p launchbound-report --test schema_and_golden
Expand Down
71 changes: 71 additions & 0 deletions scripts/check-readme-links.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env bash
# Every link in the packaged README must be absolute, must name the right
# GitHub path kind, and must point at something that exists here.
#
# All eleven crates set `readme = "../../README.md"`, so this file is shipped
# as each one's readme and crates.io renders it there. crates.io rewrites a
# *relative* link against the crate's directory in the repository, not the
# repository root -- so `docs/SAFETY.md` became
# `…/blob/HEAD/crates/launchbound-space/docs/SAFETY.md`, and all eight
# relative links were 404 on all eleven published 2.2.0 pages. Nothing here
# could see it: the same file renders correctly on GitHub, where it *is* at
# the root.
#
# So: absolute links only, and the path each names must exist. The
# existence check is offline and deterministic, which is the point -- it
# catches a renamed or deleted target, and a link checker that only asks
# GitHub would not, because GitHub answers 200 for a redirect.
#
# `blob` vs `tree` is checked for the same reason: `blob/main/action/` is a
# directory and GitHub 301s it to `tree/main/action`. It resolves, so a
# status-code checker calls it fine; it is still the wrong URL to publish.
#
# Portability: macOS ships bash 3.2 and BSD grep/sed.
set -euo pipefail

readme="${1:-README.md}"
root=$(cd "$(dirname "$0")/.." && pwd)
repo="https://github.com/vyncint/launchbound"
status=0

# `[text](target)` whose target is neither absolute nor a bare fragment.
relative=$(grep -oE '\]\([^)]+\)' "$readme" \
| sed -E 's/^\]\(//; s/\)$//' \
| grep -vE '^(https?:|#|mailto:)' || true)
if [ -n "$relative" ]; then
echo "$relative" | while IFS= read -r link; do
echo "README LINK: relative target \"$link\" — crates.io rewrites it against" >&2
echo " crates/<crate>/, where it does not exist. Use $repo/blob/main/$link" >&2
done
status=1
fi

checked=0
bad=0
for url in $(grep -oE "$repo/(blob|tree)/main/[^)]+" "$readme" | sort -u); do
kind=$(printf '%s' "$url" | sed -E "s|^.*/(blob\|tree)/main/.*$|\1|")
path=$(printf '%s' "$url" | sed -E "s|^.*/(blob\|tree)/main/||; s|#.*$||; s|/$||")
checked=$((checked + 1))
if [ ! -e "$root/$path" ]; then
echo "README LINK: \"$path\" is linked but not in the repository" >&2
bad=$((bad + 1))
continue
fi
# A directory is `tree`, a file is `blob`. GitHub redirects the wrong one
# rather than failing, so only this says so.
if [ -d "$root/$path" ] && [ "$kind" != tree ]; then
echo "README LINK: \"$path\" is a directory; use /tree/main/ not /blob/main/" >&2
bad=$((bad + 1))
elif [ -f "$root/$path" ] && [ "$kind" != blob ]; then
echo "README LINK: \"$path\" is a file; use /blob/main/ not /tree/main/" >&2
bad=$((bad + 1))
fi
done
if [ "$bad" -gt 0 ]; then
status=1
fi

if [ "$status" -eq 0 ]; then
echo "readme links: $checked absolute link(s), every target present and the right kind"
fi
exit "$status"
Loading