Wrappers

For ops whose operand semantics break the chain default, PTX.jl ships hand-written wrapper methods on the matching Operation{...} singleton. The user-facing call site is identical to the chain default — the wrapper just provides a typed method that takes priority over the @generated chain dispatch when its argument types match.

There are four patterns that force a wrapper:

  1. Mixed address-space pointer constraints. Shared-AS pointers need the r (32-bit) constraint; the chain emits l (64-bit) for any LLVMPtr. cp.async, ldmatrix, stmatrix, mbarrier use this override to satisfy ptxas.
  2. Multi-output return. The chain returns one value; ldmatrix x2/x4, mma fragments, and shfl-with-pred-output all return tuples.
  3. Special operand layout. Braced register-vector operands ({$N, $N+1, ...}), tensor-coord forms ([ptr, {c0, c1, c2}]), tied accumulators for wgmma — none have a chain-default rendering.
  4. Compile-time-constant operand types. TMA's <N>d rank, mma shape/dtype, fragment counts — these need to be pinned at the registration boundary so each variant gets its own typed method.

Each wrapper file follows the same shape: a small declarative table maps (shape, dtype, …) to register counts and constraints, a _register function builds the family-local spec (asm template, llvmcall body, or intrinsic name) and emits a typed method per valid combination from a for loop. Bookkeeping is shared, not per-file: every generator records what it defined in the wrapper registry (src/wrappers/registry.jl) — one WrapperRecord per (family, op, mods, tier, intrinsic) key behind a single idempotency set, so re-invoking a registration helper cannot inflate the inventories the test count pins protect. Tier-2 generators obtain their IntrinsicCall through wrapper_intrinsic_call, which validates the generated name against the pinned NVVM registry and records it in one step; asm/core-IR generators call register_wrapper! directly. The test-side oracles read the inventories back through wrapper_records / wrapper_intrinsic_names / wrapper_asm_forms / wrapper_missing_intrinsics.

Literal spellings are declared with the @optype_str definition macro (@inline optype"tcgen05.alloc.cta_group::1.sync.aligned.b32"(...) = ...); only generator loops that build the mods tuple programmatically define methods on the Operation{op, mods} singleton directly.

Adding a new dtype/shape combination is one entry in the table plus one line in the loop.

Family overview

