Skip to content

Use the shared cutile-rs host crates in cuda-oxide - #1102

Merged
nihalpasham merged 22 commits into
NVIDIA:mainfrom
elibol:feat/shared-host-crates
Sep 4, 2026
Merged

nihalpasham merged 22 commits into
NVIDIA:mainfrom
elibol:feat/shared-host-crates

Conversation

@elibol

@elibol elibol commented Aug 20, 2026 •

Copy link
Copy Markdown
Collaborator

What this does

This gets cuda-oxide and cutile-rs onto the same three host-crate package identities before the monorepo move, without making high-level API unification part of that move.

The shape

cutile-rs#233 temporarily carries both API families:

shared cuda-core
├── cuda_core::*          Cutile / Tile API
└── cuda_core::simt::*    cuda-oxide SIMT API

shared cuda-async
├── cuda_async::*         Cutile / Tile API
└── cuda_async::simt::*   cuda-oxide SIMT API

This PR points cuda-oxide at those shared crates:

Before                         After

cuda-oxide                     cuda-oxide
  └─ local host crates   ──►      └─ shared host crates ◄── cutile-rs

This is a namespaced compatibility bridge. It does not rewrite cuda-host or cuda-macros around the Tile APIs, and it does not decide the eventual unified API.

Most Rust edits are path changes. For example:

// before
cuda_async::device_operation::DeviceOperation

// after
cuda_async::simt::device_operation::DeviceOperation

One mechanical edit needed semantic correction: cutile_inter_kernel composes a Tile DeviceOp, so its five Tile-side imports must remain under root cuda_async. That is fixed at the current head, and the example now compiles and runs on an RTX 5090.

What is actually still missing upstream

  • Peer access: carry can_access_peer, enable_peer_access, and disable_peer_access, plus cuda-oxide's real two-GPU VMM/P2P test. The VMM implementation itself is already in #233; peer access and the cross-GPU test are the missing pieces.

  • DeviceCopy derive: preserve the existing one-import API, or agree on a compatible replacement, and carry its trybuild tests:

    use cuda_core::DeviceCopy; // imports the trait and derive today
    
    #[derive(DeviceCopy)]
    struct Pair {
        x: f32,
        y: f32,
    }

One compatibility question, not a merge gate

Primitive Rust f16 is nightly-only, so making its DeviceCopy impl an opt-in feature in a stable shared crate is reasonable. Can we confirm that this is the intended policy?

  • half::f16 remains available normally.
  • atomic_f16 and f16_stress explicitly enable the new f16 feature.
  • gemm_sol_final, the tcgen05 examples, and the matmul examples do not depend on primitive f16 or this feature.

Compatibility and release gates

  • CUDA/toolkit support: preserve cuda-oxide's CUDA 12.x/13.1 and toolkit-layout support, or explicitly agree that the shared crates now require CUDA 13.2+ and Cutile's toolkit layout.

  • Use one published oxide-artifacts crate: oxide-artifacts 0.2.1 is now published. Make both cuda-oxide and the shared cuda-core use that release. The shared cuda-core should re-export the dependency instead of copying its source into cuda_core::simt::artifacts. The copied reader understands the writer's v2 bytes today, but it is a second implementation and its public Rust types have a different identity:

    let original: oxide_artifacts::OwnedArtifactBundle = todo!();
    let _: cuda_core::simt::artifacts::OwnedArtifactBundle = original;
    // E0308 today: same fields and byte format, but different Rust types

    Keeping both projects on the same crates.io package gives the compiler and shared runtime one Rust type and one format implementation. No copied implementation, Git dependency, or transitional Cargo patch is needed.

  • Preserve the bindings C function type: ordinary calls through the shared dynamic loader work, but the generated shim currently changes the function item's Rust type from extern "C" to the Rust ABI:

    unsafe { cuda_bindings::cuInit(0) }; // works today
    
    type CuInit = unsafe extern "C" fn(u32) -> cuda_bindings::CUresult;
    let _: CuInit = cuda_bindings::cuInit;
    // E0308 today: expected extern "C" fn, found Rust fn

    Preserve bindgen's ABI when generating the public loader shims and keep this assignment as a compile-time regression test. Dynamic loading remains unchanged.

  • Scaffolds and docs: update cargo oxide new, repository/book docs, and any examples that still point at the retired local crates.

  • Release/reproducibility: publish oxide-artifacts 0.2.1 and cuda-bindings 0.3.1 first, followed by cuda-core 0.3.1 → cuda-async 0.3.1. Verify each package, pin the released versions, regenerate affected locks, and keep DCO clean.

  • Final runtime gate: run the peer/VMM P2P path on both GPUs and keep the representative SIMT, async, cooperative, cluster, and interop examples green at the final release head.

What is green now

  • Hosted CI is green, including the full 216/216 compile-only example matrix.
  • Shared cuda-core and cuda-async: 196 passed, 1 ignored on CUDA 13.3 / RTX 5090.
  • Cutile GPU script: 187 tests passed on the two RTX 5090s against #233.
  • cuda-oxide runtime checks passed: vecadd, async_vecadd, cluster, coop_groups_demo, atomic_f16, and cutile_inter_kernel.
  • nvls_all_reduce still lives in cuda-oxide, builds against the shared VMM surface, and correctly skips on these 5090s because they do not have NVSwitch multicast support.
  • cutile-rs#202 now separately provides Cutile's root VMM API and runs its vmm / vmm_multicast targets from the GPU script. That is useful shared coverage, but it is not a substitute for the missing peer/P2P test.

Merge picture

Already green
  216/216 compile cases + hosted CI + representative RTX 5090 runs
                              │
                              ▼
Close the two API gaps
  peer/P2P + DeviceCopy derive compatibility
                              │
                              ▼
Fix the two shared boundaries and update scaffolds/docs
  publish/reuse oxide-artifacts + preserve the bindings C ABI
                              │
                              ▼
Publish and pin the verified releases; DCO clean
                              │
                              ▼
                            merge

Once those items are closed, this remains a packaging move rather than a capability trade. Safety issues already present in either implementation remain separately tracked work; this migration does not claim to solve them.

@elibol elibol changed the title Build the host-side crates from cutile-rs Use cutile-rs host-side crates. Aug 20, 2026
@elibol
elibol force-pushed the feat/shared-host-crates branch from 466c97d to b62897d Compare August 20, 2026 23:46
nihalpasham pushed a commit that referenced this pull request Aug 21, 2026
…1106)

Closes #1090.

