Repository navigation
feat(tma): add CTA-local G2S tile intrinsics - #1336
Conversation
…e(3) `SharedPtr<T>` is a raw pointer into the issuing CTA's shared memory that carries its address space in its type. It wraps a `*mut SharedSlot<T>`, which the importer types as an `addrspace(3)` pointer, as it already does for pointers to `SharedArray` and `Barrier`, and the importer lowers `SharedPtr` itself to that pointer. Parameters, returns, locals, struct fields and generic code of type `SharedPtr<T>` are therefore CTA-shared pointers by type, across calls to non-inlined helpers, with no dataflow inference. - Constructors: `SharedArray::shared_ptr(&raw mut S)` and `DynamicSharedArray::shared_ptr()` / `shared_ptr_at(bytes)` keep the allocation's shared address; `unsafe SharedPtr::from_raw` is the one explicit locality assertion. - `add` / `offset` / `byte_add` / `cast` keep the type; `as_ptr` widens to a generic pointer; `addr_u32` gives the 32-bit `.shared::cta` address for the `_shared_u32` forms. - The importer drops projections onto `SharedPtr`'s field, which are the identity for this scalar-lowered newtype, so a `SharedPtr` nested in another place is read as a pointer value. - A kernel entry cannot take a `SharedPtr`: the exporter already rejects `addrspace(3)` kernel parameters. - `shared_memory_helper` runs its probe a second time through a non-inlined generic helper that takes the `SharedPtr` in a struct; `verify-code-shape.sh` checks the helper's `ptr addrspace(3)` parameter and that no PTX converts a generic address to shared. Refs NVIDIA#1337, NVIDIA#1238. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Zihua Wu <13583761+lucifer1004@users.noreply.github.com>
Add cp_async_bulk_tensor_{1..5}d_g2s_cta, the TMA tile load into the
issuing CTA's own shared memory
(cp.async.bulk.tensor.Nd.shared::cta.global...). Its destination is a
`SharedPtr<u8>`, a CTA-shared pointer by type (NVIDIA#1337), so the locality
the `.shared::cta` form needs comes from the type rather than from the
safety contract, including through helper functions. The raw ABI keeps
`*mut u8`, as `*mut Barrier` keeps `*mut u64`. The existing
cluster-shared G2S family is unchanged.
On SM12x, ptxas implements a non-multicast `.shared::cluster` tensor
load it cannot prove CTA-local with a runtime check and a syscall,
which makes it ignore `setmaxnreg` (NVIDIA#1238); this form avoids that.
- LLVM NVPTX lowers to the typed llvm.nvvm.cp.async.bulk.tensor.g2s.cta
intrinsics; libNVVM uses the exact inline PTX, as the cluster forms do.
- A destination or barrier known to be cluster shared (addrspace 7)
is a located compile error rather than a local offset.
- The PTX version detector also recognizes the typed intrinsics, so
modules using them get PTX 8.6.
- Tests: mir-lower conversion tests for both backends and a negative
addrspace(7) test, a detector test, the extended TMA admission
ordering assertion, and a tma_copy kernel whose code-shape check
requires the `.shared::cta` form with no address conversion.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Zihua Wu <13583761+lucifer1004@users.noreply.github.com>
385a07e to
8287263
Compare
|
Reworked per the plan in #1337. |
Replay the SharedPtr and CTA-local TMA G2S commits onto the monorepo layout, nightly-2026-10-11 and pliron 0.20. Every conflicting generated, probe, evidence and lock file takes main's copy; the catalog outputs are regenerated in a following commit. The overlay keeps main's reduce LLVM profile line and the two CTA profile lines, with the CTA LLVM profile named after the evidence re-recorded under the pinned LLVM in that commit. The book stamp paragraph keeps main's text until the regeneration refreshes it. In mir-lower the `pliron::location::Located` import goes away because `Operation::loc` is an inherent method in pliron 0.20. Signed-off-by: nihalpasham <nihalp@nvidia.com>
…rd schema 65 The TMA overlay gained two required evidence-profile fields for the CTA-local G2S variants, so a shard without them stopped parsing and every fixture had to change. Follow the reduction-admission precedent instead: the two fields are optional with a serde default, a shard that lists the CTA-local variants must name both and must carry overlay shard schema 65, and a shard without those variants still parses and expands to the cluster family alone. The canonical-order check accepts either the full operation list or the list without the CTA-local variants. The overlay shard schema ceiling moves from 64 to 65 and tma.toml declares schema 65. Tests cover the schema gate in both directions, the missing-profile error, the CTA-free expansion and a partial CTA-local list. Signed-off-by: nihalpasham <nihalp@nvidia.com>
…h stale CTA comments The raw ABI entries for the CTA-local G2S forms keep a generic destination pointer that the lowering converts with cvta.to.shared, but their generated safety text only asked for valid shared-memory objects. The renderer now says that the destination and barrier must lie in the issuing CTA's own shared memory and that a cluster-shared pointer from map_shared_rank would yield a wrong local offset. The mir-lower doc comment for the CTA-local lowering now describes the destination as shared by type with the barrier as the caller's promise, and names the cast and from_raw gap. The importer arity diagnostic names the _cta form when that form is being emitted. Signed-off-by: nihalpasham <nihalp@nvidia.com>
…nd regenerate The CTA-local G2S evidence was recorded against the LLVM revision the previous nightly shipped, which the catalog no longer admits. Replace it with rust-llvm-23.1.3-aab11ee0-tma-cta.json, written from five candidate probe runs of the pinned nightly-2026-10-11 sysroot llc at sm_90 +ptx86, with CUDA 13.3 ptxas V13.3.73 as the terminal stage; every stage detail cites the artifact sizes and SHA-256 digests of those runs. Regenerate the catalog outputs on top of main's copies (catalog SHA-256 27a44de2: 581 operations, 555 generated, 1034 entries, tma 116) and refresh the book stamp at both sites. Signed-off-by: nihalpasham <nihalp@nvidia.com>
…ed_memory_helper Nothing in tree exercised a struct holding two SharedPtr fields passed by value to a non-inlined helper, or a SharedPtr returned from a non-inlined helper and used as a store target. A third kernel runs both shapes over 64 lanes with partner-lane overwrites, so the final values prove the stores went through the scalarized parameters and the returned pointer. The code-shape check now also requires both Pair fields to arrive as ptr addrspace(3) parameters and select_slot to return ptr addrspace(3), and the README describes the probe and the checks. Signed-off-by: nihalpasham <nihalp@nvidia.com>
…1038
Main assigned i1030 to i1033 to the bf16x2 to FP8x2 pack conversions after
this branch merged main, so the five cp_async_bulk_tensor_{1..5}d_g2s_cta
entries move to the next free ids above main's ledger tail. The overlay
shard, the generator recipes, the ordering test and the ledger carry the
new ids; the ledger stays ascending, with i1029 followed by i1034 until
the merge with current main brings in i1030 to i1033. The generated
outputs are regenerated in this same commit (catalog SHA-256 fa1e911b,
counts unchanged: 581 operations, 555 generated, 1034 entries, tma 116)
and the book stamp is refreshed at both sites.
Signed-off-by: nihalpasham <nihalp@nvidia.com>
Second merge with main, which gained 15 commits since the first one. Every conflicting generated, probe, evidence, catalog, reference and lock file takes main's copy; the catalog outputs are regenerated in the following commit. The ABI ledger keeps main's new entries i1030 to i1033 ahead of the CTA-local entries i1034 to i1038. The overlay shard schema is 65 on both sides. The generator test totals become 67 shards and 1038 entries. The shared-memory book page keeps main's dynamic-pool alignment paragraph ahead of the SharedPtr section, and the dialect page keeps main's catalog stamp until the regeneration refreshes it. Signed-off-by: nihalpasham <nihalp@nvidia.com>
The merged overlay holds main's bf16x2 to FP8 pack admission (i1030 to i1033) next to the CTA-local TMA entries (i1034 to i1038), so the catalog hash and every generated, probe and reference file change. Generated by `cargo run -p cuda-intrinsics-gen -- generate` with no hand edits. Signed-off-by: nihalpasham <nihalp@nvidia.com>
…erge The merged catalog hashes to 7e6b4b4b and resolves 1038 entries into 559 generated operations, 585 in all with the 26 hand-written ones. The tma and packed rows were already current. Signed-off-by: nihalpasham <nihalp@nvidia.com>
The constructor is what makes a SharedPtr's locality claim true, and the safe addr_u32 relies on it. It now carries the same Safety section as as_raw_mut_ptr: a static mut SharedArray of the current kernel, obtained with &raw mut. DynamicSharedArray::shared_ptr and shared_ptr_at stay safe, as get and offset are. The doc examples wrap the call in unsafe. Signed-off-by: nihalpasham <nihalp@nvidia.com>
…pointers inside aggregates
The kernel-parameter check downcast only the top-level type, so a
struct { p: SharedPtr<f32>, n: u32 } parameter compiled, declared a
16-byte .param and applied cvta.shared.u64 to host bytes; a *mut Barrier
field had the same hole. The check now looks through struct fields and
array elements and names the field path it found, with a message
distinct from the bare form so a fixture can pin the nested case.
Unit tests cover a struct, an array, a pointer nested below an array
element, and the accepted shapes (a struct of generic and global
pointers on a kernel, the shared-holding struct on a device function).
The new error_kernel_shared_ptr_param fixture pins the nested diagnostic
in the smoketest error lane, with its STATUS.md row and verdict case.
Signed-off-by: nihalpasham <nihalp@nvidia.com>
The module-wide cvta.to.shared search also passed on main's build of this example. The script now also requires the .ll call site to pass a ptr addrspace(3), a PTX .func for shared_ptr_probe that the kernel calls once with mbarrier.init.shared inside its body, and no cvta.to.shared or isspacep anywhere; when the .func is missing it says to build with CUDA_OXIDE_NO_OPT=1. The example joins NO_OPT_SHAPE_EXAMPLES so the smoketest reads the unoptimized artifacts, and the README says that the no-feature build runs the typed probe and that the PTX half reads the unoptimized build. Signed-off-by: nihalpasham <nihalp@nvidia.com>
…oundaries Eleven kernels move a SharedPtr through the boundaries the review's scratch batteries covered: a non-inlined helper returning one, &Region<T> and &mut SharedPtr<T>, a closure through a generic apply, [SharedPtr<T>; 2] by value, Option<SharedPtr<u32>>, a SharedArray of SharedPtrs in shared memory, transmute to usize and back, a from_raw round trip with a negative offset, addr_u32 against cvta_generic_to_shared_u32, a struct pointee with a cast to its first field, and f64 and u16 byte views. The host checks every value and prints one SUCCESS line. Listed in NVVM_VERIFY_EXAMPLES so the compile-only lane also runs it through libNVVM. Structs that mix a SharedPtr with a narrower field stay out: on the p3:32 route they are not yet supported in arrays or shared memory. Signed-off-by: nihalpasham <nihalp@nvidia.com>
The snippet imports thread and kernel, lives in a kernel, uses the dynamic pool pointer it declares, and wraps the unsafe constructor. A paragraph after the table lists the limits: no SharedPtr kernel parameter, bare or inside a struct or array; unions and MaybeUninit fail closed under the union storage rules; repr(packed) carriers follow the packed limits; and on the libNVVM route a struct that mixes a SharedPtr with a narrower field is not yet supported in arrays or shared memory, a gap that predates SharedPtr. The appendix carries the same note next to the union limits. Signed-off-by: nihalpasham <nihalp@nvidia.com>
The strip exists because SharedPtr's scalar is itself a pointer, which the iterative place walker takes as an address to load from. An integer scalar newtype such as ThreadIndex already has identity extract_field arms in dialect-mir and mir-lower, so it needs none. The follow-up is to have the walker track address versus value and then drop the strip. Signed-off-by: nihalpasham <nihalp@nvidia.com>
Signed-off-by: nihalpasham <nihalp@nvidia.com>
A one-page figure for the book and the pull request, in the style of the tile-libraries direct-linkage figure. Signed-off-by: nihalpasham <nihalp@nvidia.com>
SharedArray::shared_ptr is an unsafe constructor now, and the copy itself is unsafe, so the fragment shows the block and its safety argument like the shared-memory chapter's snippet does. Signed-off-by: nihalpasham <nihalp@nvidia.com>
|
/ok to test fbe27a6 |
|
Thanks, this is the explicit local API I asked for in #1249, with the cluster contract left alone. This PR carries the
On the 5090, At This is part of #1238; the reproducer path and the in-tree cluster-form callers stay on #1337. |
What this adds
Two things that land together:
SharedPtr<T>, a pointer into the issuing CTA's shared memory whose address space is part of its type, andcp_async_bulk_tensor_{1..5}d_g2s_cta, TMA tile loads into that memory through the PTX.shared::ctadestination form (PTX 8.6, sm_90 and newer). The existingcp_async_bulk_tensor_Nd_g2sfamily and its cluster contract are unchanged.A
*mut Tin device code is a generic pointer: once it crosses a helper boundary nothing says it points at this CTA's shared memory.SharedPtr<T>keeps that fact in the type and lowers toptr addrspace(3)in parameters, returns, locals, struct fields, generics and closures, so the.shared::ctacopy never has to guess, and ptxas on sm_120a no longer pays the runtime CTA check that made it dropsetmaxnreg.SharedPtr<T>unsafe SharedArray::shared_ptr(&raw mut S)DynamicSharedArray::<T>::shared_ptr()/shared_ptr_at(bytes)get()/offset(bytes)unsafe SharedPtr::from_raw(p)__cvta_generic_to_sharedor CuTemake_smem_ptradd,offset,byte_add,castas_ptr()*mut T, for loads, stores and raw-pointer APIsaddr_u32().shared::ctaaddress, for the_shared_u32formsWhat we fixed
.shared::clustertensor load to a runtime CTA-id check with a syscall fallback. That call path makes ptxas ignoresetmaxnreg(C7506), so a warp-specialized kernel runs with 24 registers instead of 168. Only a statically emitted.shared::ctaform avoids it; sm_100a is native either way.*mut u8fromas_mut_ptr()orget()loses its shared-memory origin at the first helper boundary.SharedPtrkeeps that origin in the type, so the proof the.shared::ctaform needs does not depend on a caller promise.SharedPtrnested in another place, such asregion.baseafter rustc inlinescast, was read as the address of an aggregate. The place walkers now drop the identity projection onto the newtype's field.cvta.sharedto host-supplied bytes. The exporter now refuses it the way it refuses a bare one. Pre-existing with*mut Barrier, easier to reach withSharedPtr.SharedArray::shared_ptrisunsafe, likeas_raw_mut_ptr: the constructors establish the locality thataddr_u32relies on.Contract
dst: SharedPtr<u8>points into the issuing CTA's shared memory by type.barrier: *mut Barriermust point into the issuing CTA's shared memory, as for everymbarrieroperation.SharedPtr<T>comes from shared memory the CTA owns, or from a generic pointer throughunsafe from_raw. For an address outside the shared window,addr_u32yields an unspecified value.SharedPtrkernel parameter, bare or inside a struct or array (the host has no shared address to pass), and a barrier passed straight frommap_shared_rank_mutinto the_ctaform (a located error that names the cluster form).SharedPtr::from_rawand pointer casts applied to a cluster pointer. Those are the caller's unsafe contract; the PTX ISA makes a.shared::ctacopy into another CTA's memory undefined behavior.cuda_intrinsics::tma::cp_async_bulk_tensor_Nd_g2s_cta, ABI i1034 to i1038) keeps*mut u8and converts a generic pointer withcvta.to.shared; its safety text states CTA locality.MaybeUninit<SharedPtr<T>>fail closed under the union storage guard;repr(packed)carriers follow the packed-carrier limits. On the modern libNVVM route (p3:32) a struct that mixes aSharedPtrwith a narrower field is not yet supported in arrays or shared memory; see Caveats..shared::ctaneeds PTX 8.6; the module floor is raised when the typed intrinsic is present. The typed intrinsic needs LLVM 22 or newer; the pinned sysroot llc is LLVM 23.Implementation
SharedSlot<T>(T)is a hiddenrepr(transparent)pointee marker;SharedPtr<T> { ptr: *mut SharedSlot<T> }isrepr(transparent)with manualCloneandCopy. The importer types*mut SharedSlot<T>as a shared pointer in the pointee-keyed arm next toSharedArrayandBarrier, and lowersSharedPtr<T>itself as a scalar newtype next toThreadIndex;strip_shared_ptr_field_projectionsdrops theField(0)identity projection at the top of both place walkers.#[inline(always)]Rust: casts between*mut SharedSlot<_>stay in addrspace(3),as_ptris the 3 to 0 cast,from_rawthe 0 to 3 cast,addr_u32composes withcvta_generic_to_shared_u32.[[tma.variant]]records with ABI entries i1034 to i1038 (appended, no renumbering), per-backend evidence profilesrust-llvm-23.1.3-aab11ee0-tma-ctaandcuda-13.3-libnvvm-13.3.73-tma-cta, overlay shard schema 65 with aTMA_CTA_SHARD_SCHEMAgate; the two new shard fields areOption<String>with serde defaults and are required only when a shard listsg2s_ctavariants.ImmArg,IntrConvergent,ReadOnly<arg2>,WriteOnly<arg0>; PTX floor 8.6;dst: SharedPtr<u8>rendered for the device wrapper only; the raw-layer safety text carries the CTA-locality clause for the five records.emit_tma_g2s_ctareuses the G2S emitter with a cta flag. Lowering:convert_g2s_ctabeside the cluster lowering; LLVM NVPTX declaresllvm.nvvm.cp.async.bulk.tensor.g2s.cta.tile.Nd(p3, p3, p0, i32 x N, i64, i1), libNVVM emits the.shared::ctainline PTX with~{memory}, and anaddrspace(7)operand is a located error. PTX-version detection recognizes the typed intrinsic (8.6 floor).tma_copygainstma_copy_2d_cta_testand a generated-code check;shared_memory_helpergains theSharedPtrprobe through a generic non-inlined helper, the two-field struct and returned-pointer regressions, and a code check that reads the unoptimized build;shared_ptr_apicovers the remaining surface and runs through the libNVVM verifier in CI;error_kernel_shared_ptr_paramis the nested-parameter negative.SharedPtrsection with its limits, the TMA chapter entry with the figure, the API quick reference, the supported-features note and the catalog stamp.Verification
Checked at
fbe27a694on an RTX 5090 (sm_120a) with CUDA 13.4 and nightly-2026-10-11:-D warnings,cargo oxide fmt --check, the 18 guards, the example lock check, cargo deny, the book with-W.cuda-intrinsics-gen check,probe --all --skip-terminal --per-target,check-abi-history(1033 preserved, 1038 total), a secondgeneratewith no diff, the book stamp guard.tma_copyon LLVM NVPTX and on libNVVM (--emit-nvvm-ir --arch sm_120), 4096 of 4096 values for the_ctakernel next to the unchanged kernels;shared_memory_helperon both routes, in the normal and the unoptimized build;shared_ptr_apion both routes; memcheck and synccheck 0 errors; the generated-code checks pass.SharedPtrprovenance (static array, dynamic array, byte offset,from_raw, non-inlined helper): 1088 values on both backends, sanitizer clean; in tree they are covered by probes, cubin assembly and conversion tests.setmaxnreg_dec::<40>/setmaxnreg_inc::<232>) at sm_120a with ptxas 13.4 and nvdisasm: the_ctaform, direct and from an#[inline(never)]helper takingSharedPtr<u8>, gives 168 registers, 0 stack,USETMAXREG.DEALLOC/TRY_ALLOC,UTMALDG.2D, noCALL; the unchanged cluster form gives C7506, 24 registers,CALL.ABS.NOINC.map_shared_rank_mutinto_g2s_ctais a located error namingcp_async_bulk_tensor_2d_g2s;error_kernel_shared_ptr_param(Args { p: SharedPtr<f32>, n: u32 }) fails to compile with the located nested-path error; the llvm-export unit tests refuse{ ptr addrspace(3), i32 }and[2 x ptr addrspace(3)]kernel parameters and keep accepting a struct of generic and global pointers.tma_copy --arch sm_90aemits.version 8.6and ptxas sm_90a assembles all three entries; the five probe PTX files assemble under ptxas 13.4; the pinned llc selects the typed intrinsic at+ptx86and rejects it at+ptx85.tma_copy_2d_testandtma_pipeline_testmatches main's backend apart from symbol numbering; 8 unchanged shared-memory examples produce identical per-kernel PTX.Caveats
cp_async_bulk_tensor_2d_g2s, and the 39 in-tree cluster-form call sites keep.shared::cluster. Migration is tracked in Typed CTA-shared pointers so TMA G2S can use .shared::cta (SM12x setmaxnreg loss, #1238) #1337.SharedPtr::from_rawand pointer casts can launder amap_shared_rankpointer into the typed form; the compiler rejects only an uncastaddrspace(7)operand. A located importer error for that cast is a follow-up.struct { p: SharedPtr<u8>, x: u32 }in a local array or aSharedArraytraps withmisaligned addressbecause mir-lower sizes every pointer at 8 bytes when it picks a struct layout; the same happens with*mut Barrieron main. NVPTX is unaffected, and so areRegion { SharedPtr, usize }and arrays of pointers. Tracked separately.shared_memory_helpercode check reads the unoptimized build, the house mechanism for checks that need a real call boundary._ctafamily is 2D (tma_copy); ranks 1 and 3 to 5 ran in a scratch example at review time.im2col, no L2 cache hint, nocta_groupforms, matching the cluster family..extern .funcdeclarations for the typed intrinsic without a diagnostic; the pinned sysroot llc is LLVM 23.Supersedes #1341, whose commit lands here unchanged. Part of #1337.