FamilyFileSurface
cp.async (scalar)wrappers/cp_async.jlcp.async.{ca,cg}.shared.global [smem], [global], Val(N) — needs shared r constraint and N-baked size
cp.async.bulk.tensor (TMA)wrappers/tma.jlcp.async.bulk.tensor.{1..5}d.{shared::cluster|shared::cta}.global.tile.mbarrier::complete_tx::bytes (load), .global.shared::cta.tile.bulk_group (store), and cp.async.bulk.prefetch.tensor.{1..5}d.L2.global.tile[.L2::cache_hint] plus the base {3..5}d.L2.global.im2col[.L2::cache_hint] modes. Tile coordinates become positional Int32 arguments; base im2col takes exactly rank-many Int32 coordinates followed by rank-minus-two Int16 offsets. A cache-hinted prefetch adds one UInt64 policy operand. Prefetch is a weak, fire-and-forget performance hint, so no runtime result is exposed. The later .im2col::w[::128] and .tile::gather4 grammars are not part of this surface.
cvt sub-byte FP packingwrappers/cvt.jlcvt.rn.satfinite.e2m1x2.{f32,f16x2,bf16x2} pack and cvt.rn.{f16x2,bf16x2}.e2m1x2 unpack — .b8 carrier through a mov.b16 brace-pair shim because NVPTX has no i8 constraint
extended precisionwrappers/extended_precision.jlAll 48 add.cc / addc / sub.cc / subc / mad.cc / madc scalar forms with explicit Bool carry/borrow, plus fused arbitrary-limb add/sub and unsigned 2×2-limb multiply
fence.proxy.tensormap::genericwrappers/fence.jlExact PTX 8.3 / sm_90+ acquire and release forms at {cta,cluster,gpu,sys} scope. Acquire takes a generic address that resolves to global memory plus the ISA-fixed Val(128) range; release takes no operands. All eight lower through NVVM as per-thread, non-convergent ordering operations.
ldmatrixwrappers/ldmatrix.jlm8n8.{x1,x2,x4}[.trans].b16; m16n16.{x1,x2}.trans.b8; and Blackwell optional decompression from {b6x16_p32,b4x16_p64} to b8x16 for m8n16.{x1,x2,x4} and m16n16.{x1,x2}.trans, in {shared,shared::cta}. Returns one UInt32 per m8n16 matrix and two per m16n16 matrix. Generic state-space addressing is not part of the typed wrapper surface.
mbarrierwrappers/mbarrier.jl, ledgers/mbarrier_forms.jlClosed PTX 9.3 form schema for lifecycle, tx-count, arrive/drop, waits, pending-count, layout, sem/scope, and CTA/cluster spaces; exact wrappers accelerate common forms while delegating to the same schema emitter. Results are explicit sink (.sink → PTX _), UInt64 state, Bool, UInt32 pending count, (waitComplete, reportPredicate), or (waitComplete, reportPredicate, reportValueCarrier::UInt16). The ISA's two noncanonical arrive_drop example heads are provenance-marked aliases and normalize on emission. PTX's 1-byte report value occupies the carrier's low byte because NVPTX has no i8 inline-asm constraint. Explicitly shared addresses use the NVPTX r constraint; every route carries convergent nomerge.
mma.sync.alignedwrappers/mma.jlmma.sync.aligned.<shape>.<layA>.<layB>.<d>.<a>.<b>.<c> for bf16/f16/tf32/FP8, modern m16n8 u8/s8 and u4/s4 integer forms, the exact six s32.b1.b1.s32.{xor,and}.popc forms, and kind::f8f6f4 5×5 sub-byte FP A/B (consumer Blackwell). Single-bit fragments are (A,B,C/D) = (1,1,2) registers at m8n8k128, (2,1,4) at m16n8k128, and (4,2,4) at m16n8k256; A/B use packed UInt32, while C/D use Int32. XOR starts at PTX 7.0, AND at PTX 7.1; only m8n8k128.xor has an sm75 floor, with the other five at sm80. LLVM 22 rejects its m8 XOR intrinsic at sm_75, so that one form uses typed convergent inline assembly while the other five use NVVM.
mma.sp[::ordered_metadata].sync.alignedwrappers/mma.jlA closed sparse surface comprising 12 floating ABIs per metadata variant plus 64 integer forms: u8/s8 at m16n8k{32,64} and u4/s4 at m16n8k{64,128}, every A/B signedness pair, optional .satfinite, and ordinary/ordered metadata. A/B fragments and metadata use UInt32; integer C/D use Int32. Ordered metadata requires increasing retained indices, with exact shape-dependent selectors (0:3, 0:1, or 0).
mma.sync.aligned.kind::mxf* (block-scaled)wrappers/mma_scaled.jlThree Blackwell-introduced kinds: mxf4, mxf4nvf4, mxf8f6f4. Operand layout (scale_data::UInt32, byte_id::UInt16, thread_id::UInt16) per side per PTX 9.2 §9.7.14.3
shfl.syncwrappers/shfl.jlup / down / bfly / idx × b32 × {data-only, data+pred}
stmatrixwrappers/stmatrix.jlmirror of ldmatrixm8n8.b16 (sm_70+) and m16n8.b8 (Hopper)
vec_ldstwrappers/vec_ldst.jlld.global.v{2,4}.{f32,b32,b16} / st.global.v{2,4}.{f32,b32,b16} — braced register-vector I/O for HBM-saturating bandwidth
wgmma.mma_async (Hopper sm_90a)wrappers/wgmma.jlwgmma.mma_async.sync.aligned.m64nNk{8,16,32}.<d>.<a>.<b> — accumulator passed by value (tied operands). Floating forms use all 32 N values stepped by 8 through 256; integer forms use the ISA's 16-value grid 8,16,24,32,48:16:224. The closed surface is 256 floating + 64 integer shape/type forms, each with SS runtime/constant scale_d and RF-A runtime variants.
tcgen05 (Blackwell sm100a/sm110a and family targets)wrappers/tcgen05.jlExact lifecycle, fence/wait, TMEM address, load/store, dense-MMA, and MX block-scale forms. MX uses the complete seven-operand schema (d, a_desc_or_tmem, b_desc, idesc, scale_a_tmem, scale_b_tmem, enable_input_d) and all eight legal kind × {scale_vec,block} spellings; the former five-argument MX surface is rejected. shift / dealloc / cp / ld / st take a 32-bit TMEM address returned by tcgen05.alloc, while alloc/commit use reviewed shared-memory carriers. ld/st cover the complete Table-52 grid — all five shapes including 16x32bx2, whose immHalfSplitoff is a positional Val(off) immediate — each with the optional .pack::16b/.unpack::16b repack qualifier; ld.red reduction forms have no NVVM records at the pinned backend and stay outside the surface. cp covers all six shape spellings — 64x128b/32x128b only with their ISA-mandated warpx2::*/warpx4 multicast — each optionally with the .b8x16.{b6x16_p32,b4x16_p64} decompression pair. Dense MMA covers both A sources (UInt64 SMEM descriptor vs UInt32 TMEM address, dispatched like the ISA's a-desc vs [a-tmem]), the collector::a::{fill,use,lastuse} qualifiers (discard is the spelled-nothing default), .ashift (TMEM A; discard/lastuse only), an optional positional disable-output-lane mask (`NTuple{4

Schema-driven structured results

setp, lop3, match.sync, and elect.sync no longer rely on a small set of hand-written dual-result methods. src/ledgers/structured_results.jl expands the complete PTX 9.3 grammar into 1,134 reviewed schemas and the generic chain emitter consumes those schemas directly. This keeps scalar and grouped siblings in the same closed boundary: modifier legality, source carriers, result tuple shape, sink positions, and target metadata cannot drift between a wrapper and the fallback.

General setp uses the Julia-only leading .dual selector for the optional compare/complement result. Packed .f16x2 and .bf16x2 always return the two lane predicates. BoolOp lop3 returns (UInt32, Bool) and requires a Val{N} LUT with N in 0:255; match.all.sync.*.pred selects the optional predicate, and elect.sync always returns (UInt32, Bool). The two warp-collective families use the same convergent nomerge LLVM call-site path as the existing MMA and mbarrier wrappers. The single-predicate queries testp (source-type tail) and isspacep (unbracketed .u32/.u64 generic-address value) live in the same ledger because the generic trailing-dtype rule would misread their destination ABI.

For the mbarrier full-report form, reportValue is a PTX .b8 destination. This agrees with the CUDA Runtime API's description of cudaFabricOpStatusSourceMbarrierV1 as 1-byte aligned and 1-byte wide, and is enforced by ptxas evidence at sm_90 and sm_121. The Julia result is a zero-extended UInt16 carrier because LLVM's NVPTX inline-asm interface has no i8 register constraint; only its low byte is the opaque status value.

Arrival calls that spell destination _ use the synthetic .sink selector, for example ptx"mbarrier.arrive.sink.release.cluster.b64"(remote_addr). This distinction cannot be inferred from argument types: the otherwise identical local/generic form returns a UInt64 state token. It is especially important for a generic address produced for a remote cluster mbarrier, where the ISA requires the sink destination. Explicit shared::cluster forms are always sinks and need no selector. Base-plus-constant-offset addresses remain valid; tensor-coordinate address lists are rejected rather than truncated.

The simple WGMMA synchronization ops (wgmma.fence, wgmma.commit_group, wgmma.wait_group) still flow through the reviewed generic chain contract. Every tcgen05 form is typed-wrapper-only, including the ptxas-covered pointer forms of alloc and non-multicast commit and the before/after thread-sync fences. An exact method is authoritative and a dispatch miss fails before generic asm rendering. This is necessary because the shared opcode spans address destinations, register vectors, sinks, descriptors, fences, and MMA operands with incompatible schemas.

MX target evidence is deliberately split by PTX target class. The explicit .scale_vec::* spellings are compiled offline through ptxas at sm_100a and sm_110a; the equivalent .block16/.block32 aliases are compiled at sm_100f and sm_110f. Those checks validate source schema, inline-asm constraints, target gating, and assembler acceptance without launching a kernel. CUDACore 6.2.1 cannot emit an sm_110* module, so those cases emit the identical body at the matching sm_100a/sm_100f feature level, assert that only the .target directive changes, and invoke CUDA 13.3 ptxas directly on the sm_110a/sm_110f text. No tcgen05 runtime claim is made for the available CC 12.1 GB10, which does not implement tcgen05.

Ordered sparse MMA metadata

mma.sp::ordered_metadata is not a looser spelling of ordinary sparse MMA. For 2:4 f16/bf16/FP8 storage, each metadata nibble must list its two retained element indices in increasing order; other orderings have undefined behavior. (tf32 uses one retained index per 1:2 chunk.) PTX.jl cannot infer or repair that property from an opaque UInt32 metadata word, so callers must establish it while pruning and packing. The runtime evidence in test/gpu/ampere/gemm_sparse.jl does this by sorting each retained pair before encoding the nibble.

The typed surface deliberately mirrors only the 12 sparse MMA ABIs already supported by mma.sp. The ordered qualifier was introduced in PTX 8.5 and the classic f16/bf16/tf32 forms target sm_80+; the FP8 forms require sm_89+. Selector legality is enforced by exact Val dispatch: k16 16-bit and k8 tf32 accept Val(0) through Val(3), k32 16-bit and k16 tf32 accept Val(0) or Val(1), and k64 FP8 accepts only Val(0). A wrong selector, fragment width, or unreviewed shape/type combination fails before the generic scalar emitter. Like all mma.sync operations, these calls are warp-collective and carry convergent nomerge; they remain memory-free arithmetic operations.

The classic integer sparse surface is separate from tcgen05.mma.sp. It uses register fragments rather than tensor-memory descriptors and contains exactly 64 forms: u8/s8 at m16n8k32 and m16n8k64, u4/s4 at m16n8k64 and m16n8k128, all four A/B signedness pairs, with and without .satfinite, and both ordinary and ordered metadata. A/B register tuples contain 2/2, 4/4, 2/2, and 4/4 packed UInt32 values respectively; C/D are always four Int32 values, and metadata is one UInt32. The k32 u8/s8 and k64 u4/s4 products use selector Val(0) or Val(1); k64 u8/s8 and k128 u4/s4 require Val(0). For ordered u4/s4 storage, each 4:8 group is encoded as two ordered 2-element retained subgroups; the wrapper cannot validate those opaque metadata nibbles. Ordinary integer sparse MMA requires PTX 7.1, ordered metadata requires PTX 8.5, and all forms require sm_80+.

Extended-precision arithmetic

The extended-precision wrapper is a correctness boundary, not only an ergonomic overload. PTX CC.CF is implicit state that LLVM cannot name as an input/output constraint. Scalar typed wrappers reify it as Bool; aggregate helpers keep the entire chain in one asm unit. Generic, raw, and transpiled instruction-at-a-time fallbacks are rejected instead of emitting optimizer- unsafe calls.

The public helpers are PTX.add_with_carry, PTX.sub_with_borrow, and PTX.mul_wide; their full API documentation is in the reference.

The blocks are per-thread (convergent=false), carry sideeffect and ~{cc} but no false memory clobber, and use early-clobber word outputs so a low result cannot alias a high input that a later instruction still needs. Subtraction seeds and materializes the flag through sub.cc/subc, directly mirroring PTX borrow semantics instead of relying on a cross-family flag representation.

Host-side descriptor builders

Two opcodes consume packed 64-bit shared-memory descriptors plus (Blackwell only) a 32-bit instruction descriptor. PTX.jl ships pure bit-packing helpers for both:

HelperUsed by
wgmma_descriptorHopper wgmma.mma_async SMEM operand encoding (14-bit field windows + swizzle + base offset)
tcgen05_descriptorBlackwell tcgen05.mma SMEM operand encoding (3-bit layout vs wgmma's 2-bit; legal swizzle values only). The ISA-fixed 0b001 field is inserted internally; lbo_mode=1 selects the restricted sm_103a absolute leading-address mode.
tcgen05_instr_desc_f16bf16_f32Blackwell tcgen05.mma 32-bit instruction descriptor for F16/BF16/TF32 → F32 paths. The integer-only saturation bit and reserved bits are fixed at zero. Mirrors CUTLASS/CuTe's UMMA::make_instr_desc.
tcgen05_instr_desc_i8.kind::i8 instruction descriptor (u8/s8 → s32). Negation is ISA-unsupported for the integer kind, so those bits are not caller-controlled.
tcgen05_instr_desc_f8f6f4.kind::f8f6f4 instruction descriptor (independent e4m3/e5m2/e2m3/e3m2/e2m1 A/B types, f16 or f32 destination).
tcgen05_instr_desc_mxf8f6f4.kind::mxf8f6f4 block-scale instruction descriptor (scale-factor data IDs; scale matrix type is the ISA-fixed UE8M0).
tcgen05_instr_desc_mxf4, tcgen05_instr_desc_mxf4nvf4.kind::mxf4 / .kind::mxf4nvf4 block-scale instruction descriptors (E2M1 elements; transpose ISA-unsupported; nvf4 additionally chooses the scale matrix type).
smem_addr_u32Convert a Core.LLVMPtr{T, AS.Shared} to its 32-bit in-CTA SMEM offset (used as the smem_addr_u32 argument to the descriptor builders).
step_descAdvance a wgmma SMEM-operand descriptor's start address by a byte offset — walking a SMEM ring buffer or stepping K within a tile without re-packing.

These are not exported but are part of the documented API. Access them as PTX.wgmma_descriptor, PTX.tcgen05_descriptor, etc.

The tcgen05 builders deliberately expose semantic fields rather than every bit in the packed values. In particular, tcgen05_descriptor has no version keyword: PTX 9.3 §9.7.17.4.1 defines bits 46–48 as the fixed constant 0b001, not a version selector. Likewise, only tcgen05_instr_desc_i8 has a saturate keyword (the bit is integer-only), the i8 builder has no negate keywords and the mxf4 builders no transpose keywords (both ISA-unsupported for those kinds), and every architecture-gated encoding (the sm_107f-family K-dimension and scale-layout bits) is fixed at zero rather than exposed. An illegal descriptor is unrepresentable through the builders, not merely rejected. Matrix addresses and byte offsets must be 16-byte aligned and fit the 18-bit descriptor input window; invalid swizzle encodings and values that would spill into reserved fields fail with ArgumentError instead of being truncated.

Absolute leading-address mode (lbo_mode=1) is the PTX 9.3 §9.7.17.3.1.2 escape hatch for a 48-byte K dimension. The builder enforces its shared-descriptor restrictions: 128-byte swizzling with 16-byte atomicity (BlackwellLayout.B128, code 2) and base_offset=0. Because K and major axes live outside the shared descriptor, the caller must still pair it with K-major A and B (instruction-descriptor transpose bits 15 and 16 both zero), K=48B, and the architecture-specific sm_103a target.

PTX.wgmma_descriptorFunction
wgmma_descriptor(smem_addr_u32; leading_byte_offset, stride_byte_offset,
                 swizzle = WgmmaSwizzle.NONE, base_offset = 0) -> UInt64

Pack a wgmma.mma_async shared-memory operand descriptor. Byte offsets encode as 14-bit fields covering the high bits of an 18-bit, 16-byte-aligned window; swizzle is one of the WgmmaSwizzle codes (0 = none, 1 = 128B, 2 = 64B, 3 = 32B). Get smem_addr_u32 from smem_addr_u32.

source
PTX.smem_addr_u32Function
smem_addr_u32(p::Core.LLVMPtr{T, AS.Shared}) -> UInt32

The 32-bit in-CTA SMEM offset of a shared-address-space pointer — the smem_addr_u32 argument the descriptor builders take. Bounces the pointer through inline asm with the r constraint to surface the 32-bit offset NVPTX represents internally.

source
PTX.step_descFunction
step_desc(desc::UInt64, byte_offset::Integer) -> UInt64

Advance a wgmma SMEM-operand descriptor's start address by byte_offset bytes. The descriptor's low 14 bits encode (smem_addr & 0x3FFFF) >> 4, so adding byte_offset >> 4 moves the base by the equivalent byte distance — as long as the carry stays within the 14-bit start_addr field (true for any in-CTA SMEM offset on H100). Used to walk a SMEM ring buffer (per-stage offset) or to step within a tile (per-wgmma-K offset on the inner loop).

byte_offset must be a multiple of 16 — the operand encoding has no representation for sub-16-byte offsets. Caller guarantees this; the helper truncates silently if violated.

source
PTX.tcgen05_descriptorFunction
tcgen05_descriptor(smem_addr_u32; leading_bytes, stride_bytes,
    swizzle=BlackwellLayout.NONE, base_offset=0, lbo_mode=0) -> UInt64

Pack the tcgen05 shared-memory descriptor from PTX 9.3 §9.7.17.4.1 Table 43. The matrix address and both byte fields must be 16-byte aligned and fit the descriptor's 18-bit input window. swizzle must be one of the five encodings in BlackwellLayout.

Bits 46–48 are always the ISA-mandated constant 0b001; reserved/fixed-zero bits are never caller-controlled. lbo_mode=1 selects the absolute leading- dimension byte-address mode. PTX 9.3 §9.7.17.3.1.2 restricts that mode to BlackwellLayout.B128 (128-byte swizzle with 16-byte atomicity) and a zero base_offset, which this builder enforces. The caller must additionally pair it with K-major A and B descriptors (both instruction-descriptor transpose bits zero), a 48-byte K dimension, and the architecture-specific sm_103a target. The default lbo_mode=0 is the relative byte-offset mode.

source
PTX.tcgen05_instr_desc_f16bf16_f32Function
tcgen05_instr_desc_f16bf16_f32(; m, n, ab_dtype,
    a_major=:K, b_major=:K, scale_a=1, scale_b=1,
    sparse=false, max_shift=0) -> UInt32

Pack the PTX 9.3 §9.7.17.4.2 Table 45 instruction descriptor for the .kind::f16 / .kind::tf32 paths whose destination type is .f32 and whose A/B types are the same. ab_dtype is :f16, :bf16, or :tf32.

The integer-only saturation bit and every reserved bit are fixed at zero and are not caller-controlled. sparse=true selects the .sp descriptor bit with sparsity selector zero. A nonzero max_shift is meaningful only when the descriptor is consumed by a .ws form; legality of the complete MMA shape and form remains the responsibility of the consuming instruction wrapper.

source
PTX.tcgen05_instr_desc_i8Function
tcgen05_instr_desc_i8(; m, n, a_dtype, b_dtype,
    a_major=:K, b_major=:K, saturate=false,
    sparse=false, max_shift=0) -> UInt32

Pack the PTX 9.4 §9.7.18.4.2 Table 51 instruction descriptor for the .kind::i8 path (destination type fixed at .s32). a_dtype and b_dtype are :u8 or :s8, independently.

Negation (bits 13–14) is not supported for .kind::i8 (§9.7.18.10 Table 62) and is not caller-controlled; transpose (a_major/b_major = :MN) is. Bit 29 (the wider-K encoding) is architecture-gated and fixed at zero, so the descriptor always encodes the base K (dense 32 / sparse 64). .kind::i8 itself is a-variant-exclusive (§9.7.18.10 target notes); legality of the complete MMA form remains the consuming wrapper's responsibility.

source
PTX.tcgen05_instr_desc_f8f6f4Function
tcgen05_instr_desc_f8f6f4(; m, n, a_dtype, b_dtype, d_dtype=:f32,
    a_major=:K, b_major=:K, scale_a=1, scale_b=1,
    sparse=false, max_shift=0) -> UInt32

Pack the PTX 9.4 §9.7.18.4.2 Table 51 instruction descriptor for the .kind::f8f6f4 path. a_dtype and b_dtype are independently :e4m3, :e5m2, :e2m3, :e3m2, or :e2m1; d_dtype is :f16 or :f32.

Bit 29 (the K=64 encoding) is architecture-gated and fixed at zero, so the descriptor always encodes the base K (dense 32 / sparse 64) — at which every transpose combination is legal for these element types (§9.7.18.10 Table 62's transpose exceptions apply only to the gated K=64 encoding and to decompress::lut::b, both outside this builder). The integer saturation bit is N/A for float kinds and not caller-controlled.

source
PTX.tcgen05_instr_desc_mxf8f6f4Function
tcgen05_instr_desc_mxf8f6f4(; m, n, a_dtype, b_dtype,
    scale_a_id, scale_b_id, a_major=:K, b_major=:K,
    scale_a=1, scale_b=1, sparse=false) -> UInt32

Pack the PTX 9.4 §9.7.18.4.2 Table 52 instruction descriptor for the .kind::mxf8f6f4 path. a_dtype/b_dtype take the same five element types as .kind::f8f6f4; scale_a_id/scale_b_id are the scale-factor data IDs (0–3). The scale matrix type is the ISA-fixed UE8M0 (bit 23).

The architecture-gated fields — scale-factor layout (bit 26) and the K=64 encoding (bit 31) — are fixed at zero: 32-lane scale layout, base K.

source
PTX.tcgen05_instr_desc_mxf4Function
tcgen05_instr_desc_mxf4(; m, n, scale_a_id, scale_b_id,
    scale_a=1, scale_b=1, sparse=false, sparsity_version=0) -> UInt32

Pack the PTX 9.4 §9.7.18.4.2 Table 53 instruction descriptor for the .kind::mxf4 path. Element types are the ISA-fixed E2M1 and the scale matrix type the ISA-fixed UE8M0; scale_a_id/scale_b_id must be 0 or 2. Transpose is not supported for the mxf4 kinds (§9.7.18.10 Table 62) and the transpose bits are not caller-controlled; negation is.

sparsity_version (bit 12) is architecture-bound: 0 on the sm100a-class a-variants, 1 on sm107a — a mismatch is undefined behavior, so it is caller-selected, not defaulted per target. The K-dimension encoding (bits 3 and 31) is fixed at zero: base K (dense 64 / sparse 128).

source
PTX.tcgen05_instr_desc_mxf4nvf4Function
tcgen05_instr_desc_mxf4nvf4(; m, n, scale_dtype, scale_a_id, scale_b_id,
    scale_a=1, scale_b=1, sparse=false, sparsity_version=0) -> UInt32

Pack the PTX 9.4 §9.7.18.4.2 Table 53 instruction descriptor for the .kind::mxf4nvf4 path — identical to tcgen05_instr_desc_mxf4 except the scale matrix type is caller-chosen: scale_dtype is :ue4m3, :ue8m0, or :ue5m3 (bits 23–24).

source

GMMA layout helpers

For wgmma.mma_async SMEM operands, the descriptor's leading_byte_offset / stride_byte_offset / swizzle triple is fully determined by the tile geometry (dtype, M-or-N, K, major axis). The four canonical GMMA layout families (INTERLEAVE / B32 / B64 / B128) cover all wgmma-compatible SMEM tile widths.

PTX.pick_gmma_layoutFunction
pick_gmma_layout(; elem_bytes, m_or_n, k, major) -> GmmaLayout

Canonical GMMA layout for a (dtype, M-or-N, K, major) SMEM tile. major is :K or :MN. See module-top comment for the full mapping.

source
PTX.layout_for_aFunction
layout_for_a(; dtype, m, k) -> GmmaLayout

K-major GMMA layout — natural for row-major A (MxK with K-fast), and also the right pick for operand B when the kernel lays B as row-major KxN in SMEM with K-fast (the common case across the Hopper kernels in this repo). Use this for either operand whose K-dimension is fastest-varying in SMEM.

source
PTX.layout_for_mn_majorFunction
layout_for_mn_major(; dtype, k, n) -> GmmaLayout

MN-major GMMA layout — operand B laid as col-major KxN in SMEM with N-fast (i.e. the N-or-M dimension is fastest-varying). Required only when the SMEM tile genuinely needs MN-fastness and a transposed wgmma trans_b=1 flag is used. Most Hopper kernels in this repo use layout_for_a for both operands and let wgmma run with trans_b=0 against a K-fast B tile; reach for layout_for_mn_major only when the data layout forces the issue.

source

Host-side TMA descriptor encoder

Hopper TMA (cp.async.bulk.tensor.*) consumes a 128-byte CUtensorMap blob built host-side by the CUDA driver's cuTensorMapEncodeTiled. PTX.jl wraps the driver call so descriptors take Julia types / symbols instead of raw CUtensorMapDataType enums, with a thin convenience helper for the common 2D row-major case.

These methods live in ext/CUDACoreExt.jl and load automatically when CUDACore is in the environment.

PTX.tensor_map_encode_tiledFunction
tensor_map_encode_tiled(dtype, global_addr, global_dim, global_strides,
                        box_dim; kwargs...) -> CuTensorMap

Build a CUtensorMap for a tiled TMA descriptor. Calls cuTensorMapEncodeTiled.

Arguments (innermost-first convention, matching the driver):

  • dtype — Julia type (Float32, UInt16, …) or symbol (:bf16, :tf32, …)
  • global_addrPtr{T}, CuPtr, or UInt. Caller-owned; the driver stores it in the descriptor (use cuTensorMapReplaceAddress to swap later).
  • global_dim::NTuple{N, <:Integer} — tensor shape, innermost first.
  • global_strides::NTuple{N-1, <:Integer} — outer-dim strides in bytes. Length must be N-1 (the innermost stride is elem_bytes * 1, implicit).
  • box_dim::NTuple{N, <:Integer} — per-launch tile shape, innermost first.

Keyword arguments:

  • elem_strides::NTuple{N, <:Integer} = (1, 1, …) — sub-tile stride.
  • interleave::Symbol = :NONE:NONE / :B16 / :B32.
  • swizzle::Symbol = :NONE:NONE / :B32 / :B64 / :B128 (plus Blackwell atom variants).
  • l2_promotion::Symbol = :NONE:NONE / :B64 / :B128 / :B256.
  • oob_fill::Symbol = :NONE:NONE / :NAN_REQUEST_ZERO_FMA.

Requires the CUDACore package extension: load CUDA.jl (which depends on CUDACore) or CUDACore itself. Without it this function has no methods, and calling it raises a MethodError whose hint names the missing package.

source
PTX.tensor_map_tile_2dFunction
tensor_map_tile_2d(dtype, global_addr, rows, cols, box_rows, box_cols;
                   swizzle=:B128, oob_fill=:NONE) -> CuTensorMap

Convenience for a 2D row-major (rows, cols) tensor + (box_rows, box_cols) tile. Innermost dim is cols. Stride is cols * elem_bytes.

Mirrors pyptx synthesize_tma_descriptor (2D path). Caller is responsible for keeping box_cols * elem_bytes consistent with the swizzle (e.g. 128B for :B128).

source

When to extend

Most chain-default coverage is sufficient. Reach for a wrapper when:

  • ptxas rejects the chain output ("Arguments mismatch", wrong constraint letter on a shared-AS pointer, missing brackets on a memory operand);
  • the op returns multiple values;
  • the operand layout has a brace group, tensor-coord vector, or other shape with no $N rendering;
  • the dispatch needs to key on a fragment shape or count that the chain can't see.

The pattern: copy the closest existing wrapper file, adjust the table and asm template, add a for loop that emits one typed method per combination — spelling literal forms with optype"" and routing the generator's bookkeeping through register_wrapper! / wrapper_intrinsic_call (see src/wrappers/registry.jl). ~80 LOC for a new family in most cases.