`mlir-dialects.md` states its own freshness rule:

    At catalog SHA-256 `26a9cc13` (the stamp in every `ops/generated/` file
    header), the dialect holds 570 operations across 42 modules ...
    If the header stamp no longer starts with `26a9cc13`, the counts on this
    page predate the catalog you are reading.

Nothing enforced that rule, so the rule that would expose stale counts was
itself unchecked. The page failed its own test for weeks before #1052 (560
printed against 570 in the tree), and **it is failing again right now**: the
catalog hashes to `00372dfe`, five hand-written ops and five catalog entries
after that refresh landed. Two hand audits in two batches is the pattern this
closes.

## The gate

One file hashed, one page grepped, no cargo and no network. The hash is the one
`cuda-intrinsics-gen` stamps into every generated file --
`sha256_bytes(catalog_json.as_bytes())` over the file as committed -- so a
plain `sha256sum` reproduces it, and the page prints its first eight digits.

Three checks rather than one, so each failure says which thing moved:

- The generated sources must stamp the hash `catalog.json` actually has.
  Failing here means the generated outputs are stale, and the message says to
  run `cuda-intrinsics-gen -- generate` -- not to edit the book.
- The page must print exactly one distinct stamp. It names it twice, once
  stating it and once in the rule that tells a reader what a mismatch means, so
  updating one and not the other leaves the rule contradicting the statement
  above it.
- The stamps must agree, and the failure prints both sides plus the two
  commands that re-derive the counts the stamp guards.

It refuses to report success from a check that found nothing: a page reworded
so no stamp is left fails with that stated, rather than passing vacuously. That
path needed the `|| true` on the capture rather than on a later pipeline --
under `pipefail` a grep matching nothing aborted the script before the
diagnostic, turning a reworded page into a silent non-zero exit.

## The refresh that makes it pass

Five numbers and the stamp, all re-derived from the tree:

| claim | was | now |
|---|--:|--:|
| catalog stamp (x2) | `26a9cc13` | `00372dfe` |
| total operations | 570 | 575 |
| hand-written operations | 21 | 26 |
| hand-written `wgmma` row | 9 | 14 |
| catalog entries | 1010 | 1015 |

Generated stays 549 across 35 modules and every area row is unchanged, because
#1085's five new catalog entries are aliases onto existing structural ops --
exactly what the page's "several intrinsics can share one structural op"
sentence predicts. The +5 hand-written are all `wgmma`, from #1075, #1077 and
#1079.

The `wgmma` row's description moves with its count, as in #1052: it said "the
m64n64k16 bf16/f16 shapes", and the module now holds four shape/type pairs --
`m64n64k16` bf16 and f16, `m64n128k16` bf16 (#1079), `m64n64k8` tf32 (#1077).

## Verification

- Four mutations, each failing with its own message: a stale stamp, only one of
  the two stamps updated, the page reworded so none is found, and an edited
  `catalog.json`. The healthy tree passes.
- Every count re-checked against the tree: 26 + 549 = 575, both tables sum to
  their stated totals (7 rows to 26, 11 rows to 549), and each still lists
  every module on disk.
- All eleven guard scripts pass, `check-spdx-headers.sh` accepts the new file,
  `cuda-intrinsics-gen -- check` reports outputs current, the workflow parses
  with all nine jobs on `ubuntu-latest`, and the book builds clean under
  `sphinx-build -W`.

`just check-guards` should list this script too, one line beside the other ten.
Its recipe is being restructured in #1102, so that line is left for a follow-up
rather than conflicting; no guard enforces that list, so CI is unaffected
either way.

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
@nihalpasham nihalpasham changed the title Use cutile-rs host-side crates. Use the shared cutile-rs host crates in cuda-oxide Aug 21, 2026
@nihalpasham

nihalpasham commented Aug 21, 2026 •

Copy link
Copy Markdown
Collaborator

I updated the PR description to be clearer and more precise about what is already working and what is still needed.

I also fixed the one failing example. cutile_inter_kernel was accidentally pointed at the SIMT async surface even though it composes a Tile DeviceOp; those five imports are back on root cuda_async. The current head is green in hosted CI, including all 216 compile cases, and the example compiles and runs on an RTX 5090.

I published oxide-artifacts 0.2.1 from cuda-oxide. Could you update #233 so the shared cuda-core depends on and re-exports that crate instead of carrying a copied simt::artifacts implementation?

The remaining upstream items I see are:

  • add the peer-access wrappers and the existing two-GPU vmm_p2p test;
  • preserve the DeviceCopy derive re-export through a publishable solution and carry its trybuild tests;
  • preserve the bindings extern "C" function type and add the function-pointer assignment regression;
  • explicitly agree whether the shared releases preserve cuda-oxide's CUDA 12.x/13.1 and toolkit-layout support, or intentionally move to CUDA 13.2+.

The VMM implementation itself is already present in #233. I am also treating primitive f16 being opt-in as policy for nightly additions.

Once those upstream pieces are in, I will replace the temporary branch dependencies here with the published 0.3.1 crates, regenerate every affected lockfile, update cargo oxide new and the remaining docs/examples, and rerun the final single-/two-GPU matrix. We should also clean up the missing DCO trailers before release.

If we close those items without dropping existing cuda-oxide capability, I think this is in good shape to merge.

@elibol

elibol commented Aug 21, 2026 •

Copy link
Copy Markdown
Collaborator Author

Thanks for the fixes and for publishing oxide-artifacts. The interop example is looking good.

Follow-ups now on #233:

  • cuda-core depends on oxide-artifacts 0.2.1 and re-exports it as simt::artifacts.
  • Peer wrappers are in, along with the two-GPU vmm_p2p and vmm_multicast tests. They self-skip on single-device machines.
  • #[derive(DeviceCopy)] moved to a new cuda-core-derive crate so we can publish it. It's re-exported next to the trait, so use cuda_core::DeviceCopy; brings both into scope. The trybuild suite came with it.
  • The bindings' loaded driver table is now public. Its fields are the raw unsafe extern "C" fn pointers, so the function-pointer type is preserved without changing the flat wrapper API. Added a regression test for the assignment.

Re: the CUDA floor: the shared host-side crates keep cuda-oxide's 12.x support using your probe approach. The bindings floor drops to 12.0, the elapsed-time helper goes back to the v1/v2 dispatch under the probed cfg, multicast goes back to a header probe (your cuda-core build script, verbatim), and CI here goes back to 13.1.1. cutile enforces its own 13.2 floor in cutile-compiler at tool discovery, so SIMT users below that never see it. We added a 12.x compile lane for the shared crates so we don't silently break 12.x.

One thing we should settle now rather than later: the cuTe DSL will have similar versioning constraints to cuTile (it has a tileiras-like binary for compiling cute dsl). I think the pattern should be per-stack floors enforced at each stack's tool boundary, with the shared crates at the lowest common floor. Does that match what you expect?

I'll clean up the DCO trailers before release.

@elibol

elibol commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator Author

Correction on the floor: 12.0 is lower than what the current wrappers support. The 12.x lane found what the shim calls directly: cuCtxSetFlags (12.1), cuMemAdvise_v2 and cuMemPrefetchAsync_v2 (12.2), cuEventElapsedTime_v2 (12.8), and cuLaunchHostFunc_v2 plus the CUhostTaskSyncMode enum (13.x only). So the floor is 12.8 for now. The 13.x-only host-function launch goes through a probed helper in cuda-bindings. On older toolkits it falls back to the blocking default instead of spin-wait.

Going below 12.8 will require some work... We'd need to route the elapsed-time call through the existing helper and add fallbacks for the three 12.1/12.2 symbols. The commits list them.

Will cuda-oxide require CUDA versions < 12.8?

vyncint added a commit to vyncint/cuda-oxide that referenced this pull request Aug 23, 2026
Every guard under `scripts/` is wired twice: a step in a workflow, and a line
in the Justfile's `check-guards` recipe. The Justfile states that as a contract
-- "`check-guards` covers the status-guard, naming-guard and cargo-deny
workflows in full" -- and nothing enforced it. `check-book-catalog-stamp.sh`
landed in NVIDIA#1106 wired into `status-guard.yml` and never added to
`check-guards`, so twelve of the thirteen guards ran locally and "in full" was
false.

The first version of this check searched both files for each script's name.
NVIDIA#1133's review was right that a text search is not enforcement evidence, and
gave the counterexamples: the same text inside a `run:` block that only echoes
it, a step behind `if: false`, or a reusable workflow nothing calls. All three
left the search green while no pull request ran the guard. This reads structure.

**Workflows are a graph.** The YAML is parsed, and reachability computed:
a workflow is an entry point only if it triggers on `pull_request` or
`pull_request_target`; a reusable workflow counts only if a reachable job
`uses:` it, resolved to a fixpoint; a job or step proven `if: false` is dropped;
and anything that may `continue-on-error` is not credited, because a swallowed
failure is not enforcement.

**A `run:` script is parsed into commands**, and a guard counts only in command
position. `echo "bash scripts/x.sh"` runs `echo`. Leading `VAR=value`
assignments and wrappers (`bash`, `sh`, `env`, `.`, `source`, `exec`, `time`,
`sudo`) are stepped over, comment lines are dropped, and `&&`, `||`, `;`, `|`
split a line so a guard buried mid-pipeline still counts.

**The Justfile gets the same treatment**, as the review asked. Recipes and their
dependencies are a graph, walked from `check`, so a guard moved into a recipe
`check` does not depend on stops counting. A body line prefixed with `-` is
just's `continue-on-error` and does not enforce. The same command-position rule
applies, so a recipe that echoes an invocation is not running it.

Reachability is conservative in one direction only, on purpose. An `if:` that is
not literally `false` is treated as reachable, because
`github.event_name == 'pull_request'` cannot be evaluated statically -- the
guard never calls a live step dead. It is strict the other way: a step that may
be skipped, or may swallow its failure, is not coverage.

One subtlety worth recording, since it decides whether any of this works: YAML
1.1 reads a bare `on` as the boolean true, so PyYAML hands back `{True: ...}`
for `on:` written unquoted, which is how all eleven workflows here write it.
`document.get("on")` is `None` on every one of them. Both keys are looked up,
and a self-test refuses if no workflow appears to trigger on `pull_request` --
the shape that would otherwise make the reachable set silently empty.

Verified against every case, nine that must fail and seven that must refuse:

| the guard is defeated by | caught as |
|---|---|
| the `run:` text only echoed | executed by `just check` but by no workflow |
| a step behind `if: false` | same |
| only in a reusable workflow nothing calls | same |
| a step with `continue-on-error: true` | same |
| a job with `continue-on-error: true` | same |
| moved to a workflow with no `pull_request` trigger | same |
| a Justfile line prefixed with `-` | executed by a workflow but not by `just check` |
| moved to a recipe `check` does not reach | same |
| the Justfile echoing it | same |

| broken input | refusal |
|---|---|
| nothing triggers on `pull_request` | parse self-test failed, naming the `on:` case |
| the `check` recipe renamed | parse self-test failed |
| the `check-guards` recipe renamed | parse self-test failed |
| `check` no longer depends on `check-guards` | not reachable from `check` |
| a job calls a workflow without `workflow_call` | it would never run |
| a job calls a workflow that does not exist | named |
| a workflow file is not valid YAML | named, with the parser error |
| no PyYAML available | refuses with the install command |

Unmutated: "OK: all 14 guard scripts are executed by a pull-request workflow
(11 reachable of 11) and by `just check` (8 reachable recipes)", exit 1 on
failure, all fourteen guards still pass individually, 0.09s, no cargo, no
network, no toolchain.

The interpreter is probed rather than assumed: the first `python3` on PATH is
not always the one with PyYAML -- where Homebrew shadows the system Python it is
not -- so `python3`, `/usr/bin/python3` and `python` are tried in turn and a
missing parser is a refusal with the install command, never a pass.

Note for review: NVIDIA#1102 also edits the Justfile, in the `test-cuda` recipe around
line 105. The `check-guards` change here is around line 235, so they do not
overlap textually.

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
vyncint added a commit to vyncint/cuda-oxide that referenced this pull request Aug 24, 2026
Every guard under `scripts/` is wired twice: a step in a workflow, and a line
in the Justfile's `check-guards` recipe. The Justfile states that as a contract
-- "`check-guards` covers the status-guard, naming-guard and cargo-deny
workflows in full" -- and nothing enforced it. `check-book-catalog-stamp.sh`
landed in NVIDIA#1106 wired into `status-guard.yml` and never added to
`check-guards`, so twelve of the thirteen guards ran locally and "in full" was
false.

## Before -> after

The first version of this check searched both files for each script's name.
NVIDIA#1133's review was right that a text search is not enforcement evidence:

    before:  grep finds "check-book-catalog-stamp.sh" in status-guard.yml
             -> OK: all 14 guard scripts run in both places

    after:   the step is `echo "bash scripts/check-book-catalog-stamp.sh"`
             -> error: these guards are executed by `just check` but by no
                pull-request workflow: check-book-catalog-stamp.sh

Same for a step behind `if: false`, a step or job with `continue-on-error`, a
guard in a reusable workflow nothing calls, a guard moved to a workflow with no
`pull_request` trigger, a Justfile line prefixed with `-`, a recipe `check`
never reaches, and a Justfile line that echoes the invocation. Nine ways to
defeat the old check; all nine now fail.

## How

Workflow YAML is parsed and reachability computed as a graph: an entry point
must trigger on `pull_request`/`pull_request_target`; a reusable workflow counts
only if a reachable job `uses:` it, to a fixpoint; a job or step proven
`if: false` is dropped; anything that may `continue-on-error` is not credited.
A `run:` script is split into commands and a guard counts only in command
position, stepping over `VAR=value` prefixes and the `bash`/`sh`/`env`/`.`/
`source`/`exec`/`time`/`sudo` wrappers. The Justfile gets the same treatment:
recipes and dependencies are a graph walked from `check`, and a `-` prefix is
just's `continue-on-error`.

Conservative in one direction only, on purpose: an `if:` that is not literally
`false` is treated as reachable, because `github.event_name == 'pull_request'`
cannot be evaluated statically. The guard never calls a live step dead. It is
strict the other way -- a step that may be skipped, or may swallow its failure,
is not coverage.

## No third-party parser

The YAML is read by a strict-subset reader in the script rather than PyYAML.
PyYAML is **not** in the `actions/runner-images` preinstalled manifest, so
depending on it would risk the job failing on merge, and no other guard in this
family needs a third-party package -- on a repo that already works around
macOS's bash 3.2 and BSD grep for the same reason.

The reader covers exactly what these files use: block mappings and sequences,
plain and quoted scalars, literal `|` and folded `>` block scalars with chomping
indicators, flow sequences, comments, one leading document marker. Anything else
-- a tab, an anchor, an alias, a tag, a flow mapping, a second document -- is a
refusal, never a guess. That is what separates it from the text search: it
cannot silently misread, only decline to read.

Folding is implemented rather than approximated because `book.yml` folds one
command across two lines; treating it as literal would invent a second command
whose first token is `cuda-oxide-book`.

Where PyYAML *is* installed the script uses it as an **oracle**: it compares its
own parse against PyYAML's on every workflow, over exactly the fields the walk
reads, and refuses on any disagreement. Locally that reports "agrees with
PyYAML on all 11 workflows". So the reader is checked, not trusted, without
being required.

One subtlety the whole walk depends on: YAML 1.1 reads a bare `on` as the
boolean true, so `on:` written unquoted is the key `True`, and
`document.get("on")` is `None` on all eleven files. Both keys are looked up.

## Tests

The nine defeats above were originally verified by hand-mutating this
repository, which is neither repeatable nor visible in CI. They are now
fixtures the guard asserts about itself on every run, alongside the controls
that catch a rule grown too strict (a live step, a guard mid-pipeline, a called
reusable workflow, `@`-suppressed Justfile lines) and four reader refusals.

Each was confirmed to bite by breaking the implementation it guards:

| implementation broken | self-test that failed |
|---|---|
| step `if`/`continue-on-error` check removed | a step behind `if: false` was counted |
| job `continue-on-error` check removed | a job with continue-on-error was counted |
| command position -> any token | the invocation only echoed was counted |
| reusable-workflow fixpoint removed | a called reusable workflow was not counted |
| Justfile `-` skip removed | a `-` prefixed script path was counted |
| Justfile reachability walk removed | a recipe `check` does not reach was counted |
| reader's tab refusal removed | the reader accepted a tab |
| reader's flow-mapping refusal removed | the reader accepted a flow mapping |

Unmutated: all fourteen guards pass individually, 0.02s, needs only python3 --
no cargo, no network, no third-party package.

Note for review: at 835 lines this is the largest guard in the family by some
margin, and that is what structural parsing costs. Say so if you would rather
have the dependency and a shorter script. NVIDIA#1102 also edits the Justfile, in the
`test-cuda` recipe around line 105; the `check-guards` change here is around
line 235, so they do not overlap.

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
@nihalpasham

nihalpasham commented Aug 24, 2026 •

Copy link
Copy Markdown
Collaborator

Thanks, I went through the current #233 head again.

CUDA 12.8+ is fine for cuda-oxide; we do not need support below 12.8. The oxide-artifacts, peer/P2P, and DeviceCopy derive changes look good.

The only remaining changes we'd need on #233 are:

  • CUDA toolkit discovery: Please copy cuda-oxide’s existing lookup logic and tests into the shared cuda-bindings. We have users with custom, Jetson/Grace, cross-build, and targets/<arch> toolkit layouts. Those work in cuda-oxide today, but Add cuda-oxide host surface to host-side crates NVlabs/cutile-rs#233 only accepts <toolkit>/include/cuda.h, so these users would fail during the build.

  • Shared-crate tests: The current cargo test --no-run step compiles the copied tests but does not execute their assertions or trybuild cases. Can we please wire the CPU-safe cuda-core/cuda-async tests into the CPU test path and the GPU-dependent tests into the GPU path, including simt_vmm_p2p and simt_vmm_multicast. This is ordinary Cargo testing; it does not involve cargo oxide. I will still run the final integrated single-/two-GPU matrix from Use the shared cutile-rs host crates in cuda-oxide #1102.

Could you also refresh the #233 description? It still describes peer/VMM as omitted, oxide-artifacts as vendored, and the derive as remaining in cuda-oxide.

Once those items and the planned DCO cleanup are done, I think #233 is ready to be the upstream release piece. I’ll handle the dependency switch, locks, local artifact identity, scaffolds/docs, and final integration here after 0.3.1 is published.

@elibol

elibol commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator Author

@nihalpasham any thoughts on how to address the CodeQL alerts?

@nihalpasham

Copy link
Copy Markdown
Collaborator

@nihalpasham any thoughts on how to address the CodeQL alerts?

@elibol - The 7 new alerts are pre-existing main-branch alerts (all 13 are open on main, created by the Aug 28 scan) that CodeQL misattributed to this PR because the diff was large.

They're all rust/access-invalid-pointer on #[cuda_module] kernel code, i.e. device kernels dereferencing GPU buffer pointers by design. We'll dismiss them as false positives / exclude example kernels from the CodeQL config on our side; nothing to change in this PR, and CodeQL isn't a required check.

@elibol
elibol force-pushed the feat/shared-host-crates branch from bf81c4a to 9cf43b9 Compare September 2, 2026 05:31
@nihalpasham nihalpasham added refactor Internal restructuring with no behavior change breaking-change Changes a public API, import path, or dependency line that downstream users must update host-apis Host-side runtime APIs (cuda-host, cuda-core, cuda-async) cuda-bindings Raw FFI bindings to the CUDA driver/runtime device-apis User-facing device-side and kernel-authoring APIs (cuda-device, cuda-macros) labels Sep 3, 2026
elibol and others added 9 commits September 3, 2026 18:58
Main's NVIDIA#1198 gates CI on sync-example-locks.sh --check; align the
git-pinned workspaces the way its own docs prescribe (--align never
touches the git pins). 1920 pins moved, 221 locks agree.
cuda-bindings was never given the standalone [workspace] table its two
siblings got at retirement, and main's stricter license guard now walks
every tracked manifest. Detach it the same way, commit the three
standalone locks the guard's --locked metadata needs, and name all
three retired roots first-party so their declared dependencies stay in
the inventory.
The NVIDIA#233 branch gained the extern "C" wrapper ABI and the cuInit
coercion regression nihalpasham asked for; manifests are unchanged, so
this is a pure pin substitution across all 222 locks.
reborrow, sparse_mma_ordered_float, and shared_stage_select arrived on
main with local path deps and root LaunchConfig imports; convert them
the established way and seed their locks from vecadd's per the sync
script's doc, which lands them aligned on the first try (224 locks
agree). This also clears the clippy and cargo-deny lanes, which were
failing on the unconverted expansion and the stale --locked resolve.
cutile-rs#233 was squash-merged into cutile-rs main as a2c1ea4 and its
feature branch deleted, so the git dependencies on
feat/cuda-oxide-host-support no longer resolve. Point the 222 manifests
that declare cuda-bindings, cuda-core, and cuda-async at branch main and
repin the 225 lockfiles that carry the four shared packages
(cuda-bindings, cuda-core, cuda-core-derive, cuda-async, all 0.3.0) at
a2c1ea4bb4ae804f573a879785c96f870a4e1b84.

The shared crates' manifests are unchanged since the previous pin, so
the lock rewrite is a pure source substitution; every one of the 225
workspaces resolves with --locked. The only change in the four crates
between aaf1364 and main is the host-side toolkit floor moving from
CUDA 12.8 to 13.0 (MIN_CUDA_VERSION 13000 in cuda-bindings/build.rs);
CI installs 13.1.1 and already clears it.

From here cuda-oxide tracks cutile-rs main; advancing the pin is a
deliberate cargo update, reviewed like any dependency bump.

Signed-off-by: Melih Elibol <elibol@users.noreply.github.com>
Point the 222 manifests that declare cuda-bindings, cuda-core, and
cuda-async at the v0.3.1 tag instead of branch main, and re-resolve the
225 lockfiles that carry the four shared packages with cargo update, so
they now record cuda-bindings, cuda-core, cuda-core-derive, and cuda-async
0.3.1 at the tag's current target, 9f68be2d0aff0882ec329a453ddc756ad6e4f9e7.
Every workspace resolves with --locked and the example locks still agree
on one version per crate.

Referencing the tag rather than a commit means a re-tag before the final
release costs one cargo update pass and no manifest edits.

Between a2c1ea4 and the tag the shared crates gained toolkit-discovery
parity (CUDA_HOME fallback, targets/<arch>/include lookup,
CUDA_TOOLKIT_TARGET_DIR), executed host-crate tests, and the host runtime
audit follow-ups; DType became an unsafe trait and several driver wrappers
now return Result. None of those surfaces are used by cuda-oxide's own
code, and the simt:: modules are unchanged, so nothing here needed
adapting.

Signed-off-by: Melih Elibol <elibol@users.noreply.github.com>
… copy from

The host runtime now comes from the cutile-rs crates, whose cuda-oxide
API lives under cuda_core::simt and cuda_async::simt. Every snippet a
user copies has to say so: the README, the cuda-host README, the book
pages, two example READMEs, and the two cargo oxide new templates.

The scaffold also pointed cuda-core, cuda-async, and cuda-bindings at
this repository's git URL. With the local copies retired that either
resolves to nothing or to a stale copy on an old revision, so the
templates now take the crates.io release (SHARED_HOST_CRATES_VERSION)
and a test checks no git pin comes back. cuda-bindings leaves the
async template: nothing in it names cuda_bindings.

Book links that pointed at crates/cuda-core/{src,tests} now point at
the simt files in cutile-rs.

Signed-off-by: Nihal Pasham <nihalp@nvidia.com>
… the shared crates

The switch to the cutile-rs crates left the old crates/cuda-bindings,
crates/cuda-core, and crates/cuda-async on disk as standalone packages
"so the example workspaces keep building unchanged". Nothing built them:
every example already took the git pin, no CI lane tested them, and
their only effect was to make `cargo oxide new` resolve `cuda-core` from
this repository's git URL to a stale 0.2.1 copy. They are gone, together
with the dead `#[derive(DeviceCopy)]` in cuda-macros (cuda-core now
re-exports cuda-core-derive's), the three retired roots in the license
inventory guard, the CODEOWNERS rule for the moved crate, and the
comments that pointed at the old paths (root Cargo.toml, doctor.rs,
spine_kernel_ptx.rs, docs.yml).

The shared cuda-bindings loads libcuda at run time through libloading
instead of linking it, so the libcuda.so.1 stub shadowing in
unit-tests.yml, docs.yml, the Justfile, and CONTRIBUTING was dead
plumbing; it is removed and the `--exclude cuda-bindings` doctest
exclusions with it. bindgen now also reads curand.h, which the flake
provides and the docs name.

cutile_inter_kernel leaves the license-policy exemption (NVIDIA#953): its git
source is allowed and every cutile-rs crate is Apache-2.0, so it is
judged like every other example. dependency-licenses.csv describes the
crates as resolved (0.3.1 from cutile-rs) and gains cuda-core-derive and
dashmap rows.

Docs: the CUDA floor is 13.0 with an R580+ driver (README, installation,
building-from-source, example READMEs), the remaining root-path snippets
move to the simt paths, the `f16` feature is explained where it is
enabled, the cuda-host re-export of cuda_async says which surface is
which, and the docs.rs links for LaunchConfig point at the simt page.
Seven trybuild fixtures get the line-neutral formatting fixes the path
rewrite skipped.

Signed-off-by: Nihal Pasham <nihalp@nvidia.com>
…arity

Two things the crate switch showed nothing was checking:

- Some forty book, README, and scaffold lines kept spelling the old root
  paths (cuda_core::LaunchConfig, cuda_async::device_operation::...),
  which on the shared crates either do not resolve or bind cutile's
  Tile-side types. No lane compiles those snippets.
  check-host-api-paths.sh greps them for the root spellings that are
  wrong on the shared crates; it fails on the pre-fix tree at 50 sites
  and passes now.

- The shared-crate release is named once in the root workspace and
  copied into 221 example manifests and the `cargo oxide new` templates.
  check-shared-crate-pin.sh requires every copy to agree with the root
  on version and form (git tag or crates.io), so the crates.io repin
  cannot leave a straggler and the scaffold cannot drift.

Both run in the status-guard workflow and in `just check-guards`.

Signed-off-by: Nihal Pasham <nihalp@nvidia.com>
@nihalpasham
nihalpasham force-pushed the feat/shared-host-crates branch from 5204f10 to ecf70b4 Compare September 3, 2026 13:47
@nihalpasham nihalpasham added cargo-oxide The `cargo oxide` subcommand and build automation documentation Improvements or additions to documentation labels Sep 3, 2026
…e crate is the publish source

The backend wrote artifact bundles with the in-tree crates/oxide-artifacts
(a path dependency) while the shared cuda-core from cutile-rs reads them
through crates.io oxide-artifacts 0.2.1. Same version, same code, but two
crates to rustc: the root lock carried both, and nothing tied the two
versions together. It worked only because just bytes cross the boundary.

The root workspace and the backend workspace now take oxide-artifacts
from crates.io like every other consumer, so writer and reader are one
crate. crates/oxide-artifacts stays a workspace member as the source the
release is published from; format changes go bump, publish, then move
the requirement and relock.

check-oxide-artifacts-parity.sh keeps the in-tree version, both manifest
requirements, and the version resolved in every lock equal, and fails if
any lock stops consuming the registry copy. It fails on a drifted in-tree
bump and passes on this tree. Runs in status-guard and just check-guards.

Validated on an RTX 5090: cargo-oxide, mir-importer, ptx-schedule and
backend lib tests, workspace clippy and fmt, all guards, and the full
smoketest (222/222) with the backend writing through the registry crate.

Signed-off-by: Nihal Pasham <nihalp@nvidia.com>
@nihalpasham

Copy link
Copy Markdown
Collaborator

@elibol - where we are, after a full review and a runtime pass:

  • The switch is correct: every path resolves on v0.3.1, 222/222 examples pass, and the simt GPU suites pass here too (two-GPU P2P included).
  • I rebased the branch onto current main (your 17 commits, same authorship) and pushed four commits on top:
    • cargo oxide new templates and the docs sweep now use the simt paths and the crates.io release
    • the retired local cuda-core / cuda-async / cuda-bindings are deleted, with the CI plumbing and license guards that referenced them
    • oxide-artifacts is consumed from crates.io everywhere (one crate for writer and reader), with a parity guard
    • two more guards: host-API paths in docs/templates, and shared-crate pin parity across the example manifests
  • CodeQL is fixed at the root on main (cuda-macros: keep generic kernel specializations alive with black_box, not a null-pointer volatile pair #1226), so the check is clean here now.

What we need for 0.3.1:

  • publish the whole set to crates.io, cutile crates included
  • leave v0.3.1 alone once published; any fix is a v0.3.2
  • a git rebase --signoff on this branch after my last push, since 14 commits have no Signed-off-by (not a blocker)

Then I repoint the dependency lines to the release, relock, rerun the smoketest, and we merge.

@nihalpasham
nihalpasham marked this pull request as ready for review September 4, 2026 02:22
@nihalpasham
nihalpasham self-requested a review as a code owner September 4, 2026 02:22
cutile-rs 0.3.1 is on crates.io (cuda-bindings, cuda-core-derive,
cuda-core, cuda-async, and the cutile stack), so the git-tag pin that
bridged the gap goes away:

- root workspace and all 224 example workspaces (nested ones included)
  name `"0.3.1"` from the registry; `features = ["f16"]` and the
  `package = "cuda-core"` rename in cutile_inter_kernel are kept, and
  cutile_inter_kernel's cutile / cutile-compiler pins move with them
- every Cargo.lock re-resolved with `cargo update -w`: 684 source lines
  flip from `git+...cutile-rs?tag=v0.3.1` to the registry plus a
  checksum each; no other package moves
- deny.toml drops the cutile-rs `allow-git` entry (pliron is the only
  git source left)
- dependency-licenses.csv: the four rows become "1st Party (crates.io)"
- async_mlp and async_vecadd drop their direct cuda-bindings dependency;
  neither names `cuda_bindings` anywhere
- comments in the two license guards and the cutile_inter_kernel README
  stop describing a git dependency

Provenance, checked before this commit: the published .crate archives
match the v0.3.1 tag tree (cdc69c1) file for file, and their sha256
matches both the crates.io index and the new lock checksums. The tag
moved from 9f68be2 (the previous pin) to cdc69c1 for the release; the
shared-crate delta is confined to the Tile-side `AsyncKernelLaunch`
argument storage in cuda-async plus workspace lint config. The
`cuda_async::simt` launch path cuda-oxide uses is unchanged.

Signed-off-by: nihalpasham <nihalp@nvidia.com>
@nihalpasham
nihalpasham merged commit 28e8953 into NVIDIA:main Sep 4, 2026
53 checks passed
roivanov added a commit to roivanov/cuda-rust that referenced this pull request Sep 29, 2026
Run cutile-rs workflows from the nested tree, and point semver-checks
and CUDA 13 shared-crate builds at that workspace after NVIDIA#1102. Skip
cutile-rs in Oxide license inventory and SPDX checks so the import is
not judged by Oxide's CSV and headers.

ci: run the cutile-rs workflow on main

This import lands cutile-rs on private main, so main pushes should run the workflow. Keep the paths filter off so copy-pr-bot mirror pushes still match.
roivanov added a commit to roivanov/cuda-rust that referenced this pull request Sep 30, 2026
Run cutile-rs workflows from the nested tree, and point semver-checks
and CUDA 13 shared-crate builds at that workspace after NVIDIA#1102. Skip
cutile-rs in Oxide license inventory and SPDX checks so the import is
not judged by Oxide's CSV and headers.

ci: run the cutile-rs workflow on main

This import lands cutile-rs on private main, so main pushes should run the workflow. Keep the paths filter off so copy-pr-bot mirror pushes still match.

fix: stop seeding inline kernel-arg pointers with null

CodeQL followed that null fill into the test read of a parameter pointer.
roivanov added a commit to roivanov/cuda-rust that referenced this pull request Sep 30, 2026
Update workspace members, CI, and developer tooling for the nested
SIMT tree. Search reserved-prefix literals under cuda-oxide/crates
after NVIDIA#1102 removed the root host crates. Keep CODEGEN_CRATE_SUBDIR
only in tests.

ci: match stacked PR bases with one glob per line

Oxide pull_request gates run for main, ci/**, and layout/** so stacked
private PRs do not need a new branch name in seven workflows. One
pattern per line keeps the conflict to a single added or removed line.
roivanov added a commit to roivanov/cuda-rust that referenced this pull request Sep 30, 2026
Update workspace members, CI, and developer tooling for the nested
SIMT tree. Search reserved-prefix literals under cuda-oxide/crates
after NVIDIA#1102 removed the root host crates. Keep CODEGEN_CRATE_SUBDIR
only in tests.

ci: match stacked PR bases with one glob per line

Oxide pull_request gates run for main, ci/**, and layout/** so stacked
private PRs do not need a new branch name in seven workflows. One
pattern per line keeps the conflict to a single added or removed line.
roivanov added a commit to roivanov/cuda-rust that referenced this pull request Sep 30, 2026
Update workspace members, CI, and developer tooling for the nested
SIMT tree. Search reserved-prefix literals under cuda-oxide/crates
after NVIDIA#1102 removed the root host crates. Keep CODEGEN_CRATE_SUBDIR
only in tests.

ci: match stacked PR bases with one glob per line

Oxide pull_request gates run for main, ci/**, and layout/** so stacked
private PRs do not need a new branch name in seven workflows. One
pattern per line keeps the conflict to a single added or removed line.

fix: retarget checks after nesting the SIMT tree

The source-reference guard and its workflow step still assumed scripts and crates at the git root. Drop the private ci/** and layout/** pull_request branches the path fix added.

docs: keep the Oxide README at the repository root

GitHub only renders the root file, so the nested layout should not replace the Oxide overview with a short index. Point links in that file at the nested SIMT tree.
roivanov added a commit to roivanov/cuda-rust that referenced this pull request Oct 1, 2026
Update workspace members, CI, and developer tooling for the nested
SIMT tree. Search reserved-prefix literals under cuda-oxide/crates
after NVIDIA#1102 removed the root host crates. Keep CODEGEN_CRATE_SUBDIR
only in tests.

ci: match stacked PR bases with one glob per line

Oxide pull_request gates run for main, ci/**, and layout/** so stacked
private PRs do not need a new branch name in seven workflows. One
pattern per line keeps the conflict to a single added or removed line.

fix: restore paths after host-crate lift

Join the lifted crates to the root workspace with inlined cutile
metadata (edition 2021, 0.3.1). Nested cutile-rs path-depends on
../cuda-*; Oxide keeps the crates.io pin so example workspaces do not
see two cuda_core crates. Host-crate tests run from the git root.
Oxide rustdoc excludes the imported crates. PR gates also run when the
base is layout/cuda-oxide-crates.
roivanov added a commit to roivanov/cuda-rust that referenced this pull request Oct 1, 2026
Update workspace members, CI, and developer tooling for the nested
SIMT tree. Search reserved-prefix literals under cuda-oxide/crates
after NVIDIA#1102 removed the root host crates. Keep CODEGEN_CRATE_SUBDIR
only in tests.

ci: match stacked PR bases with one glob per line

Oxide pull_request gates run for main, ci/**, and layout/** so stacked
private PRs do not need a new branch name in seven workflows. One
pattern per line keeps the conflict to a single added or removed line.

fix: restore paths after host-crate lift

Join the lifted crates to the root workspace with inlined cutile
metadata (edition 2021, 0.3.1). Nested cutile-rs path-depends on
../cuda-*; Oxide keeps the crates.io pin so example workspaces do not
see two cuda_core crates. Host-crate tests run from the git root.
Oxide rustdoc excludes the imported crates. PR gates also run when the
base is layout/cuda-oxide-crates.
roivanov added a commit to roivanov/cuda-rust that referenced this pull request Oct 2, 2026
Update workspace members, CI, and developer tooling for the nested
SIMT tree. Search reserved-prefix literals under cuda-oxide/crates
after NVIDIA#1102 removed the root host crates. Keep CODEGEN_CRATE_SUBDIR
only in tests.

ci: match stacked PR bases with one glob per line

Oxide pull_request gates run for main, ci/**, and layout/** so stacked
private PRs do not need a new branch name in seven workflows. One
pattern per line keeps the conflict to a single added or removed line.

fix: restore paths after host-crate lift

Join the lifted crates to the root workspace with inlined cutile
metadata (edition 2021, 0.3.1). Nested cutile-rs path-depends on
../cuda-*; Oxide keeps the crates.io pin so example workspaces do not
see two cuda_core crates. Host-crate tests run from the git root.
Oxide rustdoc excludes the imported crates. PR gates also run when the
base is layout/cuda-oxide-crates.
nihalpasham added a commit that referenced this pull request Oct 15, 2026
…t source (#1488)

## What this adds

`cargo oxide new` wrote a manifest that took `cuda-device` and
`cuda-host` from the repository's git URL but `cuda-core` (and
`cuda-async` with `--async`) from crates.io 0.4.0. `cuda-host` depends
on `cuda-core` by path inside that checkout, so every scaffolded project
resolved two `cuda-core` packages and the code `#[cuda_module]`
generates failed to type-check. The templates now name every CUDA crate
from the same git source, so cargo resolves one copy of each.

```text
Before:  cuda-host = { git = <repo> }   cuda-core = "0.4.0"
         Cargo.lock: cuda-core x2 (registry + git), cuda-bindings x2, cuda-core-derive x2
         error[E0308]: expected `cuda_core::simt::context::CudaContext`, found `CudaContext`
         7 errors sync, 13 errors async; nothing runs

After:   cuda-host = { git = <repo> }   cuda-core = { git = <repo> }   cuda-async = { git = <repo> }
         Cargo.lock: cuda-core x1, cuda-bindings x1, cuda-core-derive x1, all at one commit
         PASSED: all 1024 elements correct
```

```rust
#[cuda_module]
mod kernels {
    #[kernel]
    pub fn vecadd(a: &[f32], b: &[f32], mut c: DisjointSlice<f32>) { /* ... */ }
}
let module = unsafe { kernels::load(&ctx)? }; // generated code names cuda-host's CudaContext
```

## What we fixed

- Both templates take `cuda-core` and `cuda-async` from `GIT_REPO`, the
source `cuda-host` comes from; `SHARED_HOST_CRATES_VERSION` is gone.
- `check-shared-crate-pin.sh` section 3 requires `git = "{GIT_REPO}"` on
every host-crate line in the templates and rejects a crates.io
requirement there.
- New test `scaffold_takes_every_cuda_crate_from_one_git_source` asserts
every `cuda-*` line in both templates names the same git source as
`cuda-device`.
- The manifest comment and the status-guard step name that still
described a crates.io scaffold now describe the git source.

## Scaffold dependency contract

Every CUDA crate in a new project comes from one git source with no
version requirement. The first build locks all of them to one commit of
that repository, and `cargo update` moves them together. A crates.io
`cuda-core` or `cuda-async` beside a git `cuda-host` is always a second
crate and does not compile.

<details><summary>Implementation</summary>

- The regression began at bd6c3ce (#29, 2026-10-01), which moved
`cuda-oxide` from crates.io `cuda-core` 0.3.1 onto the git-root path
copies while the scaffold kept its crates.io constant; #1466 only
dropped the `version = "=0.4.0"` qualifier. Lockfiles resolved at the
commit before #29 hold one `cuda-core`; at #29, before #1466 and at
#1466 they hold two.
- Cargo identifies a package by name, version and source. A path
dependency declared inside a git-sourced crate resolves to that git
source, and nothing unifies it with a registry copy, so the scaffold has
to name `cuda-core` from the source `cuda-host` uses.
- This is the pre-#1102 form of the template minus `cuda-bindings`,
which no user code names and which `cuda-core` pulls by path.
- The failures are identity mismatches on `CudaContext`, `CudaModule`,
`DeviceBuffer` and the `DriverError` closure: 6 E0308 plus 1 E0277 in
the sync template; 9 E0308, 3 E0277 and 1 E0599 in the async one.
- This supersedes the #1466 plan to derive the scaffold's version from
the root manifests: with no version in the template there is nothing to
derive.

</details>

## Verification

Checked at `180770a9`:

- ✅ `cargo test -p cargo-oxide --all-targets`: 244 passed. With the old
template lines restored and the new tests kept, 3 scaffold tests fail.
- ✅ `cargo clippy -p cargo-oxide --all-targets -- -D warnings`, `cargo
fmt --check` and rustdoc with `-D warnings` clean.
- ✅ All 18 `check-*.sh` guards pass. `check-shared-crate-pin.sh` rejects
four reintroduced forms (`cuda-core = "0.4.0"`, `{ version = "0.4.0" }`,
a different git URL, `cuda-async = "0.4.0"`) with exit 1.
- ✅ Baseline: main's `cargo oxide new` for both templates fails to
build, 7 and 13 errors, with two `cuda-core`, `cuda-bindings` and
`cuda-core-derive` per lockfile (and two `cuda-async`).
- ✅ Fix: `cargo oxide new` then `cargo oxide run` for both templates on
an RTX 5090 (sm_120a, CUDA 13.4): `PASSED: all 1024 elements correct`,
`compute-sanitizer --tool memcheck` 0 errors, one `cuda-core` per
lockfile and zero registry `cuda-*` packages. Same result on the
author's RTX 5060 Laptop (sm_120, CUDA 13.3).
- ✅ Merges cleanly with #1489 in either order; after both, every
template line names `https://github.com/NVIDIA/cuda-rust.git`.

## Caveats

- The test and guard compare the git URL only. A `rev`, `branch` or
`tag` added to one line still resolves a second `cuda-core`, even at the
same commit; a follow-up tightens the comparison to the full source.
- No CI job compiles a scaffolded project, which is how the broken
template survived from 2026-10-01; a scaffold compile gate is a separate
follow-up.
- The GPU runs used a cached backend; the standalone backend build from
the scaffold's git checkout is unchanged by this PR and was not re-run
here.
- `GIT_REPO` still names `NVlabs/cuda-oxide`, which GitHub redirects;
#1489 renames it.

Closes #1459.

---------

Signed-off-by: Alejandro Soto Franco <sotofranco.eng@gmail.com>
Signed-off-by: nihalpasham <nihalp@nvidia.com>
Co-authored-by: nihalpasham <nihalp@nvidia.com>
nihalpasham pushed a commit that referenced this pull request Oct 29, 2026
## What This PR Does

Moves the orphaned `**Rust**` row in
`cuda-oxide/cuda-oxide-book/getting-started/installation.md` back inside
the `Prerequisites` table directly below `**Clang**`:

1. **Restore the `**Rust**` row to the `Prerequisites` table
(`cuda-oxide-book/getting-started/installation.md`)**

---

### 1. Why the `**Rust**` Row Rendered Outside the Table

When `#1102` added the `libcuda` runtime-loading paragraph under
`Prerequisites`, it inserted the paragraph between the `**Clang**` and
`**Rust**` rows, splitting the Markdown table so Sphinx rendered the
`**Rust**` row as literal pipe text at the end of the paragraph:

```text
BEFORE (main):
  | **Clang**        | 21+                 | `clang-21` ...                                            |

  The host runtime does not link `libcuda` at build time...
  | **Rust**         | 1.99.1 (pinned)     | Pinned in `cuda-oxide/rust-toolchain.toml`                |

AFTER (This PR):
  | **Clang**        | 21+                 | `clang-21` ...                                            |
  | **Rust**         | 1.99.1 (pinned)     | Pinned in `cuda-oxide/rust-toolchain.toml`                |

  The host runtime does not link `libcuda` at build time...
```

---

## Verification

* **`just -f cuda-oxide/Justfile book`**: **PASS** (48 HTML pages built
with `-W` warnings-as-errors, 0 warnings; `<tr class="row-odd">` for
**Rust** confirmed inside `<tbody>` in
`getting-started/installation.html`).
* **Repository Guards (`scripts/check-*.sh`)**: **18 / 18 PASS**
(including `check-toolchain-parity.sh` and `check-cli-doc-coverage.sh`).

Signed-off-by: Canberk Pitirli <canberkpitirli@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change Changes a public API, import path, or dependency line that downstream users must update build-related Build scripts, toolchain discovery, or compilation setup cargo-oxide The `cargo oxide` subcommand and build automation cuda-bindings Raw FFI bindings to the CUDA driver/runtime Depends Blocked on another PR/issue or an upstream dependency device-apis User-facing device-side and kernel-authoring APIs (cuda-device, cuda-macros) documentation Improvements or additions to documentation examples Example kernels and host harnesses under crates/rustc-codegen-cuda/examples host-apis Host-side runtime APIs (cuda-host, cuda-core, cuda-async) interop Interop with external compilers / frontends refactor Internal restructuring with no behavior change supply-chain Dependency policy: licenses, sources, security advisories, lock files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants