MLIR 24.0.0git
NVVMDialect.cpp
Go to the documentation of this file.
1//===- NVVMDialect.cpp - NVVM IR Ops and Dialect registration -------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file defines the types and operation details for the NVVM IR dialect in
10// MLIR, and the LLVM IR dialect. It also registers the dialect.
11//
12// The NVVM dialect only contains GPU specific additions on top of the general
13// LLVM dialect.
14//
15//===----------------------------------------------------------------------===//
16
18
22#include "mlir/IR/Builders.h"
25#include "mlir/IR/Diagnostics.h"
27#include "mlir/IR/MLIRContext.h"
28#include "mlir/IR/Operation.h"
30#include "mlir/IR/Types.h"
32#include "llvm/ADT/STLExtras.h"
33#include "llvm/ADT/TypeSwitch.h"
34#include "llvm/IR/IRBuilder.h"
35#include "llvm/IR/NVVMIntrinsicUtils.h"
36#include "llvm/Support/Casting.h"
37#include "llvm/Support/FormatVariadic.h"
38#include "llvm/Support/NVPTXAddrSpace.h"
39#include "llvm/Support/raw_ostream.h"
40#include <cassert>
41#include <optional>
42#include <string>
43
44using namespace mlir;
45using namespace NVVM;
46
47#include "mlir/Dialect/LLVMIR/NVVMOpsDialect.cpp.inc"
48#include "mlir/Dialect/LLVMIR/NVVMOpsEnums.cpp.inc"
49
50static constexpr unsigned notIntrinsic = llvm::Intrinsic::not_intrinsic;
51
52//===----------------------------------------------------------------------===//
53// Helper/Utility methods
54//===----------------------------------------------------------------------===//
55
56static bool isPtrInAddrSpace(mlir::Value ptr, NVVMMemorySpace targetAS) {
57 auto ptrTy = llvm::cast<LLVM::LLVMPointerType>(ptr.getType());
58 return ptrTy.getAddressSpace() == static_cast<unsigned>(targetAS);
59}
60
62 return isPtrInAddrSpace(ptr, NVVMMemorySpace::Generic);
63}
64
66 return isPtrInAddrSpace(ptr, NVVMMemorySpace::Shared);
67}
68
70 return isPtrInAddrSpace(ptr, NVVMMemorySpace::SharedCluster);
71}
72
73static llvm::Value *castPtrToAddrSpace(llvm::IRBuilderBase &builder,
74 llvm::Value *ptr,
75 NVVMMemorySpace targetAS) {
76 unsigned AS = static_cast<unsigned>(targetAS);
77 return builder.CreateAddrSpaceCast(
78 ptr, llvm::PointerType::get(builder.getContext(), AS));
79}
80
81// Helper method to convert CtaGroupKind in NVVM Dialect to CtaGroupKind in LLVM
82static llvm::nvvm::CTAGroupKind
83getNVVMCtaGroupKind(NVVM::CTAGroupKind ctaGroup) {
84 switch (ctaGroup) {
85 case NVVM::CTAGroupKind::CTA_1:
86 return llvm::nvvm::CTAGroupKind::CG_1;
87 case NVVM::CTAGroupKind::CTA_2:
88 return llvm::nvvm::CTAGroupKind::CG_2;
89 }
90 llvm_unreachable("unsupported cta_group value");
91}
92
93//===----------------------------------------------------------------------===//
94// Verifier methods
95//===----------------------------------------------------------------------===//
96
97// This verifier is shared among the following Ops:
98// CpAsyncBulkTensorSharedCTAToGlobalOp (TMA Store)
99// CpAsyncBulkTensorReduceOp (TMA Store-Reduce)
100static LogicalResult cpAsyncBulkTensorCommonVerifier(size_t tensorDims,
101 bool isIm2Col,
102 size_t numIm2ColOffsets,
103 Location loc) {
104 if (tensorDims < 1 || tensorDims > 5)
105 return emitError(loc, "expects coordinates between 1 to 5 dimension");
106
107 // For Im2Col mode, there are two constraints:
108 if (isIm2Col) {
109 // 1. Tensor must always be at least 3-d.
110 if (tensorDims < 3)
111 return emitError(
112 loc,
113 "to use im2col mode, the tensor has to be at least 3-dimensional");
114 // 2. When there are Im2ColOffsets, they must be (Dims - 2) in number.
115 if (numIm2ColOffsets && (tensorDims != (numIm2ColOffsets + 2)))
116 return emitError(
117 loc, "im2col offsets must be 2 less than number of coordinates");
118 }
119 return success();
120}
121
122LogicalResult CpAsyncBulkTensorSharedCTAToGlobalOp::verify() {
123 TMAStoreMode mode = getMode();
124 // We lower through inline-ptx when getPredicate() is true.
125 // a) Only TILE mode is supported
126 // b) Cache-hint is not supported
127 if (getPredicate()) {
128 if (mode != TMAStoreMode::TILE)
129 return emitError("Inline-ptx lowering supported only for Tile mode.");
130 if (getL2CacheHint())
131 return emitError("Inline-ptx lowering unsupported with L2 cache-hint.");
132 }
133
134 size_t dims = getCoordinates().size();
135 switch (mode) {
136 case TMAStoreMode::TILE:
137 return cpAsyncBulkTensorCommonVerifier(dims, false, 0, getLoc());
138 case TMAStoreMode::IM2COL:
139 return cpAsyncBulkTensorCommonVerifier(dims, true, 0, getLoc());
140 case TMAStoreMode::TILE_SCATTER4:
141 if (dims != 5)
142 return emitError("Scatter4 mode expects 5 coordinates");
143 }
144 return success();
145}
146
147LogicalResult CpAsyncOp::verify() {
148 if (getModifier() != LoadCacheModifierKind::CG &&
149 getModifier() != LoadCacheModifierKind::CA)
150 return emitError("Only CG and CA cache modifiers are supported.");
151 if (getSize() != 4 && getSize() != 8 && getSize() != 16)
152 return emitError("expected byte size to be either 4, 8 or 16.");
153 if (getModifier() == LoadCacheModifierKind::CG && getSize() != 16)
154 return emitError("CG cache modifier is only support for 16 bytes copy.");
155 return success();
156}
157
158// This verify params can be shared across TMA Load and Prefetch Ops.
159static LogicalResult verifyTMALoadParams(size_t tensorDims, size_t numIm2colOff,
160 TMALoadMode mode, Location loc) {
161 if (tensorDims < 1 || tensorDims > 5)
162 return emitError(loc, "expects coordinates between 1 to 5 dimension");
163
164 auto checkTMALoadParams = [&](TMALoadMode mode, bool isIm2col,
165 size_t expectedIm2colOff) -> LogicalResult {
166 if (isIm2col && (tensorDims < 3))
167 return emitError(loc)
168 << "to use " << mode
169 << " mode, the tensor has to be at least 3-dimensional";
170
171 if (numIm2colOff != expectedIm2colOff)
172 return emitError(loc) << " im2col offsets expected " << expectedIm2colOff
173 << " (provided " << numIm2colOff << ")";
174
175 return success();
176 };
177
178 switch (mode) {
179 case TMALoadMode::TILE:
180 return checkTMALoadParams(mode, false, 0);
181 case TMALoadMode::IM2COL:
182 return checkTMALoadParams(mode, true, tensorDims - 2);
183 case TMALoadMode::IM2COL_W:
184 case TMALoadMode::IM2COL_W_128:
185 return checkTMALoadParams(mode, true, 2);
186 case TMALoadMode::TILE_GATHER4:
187 return (tensorDims == 5)
188 ? checkTMALoadParams(mode, false, 0)
189 : emitError(loc, "Gather4 mode expects 5 coordinates");
190 }
191 return success();
192}
193
194LogicalResult CpAsyncBulkTensorPrefetchOp::verify() {
195 return verifyTMALoadParams(getCoordinates().size(), getIm2colOffsets().size(),
196 getMode(), getLoc());
197}
198
199LogicalResult CpAsyncBulkTensorGlobalToSharedClusterOp::verify() {
200 TMALoadMode mode = getMode();
201 bool isCTAOnly = getIsCTAOnly();
202 if (getPredicate()) { // Inline-asm based lowering
203 if (isCTAOnly)
204 return emitError("Predicate is supported only for shared::cluster mode.");
205 if (mode != TMALoadMode::TILE && mode != TMALoadMode::IM2COL)
206 return emitError(
207 "Predicate is supported only for Tile and Im2col modes.");
208 } else { // Intrinsics-based lowering
209 NVVMMemorySpace expectedAS =
210 isCTAOnly ? NVVMMemorySpace::Shared : NVVMMemorySpace::SharedCluster;
211 unsigned AS = llvm::cast<LLVM::LLVMPointerType>(getDstMem().getType())
212 .getAddressSpace();
213 if (AS != expectedAS)
214 return emitError()
215 << (isCTAOnly
216 ? "Shared::cta destination requires address-space 3."
217 : "Shared::cluster destination requires address-space 7.");
218 // Checks specific to shared::cta mode
219 if (isCTAOnly) {
220 if (getMulticastMask())
221 return emitError("Multicast is not supported with shared::cta mode.");
222 if (getGroup())
223 return emitError("CTAGroup is not supported with shared::cta mode.");
224 }
225 }
226
227 return verifyTMALoadParams(getCoordinates().size(), getIm2colOffsets().size(),
228 getMode(), getLoc());
229}
230
231LogicalResult CpAsyncBulkTensorReduceOp::verify() {
232 TMAStoreMode mode = getMode();
233 size_t dims = getCoordinates().size();
234 switch (mode) {
235 case TMAStoreMode::TILE:
236 return cpAsyncBulkTensorCommonVerifier(dims, false, 0, getLoc());
237 case TMAStoreMode::IM2COL:
238 return cpAsyncBulkTensorCommonVerifier(dims, true, 0, getLoc());
239 case TMAStoreMode::TILE_SCATTER4:
240 return emitError("Scatter mode unsupported for CpAsyncBulkTensorReduceOp");
241 }
242 return success();
243}
244
245LogicalResult CpAsyncBulkGlobalToSharedClusterOp::verify() {
246 bool isSharedCTA = isPtrInSharedCTASpace(getDstMem());
247 if (isSharedCTA && getMulticastMask())
248 return emitError("Multicast is not supported with shared::cta mode.");
249
250 return success();
251}
252
253static LogicalResult verifyMBarrierArriveLikeOp(Operation *op, Value addr,
254 NVVM::MemScopeKind scope,
255 Value retVal = nullptr) {
256 if (scope != NVVM::MemScopeKind::CTA && scope != NVVM::MemScopeKind::CLUSTER)
257 return op->emitError("mbarrier scope must be either CTA or Cluster");
258
259 bool isSharedCluster = isPtrInSharedClusterSpace(addr);
260 bool hasRetValue = static_cast<bool>(retVal);
261 if (isSharedCluster && hasRetValue)
262 return op->emitError(
263 "mbarrier in shared_cluster space cannot return any value");
264
265 return success();
266}
267
268LogicalResult MBarrierArriveOp::verify() {
269 return verifyMBarrierArriveLikeOp(getOperation(), getAddr(), getScope(),
270 getRes());
271}
272
273LogicalResult MBarrierArriveDropOp::verify() {
274 return verifyMBarrierArriveLikeOp(getOperation(), getAddr(), getScope(),
275 getRes());
276}
277
278LogicalResult MBarrierArriveExpectTxOp::verify() {
279 // The inline-ptx version of this Op does not support all features.
280 // With predicate, this Op lowers to inline-ptx. So, verify and
281 // error-out if there are unsupported features.
282 if (getPredicate()) {
283 if (getScope() != NVVM::MemScopeKind::CTA)
284 return emitError("mbarrier scope must be CTA when using predicate");
285
286 if (isPtrInSharedClusterSpace(getAddr()))
287 return emitError("mbarrier in shared_cluster space is not supported when "
288 "using predicate");
289
290 if (getRes())
291 return emitError("return-value is not supported when using predicate");
292
293 if (getRelaxed() == true)
294 return emitError("mbarrier with relaxed semantics is not supported when "
295 "using predicate");
296 }
297 return verifyMBarrierArriveLikeOp(getOperation(), getAddr(), getScope(),
298 getRes());
299}
300
301LogicalResult MBarrierArriveDropExpectTxOp::verify() {
302 return verifyMBarrierArriveLikeOp(getOperation(), getAddr(), getScope(),
303 getRes());
304}
305
306//===----------------------------------------------------------------------===//
307// inferReturnTypes for mbarrier arrive-like ops
308//===----------------------------------------------------------------------===//
309
310/// Only shared_cluster (ptr<7>) produces zero results; all other address
311/// spaces (including generic) return i64.
312static LogicalResult
314 SmallVectorImpl<Type> &inferredReturnTypes) {
315 if (!isPtrInSharedClusterSpace(addr))
316 inferredReturnTypes.push_back(IntegerType::get(context, 64));
317 return success();
318}
319
320LogicalResult
321MBarrierArriveOp::inferReturnTypes(MLIRContext *context,
322 std::optional<Location> location,
323 MBarrierArriveOp::Adaptor adaptor,
324 SmallVectorImpl<Type> &inferredReturnTypes) {
325 return inferMBarrierArriveResultTypes(context, adaptor.getAddr(),
326 inferredReturnTypes);
327}
328
329LogicalResult MBarrierArriveDropOp::inferReturnTypes(
330 MLIRContext *context, std::optional<Location> location,
331 MBarrierArriveDropOp::Adaptor adaptor,
332 SmallVectorImpl<Type> &inferredReturnTypes) {
333 return inferMBarrierArriveResultTypes(context, adaptor.getAddr(),
334 inferredReturnTypes);
335}
336
337LogicalResult MBarrierArriveExpectTxOp::inferReturnTypes(
338 MLIRContext *context, std::optional<Location> location,
339 MBarrierArriveExpectTxOp::Adaptor adaptor,
340 SmallVectorImpl<Type> &inferredReturnTypes) {
341 // Predicate forces no return value (inline PTX path).
342 // Note: predicate + shared_cluster is rejected by the verifier separately.
343 if (adaptor.getPredicate())
344 return success();
345 return inferMBarrierArriveResultTypes(context, adaptor.getAddr(),
346 inferredReturnTypes);
347}
348
349LogicalResult MBarrierArriveDropExpectTxOp::inferReturnTypes(
350 MLIRContext *context, std::optional<Location> location,
351 MBarrierArriveDropExpectTxOp::Adaptor adaptor,
352 SmallVectorImpl<Type> &inferredReturnTypes) {
353 return inferMBarrierArriveResultTypes(context, adaptor.getAddr(),
354 inferredReturnTypes);
355}
356
357/// For ops with optional results, allow the user to omit the result even when
358/// inference would produce one. This preserves backward compatibility: the
359/// result can be silently discarded (e.g., for fire-and-forget arrive ops).
361 TypeRange actual) {
362 if (actual.empty())
363 return true;
364 return inferred == actual;
365}
366
367bool MBarrierArriveOp::isCompatibleReturnTypes(TypeRange l, TypeRange r) {
369}
370bool MBarrierArriveDropOp::isCompatibleReturnTypes(TypeRange l, TypeRange r) {
372}
373bool MBarrierArriveExpectTxOp::isCompatibleReturnTypes(TypeRange l,
374 TypeRange r) {
376}
377bool MBarrierArriveDropExpectTxOp::isCompatibleReturnTypes(TypeRange l,
378 TypeRange r) {
380}
381
382LogicalResult MBarrierExpectTxOp::verify() {
383 return verifyMBarrierArriveLikeOp(getOperation(), getAddr(), getScope());
384}
385
386LogicalResult MBarrierCompleteTxOp::verify() {
387 return verifyMBarrierArriveLikeOp(getOperation(), getAddr(), getScope());
388}
389
390LogicalResult MBarrierTestWaitOp::verify() {
391 return verifyMBarrierArriveLikeOp(getOperation(), getAddr(), getScope());
392}
393
394LogicalResult MBarrierTryWaitOp::verify() {
395 return verifyMBarrierArriveLikeOp(getOperation(), getAddr(), getScope());
396}
397
398LogicalResult ConvertFloatToTF32Op::verify() {
399 using RndMode = NVVM::FPRoundingMode;
400 switch (getRnd()) {
401 case RndMode::RNA:
402 if (getRelu())
403 return emitError("Relu not supported with rna rounding mode.");
404 break;
405 case RndMode::RN:
406 case RndMode::RZ:
407 break;
408 default:
409 return emitError(
410 "Only {rn,rz,rna} rounding modes supported for ConvertFloatToTF32Op.");
411 }
412 return success();
413}
414
415LogicalResult ConvertF32x2ToF6x2Op::verify() {
417
418 if (!llvm::isa<mlir::Float6E2M3FNType, mlir::Float6E3M2FNType>(getDstTy())) {
419 return emitOpError("Only ")
420 << mlir::Float6E2M3FNType::get(ctx) << " and "
421 << mlir::Float6E3M2FNType::get(ctx)
422 << " types are supported for conversions from f32x2 to f6x2.";
423 }
424 return success();
425}
426
427LogicalResult ConvertF32x2ToF8x2Op::verify() {
428 using RndMode = NVVM::FPRoundingMode;
429 using SatMode = NVVM::SaturationMode;
430
431 bool isRoundingModeRN = getRnd() == RndMode::RN;
432 bool isRoundingModeRZ = getRnd() == RndMode::RZ;
433 bool isRoundingModeRP = getRnd() == RndMode::RP;
434 bool isSatFinite = getSat() == SatMode::SATFINITE;
435
436 bool hasRelu = getRelu();
437
439
441 .Case<mlir::Float8E4M3FNType, mlir::Float8E5M2Type>(
442 [&](mlir::Type) -> LogicalResult {
443 if (!isRoundingModeRN) {
444 return emitOpError("Only RN rounding mode is supported for "
445 "conversions from f32x2 to ")
446 << mlir::Float8E4M3FNType::get(ctx) << " and "
447 << mlir::Float8E5M2Type::get(ctx) << " types";
448 }
449 if (!isSatFinite) {
450 return emitOpError("Only SATFINITE saturation mode is supported "
451 "for conversions "
452 "from f32x2 to ")
453 << mlir::Float8E4M3FNType::get(ctx) << " and "
454 << mlir::Float8E5M2Type::get(ctx) << " types";
455 }
456 return success();
457 })
458 .Case<mlir::Float8E8M0FNUType>([&](mlir::Type) -> LogicalResult {
459 if (!(isRoundingModeRZ || isRoundingModeRP)) {
460 return emitOpError("Only RZ and RP rounding modes are supported for "
461 "conversions from f32x2 to ")
462 << mlir::Float8E8M0FNUType::get(ctx) << " type";
463 }
464 if (hasRelu) {
465 return emitOpError("relu not supported for conversions to ")
466 << mlir::Float8E8M0FNUType::get(ctx) << " type";
467 }
468 return success();
469 })
470 .Default([&](mlir::Type) {
471 return emitOpError("Only ")
472 << mlir::Float8E4M3FNType::get(ctx) << ", "
473 << mlir::Float8E5M2Type::get(ctx) << ", and "
474 << mlir::Float8E8M0FNUType::get(ctx)
475 << " types are "
476 "supported for conversions from f32x2 to f8x2";
477 });
478}
479
480LogicalResult ConvertF16x2ToF8x2Op::verify() {
482
483 if (!llvm::isa<mlir::Float8E4M3FNType, mlir::Float8E5M2Type>(getDstTy())) {
484 return emitOpError("Only ")
485 << mlir::Float8E4M3FNType::get(ctx) << " and "
486 << mlir::Float8E5M2Type::get(ctx)
487 << " types are supported for conversions from f16x2 to f8x2.";
488 }
489 return success();
490}
491
492LogicalResult ConvertBF16x2ToF8x2Op::verify() {
493 using RndMode = NVVM::FPRoundingMode;
494 using SatMode = NVVM::SaturationMode;
495
496 bool isRoundingModeRN = getRnd() == RndMode::RN;
497 bool isRoundingModeRZ = getRnd() == RndMode::RZ;
498 bool isRoundingModeRP = getRnd() == RndMode::RP;
499 bool isSatFinite = getSat() == SatMode::SATFINITE;
500 bool hasRelu = getRelu();
501
503
505 .Case<mlir::Float8E4M3FNType, mlir::Float8E5M2Type>(
506 [&](mlir::Type) -> LogicalResult {
507 if (!isRoundingModeRN)
508 return emitOpError("Only RN rounding mode is supported for "
509 "conversions from bf16x2 to ")
510 << mlir::Float8E4M3FNType::get(ctx) << " and "
511 << mlir::Float8E5M2Type::get(ctx) << " types";
512 if (!isSatFinite)
513 return emitOpError("Only SATFINITE saturation mode is supported "
514 "for conversions from bf16x2 to ")
515 << mlir::Float8E4M3FNType::get(ctx) << " and "
516 << mlir::Float8E5M2Type::get(ctx) << " types";
517 return success();
518 })
519 .Case<mlir::Float8E8M0FNUType>([&](mlir::Type) -> LogicalResult {
520 if (!(isRoundingModeRZ || isRoundingModeRP))
521 return emitOpError("Only RZ and RP rounding modes are supported for "
522 "conversions from bf16x2 to ")
523 << mlir::Float8E8M0FNUType::get(ctx) << " type";
524 if (hasRelu)
525 return emitOpError("relu not supported for conversions to ")
526 << mlir::Float8E8M0FNUType::get(ctx) << " type";
527 return success();
528 })
529 .Default([&](mlir::Type) -> LogicalResult {
530 llvm_unreachable("Invalid conversion in ConvertBF16x2ToF8x2Op");
531 return failure();
532 });
533}
534
535LogicalResult ConvertF32x2ToF4x2Op::verify() {
537
538 if (!llvm::isa<mlir::Float4E2M1FNType>(getDstTy()))
539 return emitOpError("Only ")
540 << mlir::Float4E2M1FNType::get(ctx)
541 << " type is supported for conversions from f32x2 to f4x2.";
542
543 return success();
544}
545
546LogicalResult ConvertF8x2ToBF16x2Op::verify() {
548 if (llvm::isa<Float8E8M0FNUType>(getSrcType())) {
549 if (getSat() != SaturationMode::NONE)
550 return emitOpError(
551 "Only NONE saturation mode is supported for conversions from ")
552 << Float8E8M0FNUType::get(ctx) << " type";
553 if (getScaleFactor())
554 return emitOpError("scaleFactor not supported for conversions from ")
555 << Float8E8M0FNUType::get(ctx) << " type";
556 if (getRelu())
557 return emitOpError("relu not supported for conversions from ")
558 << Float8E8M0FNUType::get(ctx) << " type";
559 }
560
561 return success();
562}
563
564LogicalResult PermuteOp::verify() {
565 using Mode = NVVM::PermuteMode;
566 bool hasHi = static_cast<bool>(getHi());
567
568 switch (getMode()) {
569 case Mode::DEFAULT:
570 case Mode::F4E:
571 case Mode::B4E:
572 if (!hasHi)
573 return emitError("mode '") << getMode() << "' requires 'hi' operand.";
574 break;
575 case Mode::RC8:
576 case Mode::ECL:
577 case Mode::ECR:
578 case Mode::RC16:
579 if (hasHi)
580 return emitError("mode '")
581 << getMode() << "' does not accept 'hi' operand.";
582 break;
583 }
584
585 return success();
586}
587
588//===----------------------------------------------------------------------===//
589// Stochastic Rounding Conversion Ops
590//===----------------------------------------------------------------------===//
591
592static LogicalResult verifyConvertF32x2ToFP16x2Op(Twine dstType,
593 FPRoundingMode rnd,
594 bool hasRandomBits,
595 Operation *op) {
596 static constexpr FPRoundingMode validRndModes[] = {
597 FPRoundingMode::RN, FPRoundingMode::RZ, FPRoundingMode::RS};
598
599 if (!llvm::is_contained(validRndModes, rnd)) {
600 return op->emitOpError(
601 "Only RN, RZ, and RS rounding modes are supported for "
602 "conversions from f32x2 to ")
603 << dstType << ".";
604 }
605
606 if (rnd == FPRoundingMode::RS) {
607 if (!hasRandomBits) {
608 return op->emitOpError("random_bits is required for RS rounding mode.");
609 }
610 } else {
611 if (hasRandomBits) {
612 return op->emitOpError(
613 "random_bits not supported for RN and RZ rounding modes.");
614 }
615 }
616
617 return success();
618}
619
620LogicalResult ConvertF32x2ToF16x2Op::verify() {
621 return verifyConvertF32x2ToFP16x2Op("f16x2", getRnd(),
622 getRandomBits() ? true : false, *this);
623}
624
625LogicalResult ConvertF32x2ToBF16x2Op::verify() {
626 return verifyConvertF32x2ToFP16x2Op("bf16x2", getRnd(),
627 getRandomBits() ? true : false, *this);
628}
629
630LogicalResult ConvertF32x4ToF8x4Op::verify() {
632
633 if (!llvm::isa<mlir::Float8E4M3FNType, mlir::Float8E5M2Type>(getDstTy()))
634 return emitOpError("Only ")
635 << mlir::Float8E4M3FNType::get(ctx) << " and "
636 << mlir::Float8E5M2Type::get(ctx)
637 << " types are supported for conversions from f32x4 to f8x4.";
638
639 return success();
640}
641
642LogicalResult ConvertF32x4ToF6x4Op::verify() {
644
645 if (!llvm::isa<mlir::Float6E2M3FNType, mlir::Float6E3M2FNType>(getDstTy()))
646 return emitOpError("Only ")
647 << mlir::Float6E2M3FNType::get(ctx) << " and "
648 << mlir::Float6E3M2FNType::get(ctx)
649 << " types are supported for conversions from f32x4 to f6x4.";
650
651 return success();
652}
653
654LogicalResult ConvertF32x4ToF4x4Op::verify() {
656
657 if (!llvm::isa<mlir::Float4E2M1FNType>(getDstTy()))
658 return emitOpError("Only ") << mlir::Float4E2M1FNType::get(ctx)
659 << " type is supported for conversions from "
660 "f32x4 to f4x4.";
661
662 return success();
663}
664
665LogicalResult BulkStoreOp::verify() {
666 if (getInitVal() != 0)
667 return emitOpError("only 0 is supported for initVal, got ") << getInitVal();
668 return success();
669}
670
671LogicalResult PMEventOp::verify() {
672 auto eventId = getEventId();
673 auto maskedEventId = getMaskedEventId();
674 if (!maskedEventId && !eventId) {
675 return emitOpError() << "either `id` or `mask` must be set";
676 }
677
678 if (maskedEventId && eventId) {
679 return emitOpError() << "`id` and `mask` cannot be set at the same time";
680 }
681
682 if (eventId) {
683 if (eventId < 0 || eventId > 15) {
684 return emitOpError() << "`id` must be between 0 and 15";
685 }
686 }
687
688 return llvm::success();
689}
690
691// Given the element type of an operand and whether or not it is an accumulator,
692// this function returns the PTX type (`NVVM::MMATypes`) that corresponds to the
693// operand's element type.
694std::optional<mlir::NVVM::MMATypes>
695MmaOp::inferOperandMMAType(Type operandElType, bool isAccumulator) {
696 auto half2Type =
697 VectorType::get(2, Float16Type::get(operandElType.getContext()));
698 if (operandElType.isF64())
699 return NVVM::MMATypes::f64;
700 if (operandElType.isF16() || operandElType == half2Type)
701 return NVVM::MMATypes::f16;
702 if (operandElType.isF32() && isAccumulator)
703 return NVVM::MMATypes::f32;
704 if (operandElType.isF32() && !isAccumulator)
705 return NVVM::MMATypes::tf32;
706 if (llvm::isa<IntegerType>(operandElType)) {
707 if (isAccumulator)
708 return NVVM::MMATypes::s32;
709 return std::nullopt;
710 }
711
712 if (auto structType = llvm::dyn_cast<LLVM::LLVMStructType>(operandElType)) {
713 if (structType.getBody().empty())
714 return std::nullopt;
715 return inferOperandMMAType(structType.getBody()[0], isAccumulator);
716 }
717
718 return std::nullopt;
719}
720
721static bool isInt4PtxType(MMATypes type) {
722 return (type == MMATypes::u4 || type == MMATypes::s4);
723}
724
725static bool isInt8PtxType(MMATypes type) {
726 return (type == MMATypes::u8 || type == MMATypes::s8);
727}
728
729static bool isIntegerPtxType(MMATypes type) {
730 return isInt4PtxType(type) || isInt8PtxType(type) || type == MMATypes::b1 ||
731 type == MMATypes::s32;
732}
733
734MMATypes MmaOp::accumPtxType() {
735 std::optional<mlir::NVVM::MMATypes> val = inferOperandMMAType(
736 getODSOperands(2).getTypes().front(), /*isAccumulator=*/true);
737 assert(val.has_value() && "accumulator PTX type should always be inferrable");
738 return val.value();
739}
740
741MMATypes MmaOp::resultPtxType() {
742 std::optional<mlir::NVVM::MMATypes> val =
743 inferOperandMMAType(getResult().getType(), /*isAccumulator=*/true);
744 assert(val.has_value() && "result PTX type should always be inferrable");
745 return val.value();
746}
747
748void MmaOp::print(OpAsmPrinter &p) {
749 SmallVector<Type, 4> regTypes;
750 struct MMAOperandFragment {
751 StringRef operandName;
752 StringRef ptxTypeAttr;
753 SmallVector<Value, 4> regs;
754 explicit MMAOperandFragment(StringRef name, StringRef ptxTypeName)
755 : operandName(name), ptxTypeAttr(ptxTypeName) {}
756 };
757
758 std::array<MMAOperandFragment, 3> frags{
759 MMAOperandFragment("A", getMultiplicandAPtxTypeAttrName()),
760 MMAOperandFragment("B", getMultiplicandBPtxTypeAttrName()),
761 MMAOperandFragment("C", "")};
762 SmallVector<StringRef, 4> ignoreAttrNames{
763 mlir::NVVM::MmaOp::getOperandSegmentSizeAttr()};
764
765 for (unsigned fragIdx = 0; fragIdx < frags.size(); fragIdx++) {
766 auto &frag = frags[fragIdx];
767 auto varOperandSpec = getODSOperandIndexAndLength(fragIdx);
768 for (auto operandIdx = varOperandSpec.first;
769 operandIdx < varOperandSpec.first + varOperandSpec.second;
770 operandIdx++) {
771 frag.regs.push_back(this->getOperand(operandIdx));
772 if (operandIdx == 0) {
773 regTypes.push_back(this->getOperand(operandIdx).getType());
774 }
775 }
776 std::optional<MMATypes> inferredType = MmaOp::inferOperandMMAType(
777 regTypes.back(), /*isAccumulator=*/fragIdx >= 2);
778 if (inferredType)
779 ignoreAttrNames.push_back(frag.ptxTypeAttr);
780 }
781
782 auto printMmaOperand = [&](const MMAOperandFragment &frag) -> void {
783 p << " " << frag.operandName;
784 p << "[";
785 p.printOperands(frag.regs);
786 p << "] ";
787 };
788
789 for (const auto &frag : frags) {
790 printMmaOperand(frag);
791 }
792
793 p.printOptionalAttrDict(this->getOperation()->getAttrs(), ignoreAttrNames);
794
795 // Print the types of the operands and result.
796 p << " : " << "(";
797 llvm::interleaveComma(SmallVector<Type, 3>{frags[0].regs[0].getType(),
798 frags[1].regs[0].getType(),
799 frags[2].regs[0].getType()},
800 p);
801 p << ")";
802 p.printArrowTypeList(TypeRange{this->getRes().getType()});
803}
804
805void MmaOp::build(OpBuilder &builder, OperationState &result, Type resultType,
806 ValueRange operandA, ValueRange operandB, ValueRange operandC,
807 ArrayRef<int64_t> shape, std::optional<MMAB1Op> b1Op,
808 std::optional<MMAIntOverflow> intOverflow,
809 std::optional<std::array<MMATypes, 2>> multiplicandPtxTypes,
810 std::optional<std::array<MMALayout, 2>> multiplicandLayouts) {
811
812 assert(shape.size() == 3 && "expected shape to have size 3 (m, n, k)");
813 MLIRContext *ctx = builder.getContext();
814 result.addAttribute(
815 "shape", builder.getAttr<MMAShapeAttr>(shape[0], shape[1], shape[2]));
816
817 result.addOperands(operandA);
818 result.addOperands(operandB);
819 result.addOperands(operandC);
820
821 if (multiplicandPtxTypes) {
822 result.addAttribute("multiplicandAPtxType",
823 MMATypesAttr::get(ctx, (*multiplicandPtxTypes)[0]));
824 result.addAttribute("multiplicandBPtxType",
825 MMATypesAttr::get(ctx, (*multiplicandPtxTypes)[1]));
826 } else {
827 if (auto res = inferOperandMMAType(operandA[0].getType(), false))
828 result.addAttribute("multiplicandAPtxType", MMATypesAttr::get(ctx, *res));
829 if (auto res = inferOperandMMAType(operandB[0].getType(), false))
830 result.addAttribute("multiplicandBPtxType", MMATypesAttr::get(ctx, *res));
831 }
832
833 if (multiplicandLayouts) {
834 result.addAttribute("layoutA",
835 MMALayoutAttr::get(ctx, (*multiplicandLayouts)[0]));
836 result.addAttribute("layoutB",
837 MMALayoutAttr::get(ctx, (*multiplicandLayouts)[1]));
838 } else {
839 result.addAttribute("layoutA", MMALayoutAttr::get(ctx, MMALayout::row));
840 result.addAttribute("layoutB", MMALayoutAttr::get(ctx, MMALayout::col));
841 }
842
843 if (intOverflow.has_value())
844 result.addAttribute("intOverflowBehavior",
845 MMAIntOverflowAttr::get(ctx, *intOverflow));
846 if (b1Op.has_value())
847 result.addAttribute("b1Op", MMAB1OpAttr::get(ctx, *b1Op));
848
849 result.addTypes(resultType);
850 result.addAttribute(
851 MmaOp::getOperandSegmentSizeAttr(),
852 builder.getDenseI32ArrayAttr({static_cast<int32_t>(operandA.size()),
853 static_cast<int32_t>(operandB.size()),
854 static_cast<int32_t>(operandC.size())}));
855}
856
857// <operation> :=
858// A `[` $operandA `]` B `[` $operandB `]` C `[` $operandC `]`
859// attr-dict : (type($operandA[0]), type($operandB[0]), type($operandC[0]))
860// `->` type($res)
861ParseResult MmaOp::parse(OpAsmParser &parser, OperationState &result) {
862 struct MMAOperandFragment {
863 std::optional<MMATypes> elemtype;
864 SmallVector<OpAsmParser::UnresolvedOperand, 4> regs;
865 SmallVector<Type> regTypes;
866 };
867
868 Builder &builder = parser.getBuilder();
869 std::array<MMAOperandFragment, 4> frags;
870
871 NamedAttrList namedAttributes;
872
873 // A helper to parse the operand segments.
874 auto parseMmaOperand = [&](StringRef operandName,
875 MMAOperandFragment &frag) -> LogicalResult {
876 if (parser.parseKeyword(operandName).failed())
877 return failure();
878 if (parser
879 .parseOperandList(frag.regs, OpAsmParser::Delimiter::OptionalSquare)
880 .failed())
881 return failure();
882 return success();
883 };
884
885 // Parse the operand segments.
886 if (parseMmaOperand("A", frags[0]).failed())
887 return failure();
888 if (parseMmaOperand("B", frags[1]).failed())
889 return failure();
890 if (parseMmaOperand("C", frags[2]).failed())
891 return failure();
892
893 if (parser.parseOptionalAttrDict(namedAttributes).failed())
894 return failure();
895
896 // Parse the type specification and resolve operands.
897 SmallVector<Type, 3> operandTypes;
898 if (failed(parser.parseColon()))
899 return failure();
900 if (failed(parser.parseLParen()))
901 return failure();
902 if (failed(parser.parseTypeList(operandTypes)))
903 return failure();
904 if (failed(parser.parseRParen()))
905 if (operandTypes.size() != 3)
906 return parser.emitError(
907 parser.getNameLoc(),
908 "expected one type for each operand segment but got " +
909 Twine(operandTypes.size()) + " types");
910 for (const auto &iter : llvm::enumerate(operandTypes)) {
911 auto &frag = frags[iter.index()];
912 frag.regTypes.resize(frag.regs.size(), iter.value());
913 if (failed(parser.resolveOperands(frag.regs, frag.regTypes,
914 parser.getNameLoc(), result.operands)))
915 return failure();
916 frag.elemtype = inferOperandMMAType(frag.regTypes[0],
917 /*isAccumulator*/ iter.index() < 2);
918 }
919
920 Type resultType;
921 if (parser.parseArrow() || parser.parseType(resultType))
922 return failure();
923 frags[3].elemtype = inferOperandMMAType(resultType, /*isAccumulator*/ true);
924
925 std::array<StringRef, 2> names{"multiplicandAPtxType",
926 "multiplicandBPtxType"};
927 for (unsigned idx = 0; idx < names.size(); idx++) {
928 const auto &frag = frags[idx];
929 std::optional<NamedAttribute> attr = namedAttributes.getNamed(names[idx]);
930 if (!frag.elemtype.has_value() && !attr.has_value()) {
931 return parser.emitError(
932 parser.getNameLoc(),
933 "attribute " + names[idx] +
934 " is not provided explicitly and cannot be inferred");
935 }
936 if (!attr.has_value())
937 result.addAttribute(
938 names[idx], MMATypesAttr::get(parser.getContext(), *frag.elemtype));
939 }
940
941 result.addTypes(resultType);
942 if (!namedAttributes.empty())
943 result.addAttributes(namedAttributes);
944 result.addAttribute(MmaOp::getOperandSegmentSizeAttr(),
945 builder.getDenseI32ArrayAttr({
946 static_cast<int32_t>(frags[0].regs.size()),
947 static_cast<int32_t>(frags[1].regs.size()),
948 static_cast<int32_t>(frags[2].regs.size()),
949 }));
950 return success();
951}
952
953LogicalResult MmaOp::verify() {
954 MLIRContext *context = getContext();
955 auto f16Ty = Float16Type::get(context);
956 auto i32Ty = IntegerType::get(context, 32);
957 auto f16x2Ty = VectorType::get(2, f16Ty);
958 auto f32Ty = Float32Type::get(context);
959 auto f16x2x4StructTy = LLVM::LLVMStructType::getLiteral(
960 context, {f16x2Ty, f16x2Ty, f16x2Ty, f16x2Ty});
961
962 auto s32x4StructTy =
963 LLVM::LLVMStructType::getLiteral(context, {i32Ty, i32Ty, i32Ty, i32Ty});
964 auto f32x8StructTy =
965 LLVM::LLVMStructType::getLiteral(context, SmallVector<Type>(8, f32Ty));
966 auto f16x2x2StructTy =
967 LLVM::LLVMStructType::getLiteral(context, {f16x2Ty, f16x2Ty});
968 auto f32x4StructTy =
969 LLVM::LLVMStructType::getLiteral(context, {f32Ty, f32Ty, f32Ty, f32Ty});
970 auto s32x2StructTy =
971 LLVM::LLVMStructType::getLiteral(context, {i32Ty, i32Ty});
972
973 std::array<int64_t, 3> mmaShape{getShapeAttr().getM(), getShapeAttr().getN(),
974 getShapeAttr().getK()};
975
976 // These variables define the set of allowed data types for matrices A, B, C,
977 // and result.
978 using AllowedShapes = SmallVector<std::array<int64_t, 3>, 2>;
979 using AllowedTypes = SmallVector<SmallVector<Type, 4>, 2>;
980 AllowedShapes allowedShapes;
981 AllowedTypes expectedA;
982 AllowedTypes expectedB;
983 AllowedTypes expectedC;
984 SmallVector<Type> expectedResult;
985
986 // When M = 16, we just need to calculate the number of 8xk tiles, where
987 // k is a factor that depends on the data type.
988 if (mmaShape[0] == 16) {
989 int64_t kFactor;
990 Type multiplicandFragType;
991 switch (*getMultiplicandAPtxType()) {
992 case MMATypes::tf32:
993 kFactor = 4;
994 multiplicandFragType = i32Ty;
995 expectedResult.push_back(LLVM::LLVMStructType::getLiteral(
996 context, {f32Ty, f32Ty, f32Ty, f32Ty}));
997 break;
998 case MMATypes::bf16:
999 kFactor = 8;
1000 multiplicandFragType = i32Ty;
1001 expectedResult.push_back(LLVM::LLVMStructType::getLiteral(
1002 context, {f32Ty, f32Ty, f32Ty, f32Ty}));
1003 break;
1004 case MMATypes::f16:
1005 kFactor = 8;
1006 multiplicandFragType = f16x2Ty;
1007 expectedResult.push_back(f16x2x2StructTy);
1008 expectedResult.push_back(f32x4StructTy);
1009 break;
1010 case MMATypes::e4m3:
1011 case MMATypes::e5m2:
1012 // FP8 (m16n8k16 / m16n8k32) packs 4 values per 32-bit register, same
1013 // as s8/u8, but the accumulator is f16 or f32 (not integer).
1014 kFactor = 16;
1015 multiplicandFragType = i32Ty;
1016 expectedResult.push_back(f16x2x2StructTy);
1017 expectedResult.push_back(f32x4StructTy);
1018 break;
1019 case MMATypes::s4:
1020 case MMATypes::u4:
1021 kFactor = 32;
1022 break;
1023 case MMATypes::b1:
1024 kFactor = 128;
1025 break;
1026 case MMATypes::s8:
1027 case MMATypes::u8:
1028 kFactor = 16;
1029 break;
1030 default:
1031 return emitError("invalid shape or multiplicand type: ")
1032 << getMultiplicandAPtxType().value();
1033 }
1034
1035 if (isIntegerPtxType(getMultiplicandAPtxType().value())) {
1036 expectedResult.push_back(s32x4StructTy);
1037 expectedC.emplace_back(4, i32Ty);
1038 multiplicandFragType = i32Ty;
1039 } else {
1040 expectedC.emplace_back(2, f16x2Ty);
1041 expectedC.emplace_back(4, f32Ty);
1042 }
1043
1044 int64_t unitA = (mmaShape[0] / 8) * (mmaShape[2] / kFactor);
1045 int64_t unitB = (mmaShape[1] / 8) * (mmaShape[2] / kFactor);
1046 expectedA.emplace_back(unitA, multiplicandFragType);
1047 expectedB.emplace_back(unitB, multiplicandFragType);
1048 allowedShapes.push_back({16, 8, kFactor});
1049 allowedShapes.push_back({16, 8, kFactor * 2});
1050
1051 if (resultPtxType() != accumPtxType())
1052 return emitOpError("ctype does not match dtype");
1053 }
1054
1055 // In the M=8 case, there is only 1 possible case per data type.
1056 if (mmaShape[0] == 8) {
1057 if (*getMultiplicandAPtxType() == MMATypes::f16) {
1058 expectedA.emplace_back(2, f16x2Ty);
1059 expectedB.emplace_back(2, f16x2Ty);
1060 expectedResult.push_back(f16x2x4StructTy);
1061 expectedResult.push_back(f32x8StructTy);
1062 expectedC.emplace_back(4, f16x2Ty);
1063 expectedC.emplace_back(8, f32Ty);
1064 allowedShapes.push_back({8, 8, 4});
1065 }
1066 if (*getMultiplicandAPtxType() == MMATypes::f64) {
1067 Type f64Ty = Float64Type::get(context);
1068 expectedA.emplace_back(1, f64Ty);
1069 expectedB.emplace_back(1, f64Ty);
1070 expectedC.emplace_back(2, f64Ty);
1071 expectedResult.emplace_back(LLVM::LLVMStructType::getLiteral(
1072 context, SmallVector<Type>(2, f64Ty)));
1073 allowedShapes.push_back({8, 8, 4});
1074 }
1075 if (isIntegerPtxType(getMultiplicandAPtxType().value())) {
1076 expectedA.push_back({i32Ty});
1077 expectedB.push_back({i32Ty});
1078 expectedC.push_back({i32Ty, i32Ty});
1079 expectedResult.push_back(s32x2StructTy);
1080 if (isInt4PtxType(getMultiplicandAPtxType().value()))
1081 allowedShapes.push_back({8, 8, 32});
1082 if (isInt8PtxType(getMultiplicandAPtxType().value()))
1083 allowedShapes.push_back({8, 8, 16});
1084 if (getMultiplicandAPtxType().value() == MMATypes::b1)
1085 allowedShapes.push_back({8, 8, 128});
1086 }
1087 }
1088
1089 std::string errorMessage;
1090 llvm::raw_string_ostream errorStream(errorMessage);
1091
1092 // Check that we matched an existing shape/dtype combination.
1093 if (expectedA.empty() || expectedB.empty() || expectedC.empty() ||
1094 !llvm::is_contained(allowedShapes, mmaShape)) {
1095 errorStream << "unimplemented variant for MMA shape <";
1096 llvm::interleaveComma(mmaShape, errorStream);
1097 errorStream << ">";
1098 return emitOpError(errorMessage);
1099 }
1100
1101 // Verify the operand types for segments of A, B, and C operands.
1102 std::array<StringRef, 3> operandNames{"A", "B", "C"};
1103 for (const auto &iter : llvm::enumerate(
1104 SmallVector<AllowedTypes, 3>{expectedA, expectedB, expectedC})) {
1105 auto spec = this->getODSOperandIndexAndLength(iter.index());
1106 SmallVector<Type, 4> operandTySeg(operand_type_begin() + spec.first,
1107 operand_type_begin() + spec.first +
1108 spec.second);
1109 bool match = llvm::is_contained(iter.value(), operandTySeg);
1110
1111 if (!match) {
1112 errorStream << "Could not match types for the "
1113 << operandNames[iter.index()]
1114 << " operands; expected one of ";
1115 for (const auto &x : iter.value()) {
1116 errorStream << x.size() << "x" << x[0] << " ";
1117 }
1118 errorStream << "but got ";
1119 llvm::interleaveComma(operandTySeg, errorStream);
1120 return emitOpError(errorMessage);
1121 }
1122 }
1123
1124 // Check the result type
1125 if (!llvm::any_of(expectedResult, [&](Type expectedResultType) {
1126 return expectedResultType == getResult().getType();
1127 })) {
1128 errorStream
1129 << "Could not match allowed types for the result; expected one of ";
1130 llvm::interleaveComma(expectedResult, errorStream);
1131 errorStream << " but got " << getResult().getType();
1132 return emitOpError(errorMessage);
1133 }
1134
1135 // Ensure that binary MMA variants have a b1 MMA operation defined.
1136 if (getMultiplicandAPtxType() == MMATypes::b1 && !getB1Op()) {
1137 return emitOpError("op requires " + getB1OpAttrName().strref() +
1138 " attribute");
1139 }
1140
1141 // Ensure int4/int8 MMA variants specify the accum overflow behavior
1142 // attribute.
1143 if (isInt4PtxType(*getMultiplicandAPtxType()) ||
1144 isInt8PtxType(*getMultiplicandAPtxType())) {
1145 if (!getIntOverflowBehavior())
1146 return emitOpError("op requires " +
1147 getIntOverflowBehaviorAttrName().strref() +
1148 " attribute");
1149 }
1150
1151 // Validate layout combinations. According to the operation description, most
1152 // MMA operations require layoutA=row and layoutB=col. Only m8n8k4 with f16
1153 // can use other layout combinations.
1154 bool isM8N8K4_F16 =
1155 (mmaShape[0] == 8 && mmaShape[1] == 8 && mmaShape[2] == 4 &&
1156 getMultiplicandAPtxType() == MMATypes::f16);
1157
1158 if (!isM8N8K4_F16) {
1159 // For all other shapes/types, layoutA must be row and layoutB must be col
1160 if (getLayoutA() != MMALayout::row || getLayoutB() != MMALayout::col) {
1161 return emitOpError("requires layoutA = #nvvm.mma_layout<row> and "
1162 "layoutB = #nvvm.mma_layout<col> for shape <")
1163 << mmaShape[0] << ", " << mmaShape[1] << ", " << mmaShape[2]
1164 << "> with element types " << *getMultiplicandAPtxType() << " and "
1165 << *getMultiplicandBPtxType()
1166 << ". Only m8n8k4 with f16 supports other layouts.";
1167 }
1168 }
1169
1170 return success();
1171}
1172
1173MMATypes MmaSpOp::accumPtxType() {
1174 std::optional<mlir::NVVM::MMATypes> val = MmaOp::inferOperandMMAType(
1175 getODSOperands(2).getTypes().front(), /*isAccumulator=*/true);
1176 assert(val.has_value() && "accumulator PTX type should always be inferrable");
1177 return val.value();
1178}
1179
1180MMATypes MmaSpOp::resultPtxType() {
1181 std::optional<mlir::NVVM::MMATypes> val =
1182 MmaOp::inferOperandMMAType(getResult().getType(), /*isAccumulator=*/true);
1183 assert(val.has_value() && "result PTX type should always be inferrable");
1184 return val.value();
1185}
1186
1188MmaSpOp::getIntrinsicIDAndArgs(Operation &op, LLVM::ModuleTranslation &mt,
1189 llvm::IRBuilderBase &builder) {
1190 auto thisOp = cast<NVVM::MmaSpOp>(op);
1191
1192 // Get operands
1194 for (mlir::Value v : thisOp.getOperands())
1195 args.push_back(mt.lookupValue(v));
1196
1197 // Get intrinsic ID using the existing getIntrinsicID method
1198 auto intId = MmaSpOp::getIntrinsicID(
1199 thisOp.getShape().getM(), thisOp.getShape().getN(),
1200 thisOp.getShape().getK(), thisOp.getIntOverflowBehavior(),
1201 thisOp.getOrderedMetadata(), thisOp.getKind(),
1202 *thisOp.getMultiplicandAPtxType(), *thisOp.getMultiplicandBPtxType(),
1203 thisOp.accumPtxType(), thisOp.resultPtxType());
1204
1205 return {intId, args};
1206}
1207
1208void MmaSpOp::print(OpAsmPrinter &p) {
1209 SmallVector<Type, 4> regTypes;
1210 struct MMAOperandFragment {
1211 StringRef operandName;
1212 StringRef ptxTypeAttr;
1213 SmallVector<Value, 4> regs;
1214 explicit MMAOperandFragment(StringRef name, StringRef ptxTypeName)
1215 : operandName(name), ptxTypeAttr(ptxTypeName) {}
1216 };
1217
1218 std::array<MMAOperandFragment, 5> frags{
1219 MMAOperandFragment("A", getMultiplicandAPtxTypeAttrName()),
1220 MMAOperandFragment("B", getMultiplicandBPtxTypeAttrName()),
1221 MMAOperandFragment("C", ""), MMAOperandFragment("sparseMetadata", ""),
1222 MMAOperandFragment("selector", "")};
1223 SmallVector<StringRef, 4> ignoreAttrNames{
1224 mlir::NVVM::MmaSpOp::getOperandSegmentSizeAttr()};
1225
1226 // Handle variadic operands A, B, C
1227 for (unsigned fragIdx = 0; fragIdx < 3; fragIdx++) {
1228 auto &frag = frags[fragIdx];
1229 auto varOperandSpec = getODSOperandIndexAndLength(fragIdx);
1230 for (auto operandIdx = varOperandSpec.first;
1231 operandIdx < varOperandSpec.first + varOperandSpec.second;
1232 operandIdx++) {
1233 frag.regs.push_back(this->getOperand(operandIdx));
1234 if (operandIdx == varOperandSpec.first) {
1235 regTypes.push_back(this->getOperand(operandIdx).getType());
1236 }
1237 }
1238 std::optional<MMATypes> inferredType = MmaOp::inferOperandMMAType(
1239 regTypes.back(), /*isAccumulator=*/fragIdx >= 2);
1240 if (inferredType)
1241 ignoreAttrNames.push_back(frag.ptxTypeAttr);
1242 }
1243
1244 // Handle sparse metadata and selector (single operands)
1245 frags[3].regs.push_back(getSparseMetadata());
1246 frags[4].regs.push_back(getSparsitySelector());
1247
1248 auto printMmaSpOperand = [&](const MMAOperandFragment &frag) -> void {
1249 p << " " << frag.operandName;
1250 p << "[";
1251 p.printOperands(frag.regs);
1252 p << "]";
1253 };
1254
1255 for (const auto &frag : frags)
1256 printMmaSpOperand(frag);
1257
1258 p.printOptionalAttrDict((*this)->getAttrs(), ignoreAttrNames);
1259 p << " : ";
1260 p << "(";
1261 for (int i = 0; i < 3; ++i) {
1262 p << regTypes[i];
1263 if (i < 2)
1264 p << ", ";
1265 }
1266 p << ") -> " << getResult().getType();
1267}
1268
1269void MmaSpOp::build(
1270 OpBuilder &builder, OperationState &result, Type resultType,
1271 ValueRange operandA, ValueRange operandB, ValueRange operandC,
1272 Value sparseMetadata, Value sparsitySelector, ArrayRef<int64_t> shape,
1273 std::optional<MMAIntOverflow> intOverflow,
1274 std::optional<std::array<MMATypes, 2>> multiplicandPtxTypes) {
1275
1276 assert(shape.size() == 3 && "expected shape to have size 3 (m, n, k)");
1277 MLIRContext *ctx = builder.getContext();
1278 result.addAttribute(
1279 "shape", builder.getAttr<MMAShapeAttr>(shape[0], shape[1], shape[2]));
1280
1281 result.addOperands(operandA);
1282 result.addOperands(operandB);
1283 result.addOperands(operandC);
1284 result.addOperands(sparseMetadata);
1285 result.addOperands(sparsitySelector);
1286
1287 if (multiplicandPtxTypes) {
1288 result.addAttribute("multiplicandAPtxType",
1289 MMATypesAttr::get(ctx, (*multiplicandPtxTypes)[0]));
1290 result.addAttribute("multiplicandBPtxType",
1291 MMATypesAttr::get(ctx, (*multiplicandPtxTypes)[1]));
1292 } else {
1293 if (auto res = MmaOp::inferOperandMMAType(operandA[0].getType(), false))
1294 result.addAttribute("multiplicandAPtxType", MMATypesAttr::get(ctx, *res));
1295 if (auto res = MmaOp::inferOperandMMAType(operandB[0].getType(), false))
1296 result.addAttribute("multiplicandBPtxType", MMATypesAttr::get(ctx, *res));
1297 }
1298
1299 if (intOverflow.has_value())
1300 result.addAttribute("intOverflowBehavior",
1301 MMAIntOverflowAttr::get(ctx, *intOverflow));
1302
1303 result.addTypes(resultType);
1304 result.addAttribute(
1305 MmaSpOp::getOperandSegmentSizeAttr(),
1306 builder.getDenseI32ArrayAttr({static_cast<int32_t>(operandA.size()),
1307 static_cast<int32_t>(operandB.size()),
1308 static_cast<int32_t>(operandC.size()), 1,
1309 1})); // sparseMetadata and sparsitySelector
1310}
1311
1312ParseResult MmaSpOp::parse(OpAsmParser &parser, OperationState &result) {
1313 struct MMAOperandFragment {
1314 std::optional<MMATypes> elemtype;
1315 SmallVector<OpAsmParser::UnresolvedOperand, 4> regs;
1316 SmallVector<Type> regTypes;
1317 };
1318
1319 Builder &builder = parser.getBuilder();
1320 std::array<MMAOperandFragment, 6> frags; // A, B, C, sparseMetadata, selector
1321
1322 NamedAttrList namedAttributes;
1323
1324 // A helper to parse the operand segments.
1325 auto parseMmaSpOperand = [&](StringRef operandName,
1326 MMAOperandFragment &frag) -> LogicalResult {
1327 if (parser.parseKeyword(operandName).failed())
1328 return failure();
1329 if (parser
1330 .parseOperandList(frag.regs, OpAsmParser::Delimiter::OptionalSquare)
1331 .failed())
1332 return failure();
1333 return success();
1334 };
1335
1336 // Parse the operand segments.
1337 if (parseMmaSpOperand("A", frags[0]).failed())
1338 return failure();
1339 if (parseMmaSpOperand("B", frags[1]).failed())
1340 return failure();
1341 if (parseMmaSpOperand("C", frags[2]).failed())
1342 return failure();
1343 if (parseMmaSpOperand("sparseMetadata", frags[3]).failed())
1344 return failure();
1345 if (parseMmaSpOperand("selector", frags[4]).failed())
1346 return failure();
1347
1348 if (parser.parseOptionalAttrDict(namedAttributes).failed())
1349 return failure();
1350
1351 // Parse the type specification and resolve operands.
1352 SmallVector<Type, 3> operandTypes;
1353 if (failed(parser.parseColon()))
1354 return failure();
1355 if (failed(parser.parseLParen()))
1356 return failure();
1357 if (failed(parser.parseTypeList(operandTypes)))
1358 return failure();
1359 if (failed(parser.parseRParen()))
1360 return failure();
1361 if (operandTypes.size() != 3)
1362 return parser.emitError(
1363 parser.getNameLoc(),
1364 "expected one type for each operand segment but got " +
1365 Twine(operandTypes.size()) + " types");
1366 for (const auto &iter : llvm::enumerate(operandTypes)) {
1367 auto &frag = frags[iter.index()];
1368 frag.regTypes.resize(frag.regs.size(), iter.value());
1369 if (failed(parser.resolveOperands(frag.regs, frag.regTypes,
1370 parser.getNameLoc(), result.operands)))
1371 return failure();
1372 frag.elemtype =
1373 MmaOp::inferOperandMMAType(frag.regTypes[0],
1374 /*isAccumulator*/ iter.index() >= 2);
1375 }
1376
1377 Type resultType;
1378 if (parser.parseArrow() || parser.parseType(resultType))
1379 return failure();
1380 frags[5].elemtype =
1381 MmaOp::inferOperandMMAType(resultType, /*isAccumulator*/ true);
1382
1383 // Resolve sparse metadata and selector (assume i32 type)
1384 Type i32Type = builder.getIntegerType(32);
1385 if (parser
1386 .resolveOperands(frags[3].regs, i32Type, parser.getCurrentLocation(),
1387 result.operands)
1388 .failed())
1389 return failure();
1390 if (parser
1391 .resolveOperands(frags[4].regs, i32Type, parser.getCurrentLocation(),
1392 result.operands)
1393 .failed())
1394 return failure();
1395
1396 std::array<StringRef, 2> names{"multiplicandAPtxType",
1397 "multiplicandBPtxType"};
1398 for (unsigned idx = 0; idx < names.size(); idx++) {
1399 const auto &frag = frags[idx];
1400 std::optional<NamedAttribute> attr = namedAttributes.getNamed(names[idx]);
1401 if (!frag.elemtype.has_value() && !attr.has_value()) {
1402 return parser.emitError(
1403 parser.getNameLoc(),
1404 "attribute " + names[idx] +
1405 " is not provided explicitly and cannot be inferred");
1406 }
1407 if (!attr.has_value())
1408 result.addAttribute(
1409 names[idx], MMATypesAttr::get(parser.getContext(), *frag.elemtype));
1410 }
1411
1412 result.addTypes(resultType);
1413 if (!namedAttributes.empty())
1414 result.addAttributes(namedAttributes);
1415 result.addAttribute(MmaSpOp::getOperandSegmentSizeAttr(),
1416 builder.getDenseI32ArrayAttr({
1417 static_cast<int32_t>(frags[0].regs.size()),
1418 static_cast<int32_t>(frags[1].regs.size()),
1419 static_cast<int32_t>(frags[2].regs.size()),
1420 1, // sparseMetadata
1421 1 // sparsitySelector
1422 }));
1423 return success();
1424}
1425
1426LogicalResult MmaSpOp::verify() {
1427 MLIRContext *context = getContext();
1428 auto f16Ty = Float16Type::get(context);
1429 auto i32Ty = IntegerType::get(context, 32);
1430 auto f16x2Ty = VectorType::get(2, f16Ty);
1431 auto f32Ty = Float32Type::get(context);
1432 auto f16x2x4StructTy = LLVM::LLVMStructType::getLiteral(
1433 context, {f16x2Ty, f16x2Ty, f16x2Ty, f16x2Ty});
1434
1435 auto s32x4StructTy =
1436 LLVM::LLVMStructType::getLiteral(context, {i32Ty, i32Ty, i32Ty, i32Ty});
1437 auto f32x8StructTy =
1438 LLVM::LLVMStructType::getLiteral(context, SmallVector<Type>(8, f32Ty));
1439 auto f16x2x2StructTy =
1440 LLVM::LLVMStructType::getLiteral(context, {f16x2Ty, f16x2Ty});
1441 auto f32x4StructTy =
1442 LLVM::LLVMStructType::getLiteral(context, {f32Ty, f32Ty, f32Ty, f32Ty});
1443 auto s32x2StructTy =
1444 LLVM::LLVMStructType::getLiteral(context, {i32Ty, i32Ty});
1445
1446 std::array<int64_t, 3> mmaShape{getShapeAttr().getM(), getShapeAttr().getN(),
1447 getShapeAttr().getK()};
1448
1449 // These variables define the set of allowed data types for matrices A, B, C,
1450 // and result.
1451 using AllowedShapes = SmallVector<std::array<int64_t, 3>, 2>;
1452 using AllowedTypes = SmallVector<SmallVector<Type, 4>, 2>;
1453 AllowedShapes allowedShapes;
1454 AllowedTypes expectedA;
1455 AllowedTypes expectedB;
1456 AllowedTypes expectedC;
1457 SmallVector<Type> expectedResult;
1458
1459 // When M = 16, we just need to calculate the number of 8xk tiles, where
1460 // k is a factor that depends on the data type.
1461 if (mmaShape[0] == 16) {
1462 int64_t kFactor;
1463 Type multiplicandFragType;
1464 switch (*getMultiplicandAPtxType()) {
1465 case MMATypes::tf32:
1466 kFactor = 4;
1467 multiplicandFragType = i32Ty;
1468 expectedResult.push_back(LLVM::LLVMStructType::getLiteral(
1469 context, {f32Ty, f32Ty, f32Ty, f32Ty}));
1470 // Sparse MMA supports m16n8k8 and m16n8k16 for tf32
1471 allowedShapes.push_back({16, 8, 8});
1472 allowedShapes.push_back({16, 8, 16});
1473 break;
1474 case MMATypes::bf16:
1475 kFactor = 8;
1476 multiplicandFragType = i32Ty;
1477 expectedResult.push_back(LLVM::LLVMStructType::getLiteral(
1478 context, {f32Ty, f32Ty, f32Ty, f32Ty}));
1479 // Sparse MMA supports m16n8k16 and m16n8k32 for bf16
1480 allowedShapes.push_back({16, 8, 16});
1481 allowedShapes.push_back({16, 8, 32});
1482 break;
1483 case MMATypes::f16:
1484 kFactor = 8;
1485 multiplicandFragType = f16x2Ty;
1486 expectedResult.push_back(f16x2x2StructTy);
1487 expectedResult.push_back(f32x4StructTy);
1488 // Sparse MMA supports m16n8k16 and m16n8k32 for f16
1489 allowedShapes.push_back({16, 8, 16});
1490 allowedShapes.push_back({16, 8, 32});
1491 break;
1492 case MMATypes::s4:
1493 case MMATypes::u4:
1494 kFactor = 32;
1495 // Sparse MMA supports m16n8k64 and m16n8k128 for s4/u4
1496 allowedShapes.push_back({16, 8, 64});
1497 allowedShapes.push_back({16, 8, 128});
1498 break;
1499 case MMATypes::s8:
1500 case MMATypes::u8:
1501 kFactor = 16;
1502 // Sparse MMA supports m16n8k32 and m16n8k64 for s8/u8
1503 allowedShapes.push_back({16, 8, 32});
1504 allowedShapes.push_back({16, 8, 64});
1505 break;
1506 case MMATypes::e4m3:
1507 case MMATypes::e5m2:
1508 case MMATypes::e3m2:
1509 case MMATypes::e2m3:
1510 case MMATypes::e2m1:
1511 kFactor = 16;
1512 multiplicandFragType = i32Ty;
1513 expectedResult.push_back(f16x2x2StructTy);
1514 expectedResult.push_back(f32x4StructTy);
1515 // Sparse MMA supports m16n8k64 for FP8 types
1516 allowedShapes.push_back({16, 8, 64});
1517 break;
1518 default:
1519 return emitError("invalid shape or multiplicand type: ")
1520 << getMultiplicandAPtxType().value();
1521 }
1522
1523 if (isIntegerPtxType(getMultiplicandAPtxType().value())) {
1524 expectedResult.push_back(s32x4StructTy);
1525 expectedC.emplace_back(4, i32Ty);
1526 multiplicandFragType = i32Ty;
1527 } else if (*getMultiplicandAPtxType() >= MMATypes::e4m3 &&
1528 *getMultiplicandAPtxType() <= MMATypes::e2m1) {
1529 // FP8 types
1530 expectedC.emplace_back(2, f16x2Ty);
1531 expectedC.emplace_back(4, f32Ty);
1532 } else {
1533 expectedC.emplace_back(2, f16x2Ty);
1534 expectedC.emplace_back(4, f32Ty);
1535 }
1536
1537 // For sparse MMA, A operand is compressed (2:4 sparsity means half the
1538 // elements)
1539 int64_t unitA = (mmaShape[0] / 8) * (mmaShape[2] / kFactor) / 2;
1540 int64_t unitB = (mmaShape[1] / 8) * (mmaShape[2] / kFactor);
1541 expectedA.emplace_back(unitA, multiplicandFragType);
1542 expectedB.emplace_back(unitB, multiplicandFragType);
1543
1544 if (resultPtxType() != accumPtxType())
1545 return emitOpError("ctype does not match dtype");
1546 }
1547
1548 // In the M=8 case, there is only 1 possible case per data type.
1549 if (mmaShape[0] == 8) {
1550 if (*getMultiplicandAPtxType() == MMATypes::f16) {
1551 expectedA.emplace_back(2, f16x2Ty);
1552 expectedB.emplace_back(2, f16x2Ty);
1553 expectedResult.push_back(f16x2x4StructTy);
1554 expectedResult.push_back(f32x8StructTy);
1555 expectedC.emplace_back(4, f16x2Ty);
1556 expectedC.emplace_back(8, f32Ty);
1557 allowedShapes.push_back({8, 8, 4});
1558 }
1559 if (*getMultiplicandAPtxType() == MMATypes::f64) {
1560 Type f64Ty = Float64Type::get(context);
1561 expectedA.emplace_back(1, f64Ty);
1562 expectedB.emplace_back(1, f64Ty);
1563 expectedC.emplace_back(2, f64Ty);
1564 expectedResult.emplace_back(LLVM::LLVMStructType::getLiteral(
1565 context, SmallVector<Type>(2, f64Ty)));
1566 allowedShapes.push_back({8, 8, 4});
1567 }
1568 if (isIntegerPtxType(getMultiplicandAPtxType().value())) {
1569 expectedA.push_back({i32Ty});
1570 expectedB.push_back({i32Ty});
1571 expectedC.push_back({i32Ty, i32Ty});
1572 expectedResult.push_back(s32x2StructTy);
1573 if (isInt4PtxType(getMultiplicandAPtxType().value()))
1574 allowedShapes.push_back({8, 8, 32});
1575 if (isInt8PtxType(getMultiplicandAPtxType().value()))
1576 allowedShapes.push_back({8, 8, 16});
1577 }
1578 }
1579
1580 std::string errorMessage;
1581 llvm::raw_string_ostream errorStream(errorMessage);
1582
1583 // Check that we matched an existing shape/dtype combination.
1584 if (expectedA.empty() || expectedB.empty() || expectedC.empty() ||
1585 !llvm::is_contained(allowedShapes, mmaShape)) {
1586 errorStream << "unimplemented variant for MMA shape <";
1587 llvm::interleaveComma(mmaShape, errorStream);
1588 errorStream << ">";
1589 return emitOpError(errorMessage);
1590 }
1591
1592 // Verify the operand types for segments of A, B, and C operands.
1593 std::array<StringRef, 3> operandNames{"A", "B", "C"};
1594 for (const auto &iter : llvm::enumerate(
1595 SmallVector<AllowedTypes, 3>{expectedA, expectedB, expectedC})) {
1596 auto spec = this->getODSOperandIndexAndLength(iter.index());
1597 SmallVector<Type, 4> operandTySeg(operand_type_begin() + spec.first,
1598 operand_type_begin() + spec.first +
1599 spec.second);
1600 bool match = llvm::is_contained(iter.value(), operandTySeg);
1601
1602 if (!match) {
1603 errorStream << "Could not match types for the "
1604 << operandNames[iter.index()]
1605 << " operands; expected one of ";
1606 for (const auto &x : iter.value()) {
1607 errorStream << x.size() << "x" << x[0] << " ";
1608 }
1609 errorStream << "but got ";
1610 llvm::interleaveComma(operandTySeg, errorStream);
1611 return emitOpError(errorMessage);
1612 }
1613 }
1614
1615 // Check the result type
1616 if (!llvm::any_of(expectedResult, [&](Type expectedResultType) {
1617 return expectedResultType == getResult().getType();
1618 })) {
1619 errorStream
1620 << "Could not match allowed types for the result; expected one of ";
1621 llvm::interleaveComma(expectedResult, errorStream);
1622 errorStream << " but got " << getResult().getType();
1623 return emitOpError(errorMessage);
1624 }
1625
1626 // Ensure int4/int8 MMA variants specify the accum overflow behavior
1627 // attribute.
1628 if (isInt4PtxType(*getMultiplicandAPtxType()) ||
1629 isInt8PtxType(*getMultiplicandAPtxType())) {
1630 if (!getIntOverflowBehavior())
1631 return emitOpError("op requires " +
1632 getIntOverflowBehaviorAttrName().strref() +
1633 " attribute");
1634 }
1635
1636 // Validate sparse metadata type (should be i32)
1637 if (!getSparseMetadata().getType().isInteger(32)) {
1638 return emitOpError() << "sparse metadata must be i32 type";
1639 }
1640
1641 // Validate sparsity selector type (should be i32)
1642 if (!getSparsitySelector().getType().isInteger(32)) {
1643 return emitOpError() << "sparsity selector must be i32 type";
1644 }
1645
1646 return success();
1647}
1648
1649//===----------------------------------------------------------------------===//
1650// MMA Block Scale Operations - Shared Helpers
1651//===----------------------------------------------------------------------===//
1652
1653namespace {
1654// Shared structure for MMA operand fragments (A, B, C)
1655struct MMAOperandFragment {
1656 StringRef operandName;
1657 StringRef ptxTypeAttr;
1658 SmallVector<Value, 4> regs;
1659 explicit MMAOperandFragment(StringRef name, StringRef ptxTypeName)
1660 : operandName(name), ptxTypeAttr(ptxTypeName) {}
1661};
1662} // namespace
1663
1664// Helper to print operand list in the format: name[operands]
1665static void printOperandList(OpAsmPrinter &p, StringRef name,
1666 ArrayRef<Value> operands) {
1667 p << " " << name << "[";
1668 p.printOperands(operands);
1669 p << "]";
1670}
1671
1672// Helper to parse operand list in the format: name[operands]
1673static LogicalResult
1674parseMmaOperand(OpAsmParser &parser, StringRef operandName,
1676 if (parser.parseKeyword(operandName).failed())
1677 return failure();
1679 .failed())
1680 return failure();
1681 return success();
1682}
1683
1684// Helper to process operand fragments and determine which attributes can be
1685// inferred
1686template <typename Op>
1687static void
1688processOperandFragments(Op &op, std::array<MMAOperandFragment, 3> &frags,
1689 SmallVectorImpl<Type> &regTypes,
1690 SmallVectorImpl<StringRef> &ignoreAttrNames) {
1691 for (unsigned fragIdx = 0; fragIdx < frags.size(); fragIdx++) {
1692 auto &frag = frags[fragIdx];
1693 auto varOperandSpec = op.getODSOperandIndexAndLength(fragIdx);
1694 for (auto operandIdx = varOperandSpec.first;
1695 operandIdx < varOperandSpec.first + varOperandSpec.second;
1696 operandIdx++) {
1697 frag.regs.push_back(op.getOperand(operandIdx));
1698 if (fragIdx == 0 && operandIdx == varOperandSpec.first) {
1699 regTypes.push_back(op.getOperand(operandIdx).getType());
1700 }
1701 }
1702 if (fragIdx < 2) {
1703 regTypes.push_back(frag.regs[0].getType());
1704 }
1705 std::optional<MMATypes> inferredType =
1706 MmaOp::inferOperandMMAType(regTypes.back(),
1707 /*isAccumulator=*/fragIdx >= 2);
1708 if (inferredType)
1709 ignoreAttrNames.push_back(frag.ptxTypeAttr);
1710 }
1711}
1712
1713// Helper to parse type signature: (A_type, B_type, C_type)
1714static LogicalResult
1716 SmallVectorImpl<Type> &operandTypes) {
1717 if (parser.parseColon().failed() || parser.parseLParen().failed())
1718 return failure();
1719
1720 auto typeParser = [&]() {
1721 Type ty;
1722 if (parser.parseType(ty).failed())
1723 return failure();
1724 operandTypes.push_back(ty);
1725 return success();
1726 };
1727 if (parser.parseCommaSeparatedList(typeParser))
1728 return failure();
1729
1730 if (operandTypes.size() != 3)
1731 return parser.emitError(parser.getCurrentLocation(),
1732 "expected exactly 3 types");
1733
1734 return parser.parseRParen();
1735}
1736
1737// Helper to infer and set multiplicand PTX type attributes
1738static void
1740 const SmallVectorImpl<Type> &operandTypes) {
1741 if (!attrs.get("multiplicandAPtxType")) {
1742 if (auto inferredType =
1743 MmaOp::inferOperandMMAType(operandTypes[0], false)) {
1744 attrs.set("multiplicandAPtxType", MMATypesAttr::get(ctx, *inferredType));
1745 }
1746 }
1747 if (!attrs.get("multiplicandBPtxType")) {
1748 if (auto inferredType =
1749 MmaOp::inferOperandMMAType(operandTypes[1], false)) {
1750 attrs.set("multiplicandBPtxType", MMATypesAttr::get(ctx, *inferredType));
1751 }
1752 }
1753}
1754
1755// Helper to add common block scale properties
1756template <typename OpType>
1759 ScaleVecSize scaleVecSize,
1760 BlockScaleFormat blockScaleFormat,
1761 MMABlockScaleKind kind) {
1762 MLIRContext *ctx = builder.getContext();
1763 auto &properties = result.getOrAddProperties<typename OpType::Properties>();
1764 properties.setShape(
1765 builder.getAttr<MMAShapeAttr>(shape[0], shape[1], shape[2]));
1766 properties.setScaleVecSize(ScaleVecSizeAttr::get(ctx, scaleVecSize));
1767 properties.setBlockScaleFormat(
1768 BlockScaleFormatAttr::get(ctx, blockScaleFormat));
1769 properties.setKind(MMABlockScaleKindAttr::get(ctx, kind));
1770}
1771
1772// Helper to infer and add multiplicand PTX types to builder
1775 ValueRange operandB,
1776 std::optional<std::array<MMATypes, 2>> multiplicandPtxTypes) {
1777 if (multiplicandPtxTypes) {
1778 result.addAttribute("multiplicandAPtxType",
1779 MMATypesAttr::get(ctx, (*multiplicandPtxTypes)[0]));
1780 result.addAttribute("multiplicandBPtxType",
1781 MMATypesAttr::get(ctx, (*multiplicandPtxTypes)[1]));
1782 } else {
1783 if (auto res = MmaOp::inferOperandMMAType(operandA[0].getType(), false))
1784 result.addAttribute("multiplicandAPtxType", MMATypesAttr::get(ctx, *res));
1785 if (auto res = MmaOp::inferOperandMMAType(operandB[0].getType(), false))
1786 result.addAttribute("multiplicandBPtxType", MMATypesAttr::get(ctx, *res));
1787 }
1788}
1789
1790// Template helper for common accumPtxType/resultPtxType implementation
1791template <typename OpTy>
1792static MMATypes inferPtxTypeFromResult(OpTy op) {
1793 return *MmaOp::inferOperandMMAType(
1794 cast<LLVM::LLVMStructType>(op.getRes().getType()).getBody()[0],
1795 /*isAccumulator=*/true);
1796}
1797
1798//===----------------------------------------------------------------------===//
1799// MmaBlockScaleOp
1800//===----------------------------------------------------------------------===//
1801
1802void MmaBlockScaleOp::print(OpAsmPrinter &p) {
1803 SmallVector<Type, 4> regTypes;
1804 std::array<MMAOperandFragment, 3> frags{
1805 MMAOperandFragment("A", getMultiplicandAPtxTypeAttrName()),
1806 MMAOperandFragment("B", getMultiplicandBPtxTypeAttrName()),
1807 MMAOperandFragment("C", "")};
1808 SmallVector<StringRef, 4> ignoreAttrNames{
1809 mlir::NVVM::MmaBlockScaleOp::getOperandSegmentSizeAttr()};
1810
1811 processOperandFragments(*this, frags, regTypes, ignoreAttrNames);
1812
1813 // Print A, B, C operands
1814 for (const auto &frag : frags)
1815 printOperandList(p, frag.operandName, frag.regs);
1816
1817 // Print scale operands
1818 printOperandList(p, "scaleA",
1819 {getScaleAData(), getByteIdA(), getThreadIdA()});
1820 printOperandList(p, "scaleB",
1821 {getScaleBData(), getByteIdB(), getThreadIdB()});
1822
1823 p.printOptionalAttrDict(this->getOperation()->getAttrs(), ignoreAttrNames);
1824
1825 // Print type signature
1826 p << " : (";
1827 llvm::interleaveComma(SmallVector<Type, 3>{frags[0].regs[0].getType(),
1828 frags[1].regs[0].getType(),
1829 frags[2].regs[0].getType()},
1830 p);
1831 p << ")";
1832 p.printArrowTypeList(TypeRange{this->getRes().getType()});
1833}
1834
1835ParseResult MmaBlockScaleOp::parse(OpAsmParser &parser,
1837 struct LocalOperandFragment {
1838 std::optional<MMATypes> elemtype;
1839 SmallVector<OpAsmParser::UnresolvedOperand, 4> regs;
1840 };
1841
1842 Builder &builder = parser.getBuilder();
1843 std::array<LocalOperandFragment, 3> frags;
1844 NamedAttrList namedAttributes;
1845
1846 // Parse A[...] B[...] C[...]
1847 if (parseMmaOperand(parser, "A", frags[0].regs).failed() ||
1848 parseMmaOperand(parser, "B", frags[1].regs).failed() ||
1849 parseMmaOperand(parser, "C", frags[2].regs).failed())
1850 return failure();
1851
1852 // Parse scale operands: scaleA[...] scaleB[...]
1853 SmallVector<OpAsmParser::UnresolvedOperand, 3> scaleAOperands, scaleBOperands;
1854 if (parseMmaOperand(parser, "scaleA", scaleAOperands).failed() ||
1855 parseMmaOperand(parser, "scaleB", scaleBOperands).failed())
1856 return failure();
1857
1858 if (parser.parseOptionalAttrDict(namedAttributes).failed())
1859 return failure();
1860
1861 // Parse type signature
1862 SmallVector<Type, 3> operandTypes;
1863 if (parseMmaTypeSignature(parser, operandTypes).failed())
1864 return failure();
1865
1866 // Parse result type
1867 SmallVector<Type, 1> resultTypes;
1868 if (parser.parseArrowTypeList(resultTypes).failed())
1869 return failure();
1870
1871 // Infer element types and resolve operands
1872 for (const auto &[idx, frag] : llvm::enumerate(frags)) {
1873 frag.elemtype = MmaOp::inferOperandMMAType(operandTypes[idx],
1874 /*isAccumulator=*/idx >= 2);
1875 if (parser
1876 .resolveOperands(frag.regs, operandTypes[idx], parser.getNameLoc(),
1877 result.operands)
1878 .failed())
1879 return failure();
1880 }
1881
1882 // Resolve scale operands
1883 SmallVector<Type, 3> scaleTypes = {builder.getI32Type(), builder.getI16Type(),
1884 builder.getI16Type()};
1885 if (parser
1886 .resolveOperands(scaleAOperands, scaleTypes, parser.getNameLoc(),
1887 result.operands)
1888 .failed() ||
1889 parser
1890 .resolveOperands(scaleBOperands, scaleTypes, parser.getNameLoc(),
1891 result.operands)
1892 .failed())
1893 return failure();
1894
1895 // Add attributes
1896 result.addAttributes(namedAttributes);
1897 inferAndSetMultiplicandTypes(parser.getContext(), result.attributes,
1898 operandTypes);
1899
1900 result.addTypes(resultTypes);
1901 result.addAttribute(MmaBlockScaleOp::getOperandSegmentSizeAttr(),
1902 builder.getDenseI32ArrayAttr({
1903 static_cast<int32_t>(frags[0].regs.size()),
1904 static_cast<int32_t>(frags[1].regs.size()),
1905 static_cast<int32_t>(frags[2].regs.size()),
1906 1, // scaleAData
1907 1, // byteIdA
1908 1, // threadIdA
1909 1, // scaleBData
1910 1, // byteIdB
1911 1 // threadIdB
1912 }));
1913 return success();
1914}
1915
1916void MmaBlockScaleOp::build(
1917 OpBuilder &builder, OperationState &result, Type resultType,
1918 ValueRange operandA, ValueRange operandB, ValueRange operandC,
1919 Value scaleAData, Value byteIdA, Value threadIdA, Value scaleBData,
1920 Value byteIdB, Value threadIdB, ArrayRef<int64_t> shape,
1921 std::optional<std::array<MMATypes, 2>> multiplicandPtxTypes,
1922 ScaleVecSize scaleVecSize, BlockScaleFormat blockScaleFormat,
1923 MMABlockScaleKind kind) {
1924 assert(shape.size() == 3 && "expected shape to have size 3 (m, n, k)");
1925
1927 blockScaleFormat, kind);
1928
1929 result.addOperands(operandA);
1930 result.addOperands(operandB);
1931 result.addOperands(operandC);
1932 result.addOperands(
1933 {scaleAData, byteIdA, threadIdA, scaleBData, byteIdB, threadIdB});
1934
1935 addInferredMultiplicandTypes(builder.getContext(), result, operandA, operandB,
1936 multiplicandPtxTypes);
1937
1938 result.addTypes(resultType);
1939 result.addAttribute(MmaBlockScaleOp::getOperandSegmentSizeAttr(),
1940 builder.getDenseI32ArrayAttr({
1941 static_cast<int32_t>(operandA.size()),
1942 static_cast<int32_t>(operandB.size()),
1943 static_cast<int32_t>(operandC.size()),
1944 1, // scaleAData
1945 1, // byteIdA
1946 1, // threadIdA
1947 1, // scaleBData
1948 1, // byteIdB
1949 1 // threadIdB
1950 }));
1951}
1952
1953NVVM::IDArgPair MmaBlockScaleOp::getIntrinsicIDAndArgs(
1954 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
1955 auto curOp = cast<NVVM::MmaBlockScaleOp>(op);
1956
1958 // Add A, B, C operands
1959 for (Value operand : curOp.getOperandA())
1960 args.push_back(mt.lookupValue(operand));
1961 for (Value operand : curOp.getOperandB())
1962 args.push_back(mt.lookupValue(operand));
1963 for (Value operand : curOp.getOperandC())
1964 args.push_back(mt.lookupValue(operand));
1965
1966 // Add scale operands
1967 args.push_back(mt.lookupValue(curOp.getScaleAData()));
1968 args.push_back(mt.lookupValue(curOp.getByteIdA()));
1969 args.push_back(mt.lookupValue(curOp.getThreadIdA()));
1970 args.push_back(mt.lookupValue(curOp.getScaleBData()));
1971 args.push_back(mt.lookupValue(curOp.getByteIdB()));
1972 args.push_back(mt.lookupValue(curOp.getThreadIdB()));
1973
1974 unsigned intId = MmaBlockScaleOp::getIntrinsicID(
1975 curOp.getShape().getM(), curOp.getShape().getN(), curOp.getShape().getK(),
1976 *curOp.getMultiplicandAPtxType(), *curOp.getMultiplicandBPtxType(),
1977 inferPtxTypeFromResult(curOp), curOp.getScaleVecSize(),
1978 curOp.getBlockScaleFormat(), curOp.getKind());
1979
1980 return {intId, args};
1981}
1982
1983LogicalResult MmaBlockScaleOp::verify() {
1984 LogicalResult result = success();
1985 int m = getShape().getM();
1986 int n = getShape().getN();
1987 int k = getShape().getK();
1988
1989 if (m == 16 && n == 8 && k == 64) {
1990 if (getMultiplicandAPtxType() != NVVM::MMATypes::e2m1 ||
1991 getMultiplicandBPtxType() != NVVM::MMATypes::e2m1)
1993 "unsupported MMATypes attribute for mma.m16n8k64.(mxf4nvf4|mxf4)");
1994 if (getKind() == NVVM::MMABlockScaleKind::MXF4) {
1995 if (getScaleVecSize() != NVVM::ScaleVecSize::X2)
1997 "unsupported ScaleVecSize attribute for mma.m16n8k64.mxf4");
1998 if (getBlockScaleFormat() != NVVM::BlockScaleFormat::UE8M0)
2000 "unsupported BlockScaleFormat attribute for mma.m16n8k64.mxf4");
2001 } else if (getKind() == NVVM::MMABlockScaleKind::MXF4NVF4) {
2002 if (!((getScaleVecSize() == NVVM::ScaleVecSize::X2 &&
2003 getBlockScaleFormat() == NVVM::BlockScaleFormat::UE8M0) ||
2004 (getScaleVecSize() == NVVM::ScaleVecSize::X4 &&
2005 (getBlockScaleFormat() == NVVM::BlockScaleFormat::UE4M3 ||
2006 getBlockScaleFormat() == NVVM::BlockScaleFormat::UE8M0))))
2007 result = emitOpError("unsupported ScaleVecSize and BlockScaleFormat "
2008 "attributes for mma.m16n8k64.mxf4nvf4");
2009 } else {
2010 result = emitOpError("unsupported Kind attribute for mma.m16n8k64");
2011 }
2012 } else if (m == 16 && n == 8 && k == 32) {
2013 if (!(getKind() == NVVM::MMABlockScaleKind::MXF8F6F4 &&
2014 getScaleVecSize() == NVVM::ScaleVecSize::X1 &&
2015 getBlockScaleFormat() == NVVM::BlockScaleFormat::UE8M0))
2016 result =
2017 emitOpError("unsupported Kind, ScaleVecSize and BlockScaleFormat "
2018 "attributes for mma.m16n8k32");
2019 } else {
2020 result = emitOpError("unsupported Geom for mma with block scaling");
2021 }
2022 return result;
2023}
2024
2025//===----------------------------------------------------------------------===//
2026// MmaSpBlockScaleOp
2027//===----------------------------------------------------------------------===//
2028
2029void MmaSpBlockScaleOp::print(OpAsmPrinter &p) {
2030 SmallVector<Type, 4> regTypes;
2031 std::array<MMAOperandFragment, 3> frags{
2032 MMAOperandFragment("A", getMultiplicandAPtxTypeAttrName()),
2033 MMAOperandFragment("B", getMultiplicandBPtxTypeAttrName()),
2034 MMAOperandFragment("C", "")};
2035 SmallVector<StringRef, 4> ignoreAttrNames{
2036 mlir::NVVM::MmaSpBlockScaleOp::getOperandSegmentSizeAttr()};
2037
2038 processOperandFragments(*this, frags, regTypes, ignoreAttrNames);
2039
2040 // Print A, B, C operands
2041 for (const auto &frag : frags)
2042 printOperandList(p, frag.operandName, frag.regs);
2043
2044 // Print sparse-specific operands
2045 printOperandList(p, "sparseMetadata", {getSparseMetadata()});
2046 printOperandList(p, "selector", {getSparsitySelector()});
2047
2048 // Print scale operands
2049 printOperandList(p, "scaleA",
2050 {getScaleAData(), getByteIdA(), getThreadIdA()});
2051 printOperandList(p, "scaleB",
2052 {getScaleBData(), getByteIdB(), getThreadIdB()});
2053
2054 p.printOptionalAttrDict(this->getOperation()->getAttrs(), ignoreAttrNames);
2055
2056 // Print type signature
2057 p << " : (";
2058 llvm::interleaveComma(SmallVector<Type, 3>{frags[0].regs[0].getType(),
2059 frags[1].regs[0].getType(),
2060 frags[2].regs[0].getType()},
2061 p);
2062 p << ")";
2063 p.printArrowTypeList(TypeRange{this->getRes().getType()});
2064}
2065
2066ParseResult MmaSpBlockScaleOp::parse(OpAsmParser &parser,
2068 struct LocalOperandFragment {
2069 std::optional<MMATypes> elemtype;
2070 SmallVector<OpAsmParser::UnresolvedOperand, 4> regs;
2071 };
2072
2073 Builder &builder = parser.getBuilder();
2074 std::array<LocalOperandFragment, 3> frags;
2075 NamedAttrList namedAttributes;
2076
2077 // Parse A[...] B[...] C[...]
2078 if (parseMmaOperand(parser, "A", frags[0].regs).failed() ||
2079 parseMmaOperand(parser, "B", frags[1].regs).failed() ||
2080 parseMmaOperand(parser, "C", frags[2].regs).failed())
2081 return failure();
2082
2083 // Parse sparse-specific operands
2085 selectorOperands;
2086 if (parseMmaOperand(parser, "sparseMetadata", metadataOperands).failed() ||
2087 parseMmaOperand(parser, "selector", selectorOperands).failed())
2088 return failure();
2089
2090 // Parse scale operands
2091 SmallVector<OpAsmParser::UnresolvedOperand, 3> scaleAOperands, scaleBOperands;
2092 if (parseMmaOperand(parser, "scaleA", scaleAOperands).failed() ||
2093 parseMmaOperand(parser, "scaleB", scaleBOperands).failed())
2094 return failure();
2095
2096 if (parser.parseOptionalAttrDict(namedAttributes).failed())
2097 return failure();
2098
2099 // Parse type signature
2100 SmallVector<Type, 3> operandTypes;
2101 if (parseMmaTypeSignature(parser, operandTypes).failed())
2102 return failure();
2103
2104 // Parse result type
2105 SmallVector<Type, 1> resultTypes;
2106 if (parser.parseArrowTypeList(resultTypes).failed())
2107 return failure();
2108
2109 // Infer element types and resolve operands
2110 for (const auto &[idx, frag] : llvm::enumerate(frags)) {
2111 frag.elemtype = MmaOp::inferOperandMMAType(operandTypes[idx],
2112 /*isAccumulator=*/idx >= 2);
2113 if (parser
2114 .resolveOperands(frag.regs, operandTypes[idx], parser.getNameLoc(),
2115 result.operands)
2116 .failed())
2117 return failure();
2118 }
2119
2120 // Resolve sparse metadata and selector
2121 Type i32Type = builder.getI32Type();
2122 if (parser
2123 .resolveOperands(metadataOperands, i32Type, parser.getNameLoc(),
2124 result.operands)
2125 .failed() ||
2126 parser
2127 .resolveOperands(selectorOperands, i32Type, parser.getNameLoc(),
2128 result.operands)
2129 .failed())
2130 return failure();
2131
2132 // Resolve scale operands
2133 SmallVector<Type, 3> scaleTypes = {i32Type, builder.getI16Type(),
2134 builder.getI16Type()};
2135 if (parser
2136 .resolveOperands(scaleAOperands, scaleTypes, parser.getNameLoc(),
2137 result.operands)
2138 .failed() ||
2139 parser
2140 .resolveOperands(scaleBOperands, scaleTypes, parser.getNameLoc(),
2141 result.operands)
2142 .failed())
2143 return failure();
2144
2145 // Add attributes
2146 result.addAttributes(namedAttributes);
2147 inferAndSetMultiplicandTypes(parser.getContext(), result.attributes,
2148 operandTypes);
2149
2150 // orderedMetadata is mandatory
2151 if (!result.attributes.get("orderedMetadata"))
2152 result.addAttribute("orderedMetadata", builder.getUnitAttr());
2153
2154 result.addTypes(resultTypes);
2155 result.addAttribute(MmaSpBlockScaleOp::getOperandSegmentSizeAttr(),
2156 builder.getDenseI32ArrayAttr({
2157 static_cast<int32_t>(frags[0].regs.size()),
2158 static_cast<int32_t>(frags[1].regs.size()),
2159 static_cast<int32_t>(frags[2].regs.size()),
2160 1, // sparseMetadata
2161 1, // sparsitySelector
2162 1, // scaleAData
2163 1, // byteIdA
2164 1, // threadIdA
2165 1, // scaleBData
2166 1, // byteIdB
2167 1 // threadIdB
2168 }));
2169 return success();
2170}
2171
2172void MmaSpBlockScaleOp::build(
2173 OpBuilder &builder, OperationState &result, Type resultType,
2174 ValueRange operandA, ValueRange operandB, ValueRange operandC,
2175 Value sparseMetadata, Value sparsitySelector, Value scaleAData,
2176 Value byteIdA, Value threadIdA, Value scaleBData, Value byteIdB,
2177 Value threadIdB, ArrayRef<int64_t> shape,
2178 std::optional<std::array<MMATypes, 2>> multiplicandPtxTypes,
2179 ScaleVecSize scaleVecSize, BlockScaleFormat blockScaleFormat,
2180 MMABlockScaleKind kind) {
2181 assert(shape.size() == 3 && "expected shape to have size 3 (m, n, k)");
2182
2184 builder, result, shape, scaleVecSize, blockScaleFormat, kind);
2185 result.addAttribute("orderedMetadata", builder.getUnitAttr());
2186
2187 result.addOperands(operandA);
2188 result.addOperands(operandB);
2189 result.addOperands(operandC);
2190 result.addOperands({sparseMetadata, sparsitySelector, scaleAData, byteIdA,
2191 threadIdA, scaleBData, byteIdB, threadIdB});
2192
2193 addInferredMultiplicandTypes(builder.getContext(), result, operandA, operandB,
2194 multiplicandPtxTypes);
2195
2196 result.addTypes(resultType);
2197 result.addAttribute(MmaSpBlockScaleOp::getOperandSegmentSizeAttr(),
2198 builder.getDenseI32ArrayAttr({
2199 static_cast<int32_t>(operandA.size()),
2200 static_cast<int32_t>(operandB.size()),
2201 static_cast<int32_t>(operandC.size()),
2202 1, // sparseMetadata
2203 1, // sparsitySelector
2204 1, // scaleAData
2205 1, // byteIdA
2206 1, // threadIdA
2207 1, // scaleBData
2208 1, // byteIdB
2209 1 // threadIdB
2210 }));
2211}
2212
2213NVVM::IDArgPair MmaSpBlockScaleOp::getIntrinsicIDAndArgs(
2214 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
2215 auto curOp = cast<NVVM::MmaSpBlockScaleOp>(op);
2216
2218 // Add A, B, C operands
2219 for (Value operand : curOp.getOperandA())
2220 args.push_back(mt.lookupValue(operand));
2221 for (Value operand : curOp.getOperandB())
2222 args.push_back(mt.lookupValue(operand));
2223 for (Value operand : curOp.getOperandC())
2224 args.push_back(mt.lookupValue(operand));
2225
2226 // Add sparse metadata and selector
2227 args.push_back(mt.lookupValue(curOp.getSparseMetadata()));
2228 args.push_back(mt.lookupValue(curOp.getSparsitySelector()));
2229
2230 // Add scale operands
2231 args.push_back(mt.lookupValue(curOp.getScaleAData()));
2232 args.push_back(mt.lookupValue(curOp.getByteIdA()));
2233 args.push_back(mt.lookupValue(curOp.getThreadIdA()));
2234 args.push_back(mt.lookupValue(curOp.getScaleBData()));
2235 args.push_back(mt.lookupValue(curOp.getByteIdB()));
2236 args.push_back(mt.lookupValue(curOp.getThreadIdB()));
2237
2238 unsigned intId = MmaSpBlockScaleOp::getIntrinsicID(
2239 curOp.getShape().getM(), curOp.getShape().getN(), curOp.getShape().getK(),
2240 *curOp.getMultiplicandAPtxType(), *curOp.getMultiplicandBPtxType(),
2241 inferPtxTypeFromResult(curOp), curOp.getScaleVecSize(),
2242 curOp.getBlockScaleFormat(), curOp.getKind());
2243
2244 return {intId, args};
2245}
2246
2247LogicalResult MmaSpBlockScaleOp::verify() {
2248 // Check that orderedMetadata is present
2249 if (!getOrderedMetadata()) {
2250 return emitOpError("'orderedMetadata' attribute is mandatory");
2251 }
2252
2253 LogicalResult result = success();
2254 int m = getShape().getM();
2255 int n = getShape().getN();
2256 int k = getShape().getK();
2257
2258 if (m == 16 && n == 8 && k == 128) {
2259 if (getMultiplicandAPtxType() != NVVM::MMATypes::e2m1 ||
2260 getMultiplicandBPtxType() != NVVM::MMATypes::e2m1)
2262 "unsupported MMATypes attribute for mma.m16n8k128.(mxf4nvf4|mxf4)");
2263 if (getKind() == NVVM::MMABlockScaleKind::MXF4) {
2264 if (getScaleVecSize() != NVVM::ScaleVecSize::X2)
2266 "unsupported ScaleVecSize attribute for mma.m16n8k128.mxf4");
2267 if (getBlockScaleFormat() != NVVM::BlockScaleFormat::UE8M0)
2269 "unsupported BlockScaleFormat attribute for mma.m16n8k128.mxf4");
2270 } else if (getKind() == NVVM::MMABlockScaleKind::MXF4NVF4) {
2271 if (!((getScaleVecSize() == NVVM::ScaleVecSize::X2 &&
2272 getBlockScaleFormat() == NVVM::BlockScaleFormat::UE8M0) ||
2273 (getScaleVecSize() == NVVM::ScaleVecSize::X4 &&
2274 (getBlockScaleFormat() == NVVM::BlockScaleFormat::UE4M3 ||
2275 getBlockScaleFormat() == NVVM::BlockScaleFormat::UE8M0))))
2276 result = emitOpError("unsupported ScaleVecSize and BlockScaleFormat "
2277 "attributes for mma.m16n8k128.mxf4nvf4");
2278 } else {
2279 result = emitOpError("unsupported Kind attribute for mma.m16n8k128");
2280 }
2281 } else if (m == 16 && n == 8 && k == 64) {
2282 if (!(getKind() == NVVM::MMABlockScaleKind::MXF8F6F4 &&
2283 getScaleVecSize() == NVVM::ScaleVecSize::X1 &&
2284 getBlockScaleFormat() == NVVM::BlockScaleFormat::UE8M0))
2285 result =
2286 emitOpError("unsupported Kind, ScaleVecSize and BlockScaleFormat "
2287 "attributes for mma.m16n8k64");
2288 } else {
2289 result = emitOpError("unsupported Geom for sparse mma with block scaling");
2290 }
2291 return result;
2292}
2293
2294LogicalResult ShflOp::verify() {
2295 auto returnStructType = llvm::dyn_cast<LLVM::LLVMStructType>(getType());
2296
2297 auto verifyTypeError = [&](Twine desc, Type expectedType,
2298 Type actualType) -> LogicalResult {
2299 return emitOpError("expected " + desc + " to be of type ")
2300 << expectedType << " but got " << actualType << " instead";
2301 };
2302
2303 if (returnStructType) {
2304 if (!getReturnValueAndIsValid())
2305 return emitOpError("\"return_value_and_is_valid\" attribute must be "
2306 "specified when the return type is a struct type");
2307
2308 if (returnStructType.getBody().size() != 2)
2309 return emitOpError("expected return type to be a two-element struct");
2310
2311 llvm::ArrayRef<Type> returnStruct = returnStructType.getBody();
2312 auto resultType = returnStruct[0];
2313 if (resultType != getVal().getType())
2314 return verifyTypeError("first element in the returned struct",
2315 getVal().getType(), resultType);
2316
2317 auto predicateType = returnStruct[1];
2318 if (!predicateType.isInteger(1))
2319 return verifyTypeError("second element in the returned struct",
2320 mlir::IntegerType::get(getContext(), 1),
2321 predicateType);
2322 } else {
2323 if (getReturnValueAndIsValid())
2324 return emitOpError("expected return type to be a two-element struct");
2325
2326 if (getType() != getVal().getType())
2327 return verifyTypeError("return type", getVal().getType(), getType());
2328 }
2329 return success();
2330}
2331
2332LogicalResult
2333ShflOp::inferReturnTypes(MLIRContext *context, std::optional<Location> location,
2334 ShflOp::Adaptor adaptor,
2335 SmallVectorImpl<Type> &inferredReturnTypes) {
2336 Type valType = adaptor.getVal().getType();
2337 if (adaptor.getReturnValueAndIsValid())
2338 inferredReturnTypes.push_back(LLVM::LLVMStructType::getLiteral(
2339 context, {valType, IntegerType::get(context, 1)}));
2340 else
2341 inferredReturnTypes.push_back(valType);
2342 return success();
2343}
2344
2345std::pair<mlir::Type, unsigned> NVVM::inferMMAType(NVVM::MMATypes type,
2346 NVVM::MMAFrag frag, int nRow,
2347 int nCol,
2348 MLIRContext *context) {
2349 unsigned numberElements = 0;
2350 Type elementType;
2351 OpBuilder builder(context);
2352 Type f16x2 = VectorType::get(2, builder.getF16Type());
2353 if (type == NVVM::MMATypes::f16) {
2354 elementType = f16x2;
2355 if (frag == NVVM::MMAFrag::a || frag == NVVM::MMAFrag::b)
2356 numberElements = 8;
2357 else
2358 numberElements = 4;
2359 } else if (type == NVVM::MMATypes::f32) {
2360 elementType = builder.getF32Type();
2361 numberElements = 8;
2362 } else if (type == NVVM::MMATypes::f64) {
2363 elementType = builder.getF64Type();
2364 if (frag == NVVM::MMAFrag::a || frag == NVVM::MMAFrag::b)
2365 numberElements = 1;
2366 else
2367 numberElements = 2;
2368 } else if (type == NVVM::MMATypes::tf32) {
2369 elementType = builder.getI32Type();
2370 numberElements = 4;
2371 } else if (type == NVVM::MMATypes::s8 || type == NVVM::MMATypes::u8) {
2372 elementType = builder.getI32Type();
2373 int parallelSize = 0;
2374 if (frag == NVVM::MMAFrag::a)
2375 parallelSize = nRow;
2376 if (frag == NVVM::MMAFrag::b)
2377 parallelSize = nCol;
2378
2379 // m == 16 && n == 16 && k == 16
2380 if (parallelSize == 16)
2381 numberElements = 2;
2382 // m == 8 && n == 32 && k == 16 or m == 32 && n == 8 && k == 16
2383 else if (parallelSize == 8)
2384 numberElements = 1;
2385 else if (parallelSize == 32)
2386 numberElements = 4;
2387 } else if (type == NVVM::MMATypes::s32) {
2388 elementType = builder.getI32Type();
2389 numberElements = 8;
2390 }
2391 assert(numberElements != 0 && elementType != nullptr);
2392 return std::make_pair(elementType, numberElements);
2393}
2394
2395static std::pair<mlir::Type, unsigned>
2396inferMMATypeFromMNK(NVVM::MMATypes type, NVVM::MMAFrag frag, int m, int n,
2397 int k, MLIRContext *context) {
2398 int nRow, nCol;
2399 if (frag == NVVM::MMAFrag::a) {
2400 nRow = m;
2401 nCol = k;
2402 } else if (frag == NVVM::MMAFrag::b) {
2403 nRow = k;
2404 nCol = n;
2405 } else {
2406 nRow = m;
2407 nCol = n;
2408 }
2409 assert(nRow && nCol);
2410 return inferMMAType(type, frag, nRow, nCol, context);
2411}
2412
2413LogicalResult NVVM::WMMALoadOp::verify() {
2414 unsigned addressSpace =
2415 llvm::cast<LLVM::LLVMPointerType>(getPtr().getType()).getAddressSpace();
2416 if (addressSpace != 0 && addressSpace != NVVMMemorySpace::Global &&
2417 addressSpace != NVVMMemorySpace::Shared)
2418 return emitOpError("expected source pointer in memory "
2419 "space 0, 1, 3");
2420
2421 if (NVVM::WMMALoadOp::getIntrinsicID(getM(), getN(), getK(), getLayout(),
2422 getEltype(), getFrag()) == 0)
2423 return emitOpError() << "invalid attribute combination";
2424 std::pair<Type, unsigned> typeInfo = inferMMATypeFromMNK(
2425 getEltype(), getFrag(), getM(), getN(), getK(), getContext());
2426 // Special case for f64 fragments
2427 Type f64Ty = Float64Type::get(getContext());
2428 if (typeInfo.first == f64Ty && typeInfo.second == 1) {
2429 if (getType() != f64Ty)
2430 return emitOpError("expected destination type to be f64");
2431 return success();
2432 }
2433 // Everything else is a struct
2434 Type dstType = LLVM::LLVMStructType::getLiteral(
2435 getContext(), SmallVector<Type, 8>(typeInfo.second, typeInfo.first));
2436 if (getType() != dstType)
2437 return emitOpError("expected destination type is a structure of ")
2438 << typeInfo.second << " elements of type " << typeInfo.first;
2439 return success();
2440}
2441
2442LogicalResult NVVM::WMMAStoreOp::verify() {
2443 unsigned addressSpace =
2444 llvm::cast<LLVM::LLVMPointerType>(getPtr().getType()).getAddressSpace();
2445 if (addressSpace != 0 && addressSpace != NVVMMemorySpace::Global &&
2446 addressSpace != NVVMMemorySpace::Shared)
2447 return emitOpError("expected operands to be a source pointer in memory "
2448 "space 0, 1, 3");
2449
2450 if (NVVM::WMMAStoreOp::getIntrinsicID(getM(), getN(), getK(), getLayout(),
2451 getEltype()) == 0)
2452 return emitOpError() << "invalid attribute combination";
2453 std::pair<Type, unsigned> typeInfo = inferMMATypeFromMNK(
2454 getEltype(), NVVM::MMAFrag::c, getM(), getN(), getK(), getContext());
2455 if (getArgs().size() != typeInfo.second)
2456 return emitOpError() << "expected " << typeInfo.second << " data operands";
2457 if (llvm::any_of(getArgs(), [&typeInfo](Value operands) {
2458 return operands.getType() != typeInfo.first;
2459 }))
2460 return emitOpError() << "expected data operands of type " << typeInfo.first;
2461 return success();
2462}
2463
2464LogicalResult NVVM::WMMAMmaOp::verify() {
2465 if (NVVM::WMMAMmaOp::getIntrinsicID(getM(), getN(), getK(), getLayoutA(),
2466 getLayoutB(), getEltypeA(),
2467 getEltypeB()) == 0)
2468 return emitOpError() << "invalid attribute combination";
2469 std::pair<Type, unsigned> typeInfoA = inferMMATypeFromMNK(
2470 getEltypeA(), NVVM::MMAFrag::a, getM(), getN(), getK(), getContext());
2471 std::pair<Type, unsigned> typeInfoB = inferMMATypeFromMNK(
2472 getEltypeA(), NVVM::MMAFrag::b, getM(), getN(), getK(), getContext());
2473 std::pair<Type, unsigned> typeInfoC = inferMMATypeFromMNK(
2474 getEltypeB(), NVVM::MMAFrag::c, getM(), getN(), getK(), getContext());
2475 SmallVector<Type, 32> arguments;
2476 arguments.append(typeInfoA.second, typeInfoA.first);
2477 arguments.append(typeInfoB.second, typeInfoB.first);
2478 arguments.append(typeInfoC.second, typeInfoC.first);
2479 unsigned numArgs = arguments.size();
2480 if (getArgs().size() != numArgs)
2481 return emitOpError() << "expected " << numArgs << " arguments";
2482 for (unsigned i = 0; i < numArgs; i++) {
2483 if (getArgs()[i].getType() != arguments[i])
2484 return emitOpError() << "expected argument " << i << " to be of type "
2485 << arguments[i];
2486 }
2487 Type dstType = LLVM::LLVMStructType::getLiteral(
2488 getContext(), SmallVector<Type, 8>(typeInfoC.second, typeInfoC.first));
2489 if (getType() != dstType)
2490 return emitOpError("expected destination type is a structure of ")
2491 << typeInfoC.second << " elements of type " << typeInfoC.first;
2492 return success();
2493}
2494
2495LogicalResult NVVM::LdMatrixOp::verify() {
2496 uint32_t num = getNum(), m = getShape().getM(), n = getShape().getN();
2497 if (m == 8 && n == 8) {
2498 if (num != 1 && num != 2 && num != 4) {
2499 return emitOpError("expected num attribute to be 1, 2 or 4 for 8x8 "
2500 "matrix");
2501 }
2502 if (getEltType() != LdStMatrixEltType::B16) {
2503 return emitOpError("expected element type to be b16 for 8x8 matrix");
2504 }
2505 } else if (m == 8 && n == 16) {
2506 if (num != 1 && num != 2 && num != 4) {
2507 return emitOpError("expected num attribute to be 1, 2 or 4 for 8x16 "
2508 "matrix");
2509 }
2510 if (getLayout() != MMALayout::row) {
2511 return emitOpError("expected layout to be row for 8x16 matrix");
2512 }
2513 if (getEltType() != LdStMatrixEltType::B8X16_B4X16_P64 &&
2514 getEltType() != LdStMatrixEltType::B8X16_B6X16_P32) {
2515 return emitOpError("expected element type to be b8x16.b4x16_p64 or "
2516 "b8x16.b6x16_p32 for 8x16 matrix");
2517 }
2518 } else if (m == 16 && n == 16) {
2519 if (num != 1 && num != 2) {
2520 return emitOpError("expected num attribute to be 1 or 2 for 16x16 "
2521 "matrix");
2522 }
2523 if (getLayout() != MMALayout::col) {
2524 return emitOpError("expected layout to be col for 16x16 matrix");
2525 }
2526 if (getEltType() != LdStMatrixEltType::B8 &&
2527 getEltType() != LdStMatrixEltType::B8X16_B4X16_P64 &&
2528 getEltType() != LdStMatrixEltType::B8X16_B6X16_P32) {
2529 return emitOpError("expected element type to be b8, b8x16.b4x16_p64 or "
2530 "b8x16.b6x16_p32 for 16x16 matrix");
2531 }
2532 } else {
2533 return emitOpError("expected shape to be 8x8, 8x16 or 16x16");
2534 }
2535
2536 Type i32 = IntegerType::get(getContext(), 32);
2537 uint32_t numElements = (m == 16 && n == 16 ? num * 2 : num);
2538 if (numElements == 1 && getType() != i32)
2539 return emitOpError("expected destination type is i32");
2540 if (numElements == 2 || numElements == 4) {
2541 Type dstType = LLVM::LLVMStructType::getLiteral(
2542 getContext(), SmallVector<Type>(numElements, i32));
2543 if (getType() != dstType)
2544 return emitOpError("expected destination type is a structure of ")
2545 << numElements << " elements of type i32";
2546 }
2547
2548 return success();
2549}
2550
2551LogicalResult LdMatrixOp::inferReturnTypes(
2552 MLIRContext *context, std::optional<Location> location,
2553 LdMatrixOp::Adaptor adaptor, SmallVectorImpl<Type> &inferredReturnTypes) {
2554 uint32_t num = adaptor.getNum();
2555 uint32_t m = adaptor.getShape().getM();
2556 uint32_t n = adaptor.getShape().getN();
2557 uint32_t numElements = (m == 16 && n == 16) ? num * 2 : num;
2558
2559 Type i32 = IntegerType::get(context, 32);
2560 if (numElements == 1)
2561 inferredReturnTypes.push_back(i32);
2562 else
2563 inferredReturnTypes.push_back(LLVM::LLVMStructType::getLiteral(
2564 context, SmallVector<Type>(numElements, i32)));
2565 return success();
2566}
2567
2568LogicalResult NVVM::StMatrixOp::verify() {
2569 int numMatrix = getSources().size();
2570 if (numMatrix != 1 && numMatrix != 2 && numMatrix != 4)
2571 return emitOpError("expected num attribute to be 1, 2 or 4");
2572
2573 int m = getShape().getM(), n = getShape().getN();
2574 if (m == 8 && n == 8) {
2575 if (getEltType() != NVVM::LdStMatrixEltType::B16) {
2576 return emitOpError("expected element type to be B16 for 8x8 matrix");
2577 }
2578 } else if (m == 16 && n == 8) {
2579 if (getEltType() != NVVM::LdStMatrixEltType::B8) {
2580 return emitOpError("expected element type to be B8 for 16x8 matrix");
2581 }
2582 if (getLayout() != NVVM::MMALayout::col) {
2583 return emitOpError("expected layout to be col for 16x8 matrix");
2584 }
2585 } else {
2586 return emitOpError("expected shape to be 8x8 or 16x8");
2587 }
2588
2589 return success();
2590}
2591
2592LogicalResult NVVM::MovMatrixOp::verify() {
2593 int m = getShape().getM(), n = getShape().getN();
2594 if (m != 8 || n != 8)
2595 return emitOpError("expected shape to be 8x8");
2596 if (getLayout() != NVVM::MMALayout::col)
2597 return emitOpError("expected layout to be col");
2598 if (getEltType() != NVVM::LdStMatrixEltType::B16)
2599 return emitOpError("expected element type to be b16");
2600 return success();
2601}
2602
2603static FailureOr<int> getAllowedSizeK(NVVM::WGMMATypes typeA) {
2604 if (typeA == NVVM::WGMMATypes::tf32)
2605 return 8;
2606 if (typeA == NVVM::WGMMATypes::f16 || typeA == NVVM::WGMMATypes::bf16)
2607 return 16;
2608 if (typeA == NVVM::WGMMATypes::s8 || typeA == NVVM::WGMMATypes::u8)
2609 return 32;
2610 if (typeA == NVVM::WGMMATypes::e4m3 || typeA == NVVM::WGMMATypes::e5m2)
2611 return 32;
2612 if (typeA == NVVM::WGMMATypes::b1)
2613 return 256;
2614 return failure();
2615}
2616
2617static LogicalResult isAllowedWGMMADataType(NVVM::WGMMATypes typeD,
2618 NVVM::WGMMATypes typeA,
2619 NVVM::WGMMATypes typeB) {
2620 switch (typeA) {
2621 case NVVM::WGMMATypes::f16:
2622 if ((typeD == NVVM::WGMMATypes::f32 || typeD == NVVM::WGMMATypes::f16) &&
2623 typeB == NVVM::WGMMATypes::f16)
2624 return success();
2625 break;
2626 case NVVM::WGMMATypes::tf32:
2627 if (typeD == NVVM::WGMMATypes::f32 && typeB == NVVM::WGMMATypes::tf32)
2628 return success();
2629 break;
2630 case NVVM::WGMMATypes::u8:
2631 case NVVM::WGMMATypes::s8:
2632 if (typeD == NVVM::WGMMATypes::s32 &&
2633 (typeB == NVVM::WGMMATypes::u8 || typeB == NVVM::WGMMATypes::s8))
2634 return success();
2635 break;
2636 case NVVM::WGMMATypes::b1:
2637 if (typeD == NVVM::WGMMATypes::s32 && typeB == NVVM::WGMMATypes::b1)
2638 return success();
2639 break;
2640 case NVVM::WGMMATypes::bf16:
2641 if ((typeD == NVVM::WGMMATypes::f32 || typeD == NVVM::WGMMATypes::f16) &&
2642 typeB == NVVM::WGMMATypes::bf16)
2643 return success();
2644 break;
2645 case NVVM::WGMMATypes::e4m3:
2646 case NVVM::WGMMATypes::e5m2:
2647 if ((typeD == NVVM::WGMMATypes::f32 || typeD == NVVM::WGMMATypes::f16) &&
2648 (typeB == NVVM::WGMMATypes::e5m2 || typeB == NVVM::WGMMATypes::e4m3))
2649 return success();
2650 break;
2651 case WGMMATypes::f32:
2652 case WGMMATypes::s32:
2653 llvm_unreachable("unsupported input types");
2654 break;
2655 }
2656 return failure();
2657}
2658
2659static LogicalResult isAllowedSizeN(int sizeN, NVVM::WGMMATypes typeA) {
2660 SmallVector<int> allowedN = {8, 16, 24, 32, 40, 48, 56, 64,
2661 72, 80, 88, 96, 104, 112, 120, 128,
2662 136, 144, 152, 160, 168, 176, 184, 192,
2663 200, 208, 216, 224, 232, 240, 248, 256};
2664 SmallVector<int> allowedNshort = {8, 16, 24, 32, 48, 64,
2665 80, 96, 112, 128, 144, 160,
2666 176, 192, 208, 224, 240, 256};
2667 switch (typeA) {
2668 case WGMMATypes::f16:
2669 case WGMMATypes::tf32:
2670 case WGMMATypes::bf16:
2671 case WGMMATypes::e4m3:
2672 case WGMMATypes::e5m2:
2673 if (llvm::is_contained(allowedN, sizeN))
2674 return success();
2675 break;
2676 case WGMMATypes::u8:
2677 case WGMMATypes::s8:
2678 case WGMMATypes::b1:
2679 if (llvm::is_contained(allowedNshort, sizeN))
2680 return success();
2681 break;
2682 case WGMMATypes::f32:
2683 case WGMMATypes::s32:
2684 llvm_unreachable("unsupported input types");
2685 break;
2686 }
2687 return failure();
2688}
2689
2690LogicalResult NVVM::WgmmaMmaAsyncOp::verify() {
2691 Value outValue = getResults();
2692 auto stype = dyn_cast<LLVM::LLVMStructType>(outValue.getType());
2693 if (!stype)
2694 return emitOpError() << "expected results to be struct";
2695 int outputSize = stype.getBody().size();
2696 WGMMATypes typeD = getTypeD();
2697 WGMMATypes typeA = getTypeA();
2698 WGMMATypes typeB = getTypeB();
2699
2700 for (Type t : stype.getBody()) {
2701 if (t != stype.getBody().front())
2702 return emitOpError()
2703 << "all elements in struct must be same type but there is " << t;
2704 }
2705
2706 if (typeD != WGMMATypes::f32 && typeD != WGMMATypes::f16 &&
2707 typeD != WGMMATypes::s32) {
2708 return emitOpError() << "does not support the given output type " << typeD;
2709 }
2710 if (typeD == WGMMATypes::s32 &&
2711 (getScaleA() == WGMMAScaleIn::neg || getScaleB() == WGMMAScaleIn::neg)) {
2712 return emitOpError() << "has s32 output, scaleA and scaleB cannot be neg";
2713 }
2714
2715 if (failed(isAllowedWGMMADataType(typeD, typeA, typeB))) {
2716 return emitOpError() << typeD << " += " << typeA << " * " << typeB
2717 << ", it is not supported.";
2718 }
2719
2720 // Check M
2721 if (getShape().getM() != 64)
2722 return emitOpError() << "shape 'm' must be 64";
2723
2724 // Check K
2725 FailureOr<int> allowedK = getAllowedSizeK(typeA);
2726 if (failed(allowedK) || allowedK.value() != getShape().getK())
2727 return emitOpError() << "shape 'k' must be " << allowedK.value()
2728 << " for input type " << typeA;
2729
2730 // Check N
2731 if (failed(isAllowedSizeN(getShape().getN(), typeA))) {
2732 return emitOpError() << "has input type " << typeA << " n is set to "
2733 << getShape().getN() << ", it is not supported.";
2734 }
2735
2736 // Check transpose (only available for f16/bf16)
2737 // Matrices A should be stored in row-major and B in column-major.
2738 // Only f16/bf16 matrices can be stored in either column-major or row-major
2739 // by setting the transpose value(imm-trans-a,imm-trans-b) in PTX code.
2740 if ((typeA != WGMMATypes::f16 && typeA != WGMMATypes::bf16) &&
2741 (getLayoutA() == mlir::NVVM::MMALayout::col ||
2742 getLayoutB() == mlir::NVVM::MMALayout::row)) {
2743 return emitOpError()
2744 << "given layouts layout_a = " << getLayoutA()
2745 << " and layout_b = " << getLayoutB() << " for input types " << typeA
2746 << " and " << typeB
2747 << " requires transpose. However, this is only supported for: "
2748 << MMATypes::f16 << " and " << MMATypes::bf16;
2749 }
2750
2751 // Check result registers
2752 int expectedOutput = 0;
2753 if (typeD == WGMMATypes::f32 || typeD == WGMMATypes::s32)
2754 expectedOutput = getShape().getN() / 2;
2755 if (typeD == WGMMATypes::f16)
2756 expectedOutput = getShape().getN() / 4;
2757 if (outputSize != expectedOutput) {
2758 return emitOpError() << "results " << expectedOutput
2759 << ", however output struct has " << outputSize
2760 << " elements";
2761 }
2762 // Check satfinite (only available for s32 accumulator)
2763 if (typeD != WGMMATypes::s32 &&
2764 getSatfinite().value_or(NVVM::MMAIntOverflow::wrapped) ==
2765 NVVM::MMAIntOverflow::satfinite) {
2766 return emitOpError()
2767 << " `satfinite` can be only used with s32 accumulator, however "
2768 "the current accumulator is "
2769 << typeD;
2770 }
2771
2772 return success();
2773}
2774
2775std::string NVVM::WgmmaMmaAsyncOp::getPtx() {
2776
2777 int m = getShape().getM(), n = getShape().getN(), k = getShape().getK();
2778 bool isF16 = getTypeA() == WGMMATypes::f16 || getTypeA() == WGMMATypes::bf16;
2779
2780 StringRef outputTypeName = stringifyWGMMATypes(getTypeD());
2781
2782 int expectedOutputRegisters = 0;
2783 if (getTypeD() == WGMMATypes::f16)
2784 expectedOutputRegisters = getShape().getN() / 4;
2785 else
2786 expectedOutputRegisters = getShape().getN() / 2;
2787
2788 std::string ptx;
2789 llvm::raw_string_ostream ss(ptx);
2790
2791 ss << "{\n"
2792 ".reg .pred p;\n"
2793 "setp.ne.b32 p, $"
2794 << ((expectedOutputRegisters * 2) + 2)
2795 << ", 0;\n"
2796 "wgmma.mma_async.sync.aligned.m"
2797 << m << "n" << n << "k" << k << "." << outputTypeName << "." << getTypeA()
2798 << "." << getTypeB();
2799 if (getSatfinite().value_or(NVVM::MMAIntOverflow::wrapped) ==
2800 NVVM::MMAIntOverflow::satfinite)
2801 ss << ".satfinite";
2802 ss << " {";
2803 int regCnt = 0;
2804 for (; regCnt < expectedOutputRegisters; ++regCnt) {
2805 ss << "$" << regCnt;
2806 if (regCnt != expectedOutputRegisters - 1)
2807 ss << ", ";
2808 }
2809
2810 ss << "},";
2811 // Need to map read/write registers correctly.
2812 regCnt = (regCnt * 2);
2813 ss << " $" << (regCnt) << "," << " $" << (regCnt + 1) << "," << " p";
2814 if (getTypeD() != WGMMATypes::s32) {
2815 ss << ", $" << (regCnt + 3) << ", $" << (regCnt + 4);
2816 }
2817 // Don't add transpose parameters unless needed.
2818 if (isF16) {
2819 ss << ", $" << (regCnt + 5) << ", $" << (regCnt + 6);
2820 }
2821 ss << ";\n"
2822 << "}\n";
2823 return ptx;
2824}
2825
2826bool NVVM::WgmmaMmaAsyncOp::getAsmValues(
2827 RewriterBase &rewriter,
2828 llvm::SmallVectorImpl<std::pair<mlir::Value, mlir::NVVM::PTXRegisterMod>>
2829 &asmValues) {
2830 bool isF16 = getTypeA() == WGMMATypes::f16 || getTypeA() == WGMMATypes::bf16;
2831 if (getResults())
2832 asmValues.push_back({getResults(), mlir::NVVM::PTXRegisterMod::Write});
2833 if (getInouts())
2834 asmValues.push_back({getInouts(), mlir::NVVM::PTXRegisterMod::ReadWrite});
2835 asmValues.push_back({getDescriptorA(), mlir::NVVM::PTXRegisterMod::Read});
2836 asmValues.push_back({getDescriptorB(), mlir::NVVM::PTXRegisterMod::Read});
2837 asmValues.push_back({makeConstantI32(rewriter, static_cast<int>(getScaleD())),
2839 if (getTypeD() != WGMMATypes::s32) {
2840 asmValues.push_back(
2841 {makeConstantI32(rewriter,
2842 getScaleA() == NVVM::WGMMAScaleIn::neg ? -1 : 1),
2844 asmValues.push_back(
2845 {makeConstantI32(rewriter,
2846 getScaleB() == NVVM::WGMMAScaleIn::neg ? -1 : 1),
2848 }
2849 if (isF16) {
2850 asmValues.push_back(
2851 {makeConstantI32(rewriter, static_cast<int>(getLayoutA())),
2853 asmValues.push_back(
2854 {makeConstantI32(rewriter, 1 - static_cast<int>(getLayoutB())),
2856 }
2857 return true; // Has manual mapping
2858}
2859
2860LogicalResult NVVM::FenceProxyOp::verify() {
2861 if (getKind() == NVVM::ProxyKind::async_shared && !getSpace().has_value()) {
2862 return emitOpError() << "async_shared fence requires space attribute";
2863 }
2864 if (getKind() != NVVM::ProxyKind::async_shared && getSpace().has_value()) {
2865 return emitOpError() << "only async_shared fence can have space attribute";
2866 }
2867 return success();
2868}
2869
2870LogicalResult NVVM::FenceProxyAcquireOp::verify() {
2871 if (getFromProxy() != NVVM::ProxyKind::GENERIC)
2872 return emitOpError("uni-directional proxies only support generic for "
2873 "from_proxy attribute");
2874
2875 if (getToProxy() != NVVM::ProxyKind::TENSORMAP)
2876 return emitOpError("uni-directional proxies only support tensormap "
2877 "for to_proxy attribute");
2878 return success();
2879}
2880
2881LogicalResult NVVM::FenceProxyReleaseOp::verify() {
2882 if (getFromProxy() != NVVM::ProxyKind::GENERIC)
2883 return emitOpError("uni-directional proxies only support generic for "
2884 "from_proxy attribute");
2885
2886 if (getToProxy() != NVVM::ProxyKind::TENSORMAP)
2887 return emitOpError("uni-directional proxies only support tensormap "
2888 "for to_proxy attribute");
2889 return success();
2890}
2891
2892LogicalResult NVVM::FenceProxySyncRestrictOp::verify() {
2893 if (getFromProxy() != NVVM::ProxyKind::GENERIC)
2894 return emitOpError("only generic is support for from_proxy attribute");
2895
2896 if (getToProxy() != NVVM::ProxyKind::async)
2897 return emitOpError("only async is supported for to_proxy attribute");
2898 return success();
2899}
2900
2901LogicalResult NVVM::SetMaxRegisterOp::verify() {
2902 if (getRegCount() % 8)
2903 return emitOpError("new register size must be multiple of 8");
2904 if (getRegCount() < 24 || getRegCount() > 256)
2905 return emitOpError("new register size must be in between 24 to 256");
2906 return success();
2907}
2908
2909LogicalResult NVVM::Tcgen05CpOp::verify() {
2910 auto mc = getMulticast();
2911
2912 using SH = Tcgen05CpShape;
2913 using MC = Tcgen05CpMulticast;
2914 switch (getShape()) {
2915 case SH::SHAPE_128x256b:
2916 case SH::SHAPE_128x128b:
2917 case SH::SHAPE_4x256b:
2918 if (mc != MC::NONE)
2919 return emitError("Invalid multicast type for tcgen05.cp Op");
2920 break;
2921 case SH::SHAPE_64x128b:
2922 if (mc != MC::WARPX2_01_23 && mc != MC::WARPX2_02_13)
2923 return emitError("Shape 64x128b requires multicast warpx2_01_23 or "
2924 "warpx2_02_13 for tcgen05.cp Op");
2925 break;
2926 case SH::SHAPE_32x128b:
2927 if (mc != MC::WARPX4)
2928 return emitError(
2929 "Shape 32x128b requires multicast warpx4 for tcgen05.cp Op");
2930 break;
2931 }
2932 return success();
2933}
2934
2935LogicalResult NVVM::MatchSyncOp::verify() {
2936 if (getKind() == NVVM::MatchSyncKind::all) {
2937 auto type = llvm::dyn_cast<LLVM::LLVMStructType>(getType());
2938 if (!type || type.getBody().size() != 2 ||
2939 !type.getBody()[0].isInteger(32) || !type.getBody()[1].isInteger(1)) {
2940 return emitOpError("match.sync 'all' returns a two element struct with "
2941 "first element as i32 and second element as i1");
2942 }
2943 } else {
2944 if (!getType().isInteger(32)) {
2945 return emitOpError("match.sync 'any' returns an i32");
2946 }
2947 }
2948 return success();
2949}
2950
2951LogicalResult MatchSyncOp::inferReturnTypes(
2952 MLIRContext *context, std::optional<Location> location,
2953 MatchSyncOp::Adaptor adaptor, SmallVectorImpl<Type> &inferredReturnTypes) {
2954 if (adaptor.getKind() == NVVM::MatchSyncKind::all)
2955 inferredReturnTypes.push_back(LLVM::LLVMStructType::getLiteral(
2956 context,
2957 {IntegerType::get(context, 32), IntegerType::get(context, 1)}));
2958 else
2959 inferredReturnTypes.push_back(IntegerType::get(context, 32));
2960 return success();
2961}
2962
2963LogicalResult NVVM::VoteSyncOp::verify() {
2964 if (getKind() == NVVM::VoteSyncKind::ballot) {
2965 if (!getType().isInteger(32)) {
2966 return emitOpError("vote.sync 'ballot' returns an i32");
2967 }
2968 } else {
2969 if (!getType().isInteger(1)) {
2970 return emitOpError("vote.sync 'any', 'all' and 'uni' returns an i1");
2971 }
2972 }
2973 return success();
2974}
2975
2976LogicalResult VoteSyncOp::inferReturnTypes(
2977 MLIRContext *context, std::optional<Location> location,
2978 VoteSyncOp::Adaptor adaptor, SmallVectorImpl<Type> &inferredReturnTypes) {
2979 unsigned width = adaptor.getKind() == NVVM::VoteSyncKind::ballot ? 32 : 1;
2980 inferredReturnTypes.push_back(IntegerType::get(context, width));
2981 return success();
2982}
2983
2984LogicalResult NVVM::PrefetchOp::verify() {
2985 using MemSpace = NVVM::NVVMMemorySpace;
2986 using CacheLevel = NVVM::PrefetchCacheLevel;
2987
2988 unsigned addressSpace =
2989 llvm::cast<LLVM::LLVMPointerType>(getAddr().getType()).getAddressSpace();
2990 std::optional<NVVM::CacheEvictionPriority> evictPriority = getEvictPriority();
2991 std::optional<NVVM::PrefetchCacheLevel> cacheLevel = getCacheLevel();
2992
2993 if (getTensormap() && cacheLevel)
2994 return emitOpError("cannot specify both tensormap and cache level");
2995
2996 if (getTensormap()) {
2997 if (addressSpace != MemSpace::Generic &&
2998 addressSpace != MemSpace::Constant) {
2999 return emitOpError(
3000 "prefetch tensormap requires a generic or constant pointer");
3001 }
3002
3003 if (evictPriority) {
3004 return emitOpError(
3005 "prefetch tensormap does not support eviction priority");
3006 }
3007
3008 if (getInParamSpace() && addressSpace != MemSpace::Generic) {
3009 return emitOpError(
3010 "in_param_space can only be specified for a generic pointer");
3011 }
3012
3013 } else if (cacheLevel) {
3014 if (addressSpace != MemSpace::Generic && addressSpace != MemSpace::Global &&
3015 addressSpace != MemSpace::Local) {
3016 return emitOpError("prefetch to cache level requires a generic, global, "
3017 "or local pointer");
3018 }
3019
3020 if (getUniform()) {
3021 if (*cacheLevel != CacheLevel::L1) {
3022 return emitOpError(
3023 "unsupported cache level, the only supported uniform "
3024 "cache level is L1");
3025 }
3026
3027 if (addressSpace != MemSpace::Generic) {
3028 return emitOpError(
3029 "prefetch to uniform cache requires a generic pointer");
3030 }
3031 }
3032
3033 if (evictPriority) {
3034 if (*cacheLevel != CacheLevel::L2)
3035 return emitOpError(
3036 "cache eviction priority supported only for cache level L2");
3037
3038 if (addressSpace != MemSpace::Global)
3039 return emitOpError("cache eviction priority requires a global pointer");
3040
3041 if (*evictPriority != NVVM::CacheEvictionPriority::EvictNormal &&
3042 *evictPriority != NVVM::CacheEvictionPriority::EvictLast)
3043 return emitOpError(
3044 "unsupported cache eviction priority, only evict_last and "
3045 "evict_normal are supported");
3046 }
3047
3048 if (getPredicate())
3049 return emitOpError("predicate supported only on prefetch tensormap");
3050
3051 } else {
3052 return emitOpError(
3053 "requires specification of either cache level or tensormap");
3054 }
3055
3056 return success();
3057}
3058
3059LogicalResult NVVM::ClusterLaunchControlQueryCancelOp::verify() {
3060 switch (getQueryType()) {
3061 case NVVM::ClusterLaunchControlQueryType::IS_CANCELED:
3062 if (!getType().isInteger(1))
3063 return emitOpError("is_canceled query type returns an i1");
3064 break;
3065 case NVVM::ClusterLaunchControlQueryType::GET_FIRST_CTA_ID_X:
3066 case NVVM::ClusterLaunchControlQueryType::GET_FIRST_CTA_ID_Y:
3067 case NVVM::ClusterLaunchControlQueryType::GET_FIRST_CTA_ID_Z:
3068 if (!getType().isInteger(32)) {
3069 return emitOpError("get_first_cta_id_x, get_first_cta_id_y, "
3070 "get_first_cta_id_z query types return an i32");
3071 }
3072 break;
3073 }
3074 return success();
3075}
3076
3077LogicalResult ClusterLaunchControlQueryCancelOp::inferReturnTypes(
3078 MLIRContext *context, std::optional<Location> location,
3079 ClusterLaunchControlQueryCancelOp::Adaptor adaptor,
3080 SmallVectorImpl<Type> &inferredReturnTypes) {
3081 unsigned width =
3082 adaptor.getQueryType() == NVVM::ClusterLaunchControlQueryType::IS_CANCELED
3083 ? 1
3084 : 32;
3085 inferredReturnTypes.push_back(IntegerType::get(context, width));
3086 return success();
3087}
3088
3089LogicalResult NVVM::ReduxOp::verify() {
3090 mlir::Type reduxType = getType();
3091
3092 if (!reduxType.isF32()) {
3093 if (getAbs())
3094 return emitOpError("abs attribute is supported only for f32 type");
3095 if (getNan())
3096 return emitOpError("nan attribute is supported only for f32 type");
3097 }
3098
3099 NVVM::ReductionKind kind = getKind();
3100 switch (kind) {
3101 case NVVM::ReductionKind::ADD:
3102 case NVVM::ReductionKind::AND:
3103 case NVVM::ReductionKind::OR:
3104 case NVVM::ReductionKind::XOR:
3105 case NVVM::ReductionKind::MAX:
3106 case NVVM::ReductionKind::MIN:
3107 case NVVM::ReductionKind::UMAX:
3108 case NVVM::ReductionKind::UMIN:
3109 if (!reduxType.isInteger(32))
3110 return emitOpError("'")
3111 << kind << "' reduction kind unsupported with " << reduxType
3112 << " type. Only supported type is 'i32'.";
3113 break;
3114 case NVVM::ReductionKind::FMIN:
3115 case NVVM::ReductionKind::FMAX:
3116 if (!reduxType.isF32())
3117 return emitOpError("'")
3118 << kind << "' reduction kind unsupported with " << reduxType
3119 << " type. Only supported type is 'f32'.";
3120 break;
3121 }
3122
3123 return success();
3124}
3125
3126LogicalResult NVVM::TensormapReplaceOp::verify() {
3127 auto ord = getOrd();
3128 Value newVal = getNewValue();
3129 auto newValAttr = getNewValueAttr();
3130 auto fieldName = stringifyEnum(getField());
3131
3132 if (ord && !llvm::is_contained({NVVM::TensormapField::BOX_DIM,
3133 NVVM::TensormapField::GLOBAL_DIM,
3134 NVVM::TensormapField::GLOBAL_STRIDE,
3135 NVVM::TensormapField::ELEMENT_STRIDE},
3136 getField()))
3137 return emitOpError("ordinal is not supported for ")
3138 << fieldName << " field";
3139
3140 auto invalidNewVal = [&](llvm::Twine type) -> std::string {
3141 return llvm::Twine("new_value must be specified and must be an " + type +
3142 " for " + llvm::Twine(fieldName) + " field")
3143 .str();
3144 };
3145
3146 auto invalidNewValAttr = [&]() -> std::string {
3147 return (llvm::Twine(
3148 "new_value_attr must be specified and must be a valid ") +
3149 llvm::Twine(fieldName) + " attribute for " + fieldName + " field")
3150 .str();
3151 };
3152
3153 switch (getField()) {
3154 case NVVM::TensormapField::GLOBAL_ADDRESS:
3155 if (!(newVal && newVal.getType().isInteger(64)))
3156 return emitOpError(invalidNewVal("i64"));
3157 break;
3158 case NVVM::TensormapField::RANK:
3159 if (!(newVal && newVal.getType().isInteger(32)))
3160 return emitOpError(invalidNewVal("i32"));
3161 break;
3162 case NVVM::TensormapField::GLOBAL_STRIDE:
3163 if (!ord)
3164 return emitOpError("ordinal is required for global_stride field");
3165 if (!(newVal && newVal.getType().isInteger(64)))
3166 return emitOpError(invalidNewVal("i64"));
3167 break;
3168 case NVVM::TensormapField::BOX_DIM:
3169 case NVVM::TensormapField::GLOBAL_DIM:
3170 case NVVM::TensormapField::ELEMENT_STRIDE:
3171 if (!ord)
3172 return emitOpError("ordinal is required for ")
3173 << stringifyEnum(getField()) << " field";
3174 if (!(newVal && newVal.getType().isInteger(32)))
3175 return emitOpError(invalidNewVal("i32"));
3176 break;
3177 case NVVM::TensormapField::ELEMTYPE:
3178 if (!(newValAttr && llvm::isa<TensormapElemtypeAttr>(*newValAttr)))
3179 return emitOpError(invalidNewValAttr());
3180 break;
3181 case NVVM::TensormapField::INTERLEAVE_LAYOUT:
3182 if (!(newValAttr && llvm::isa<TensormapInterleaveLayoutAttr>(*newValAttr)))
3183 return emitOpError(invalidNewValAttr());
3184 break;
3185 case NVVM::TensormapField::SWIZZLE_MODE:
3186 if (!(newValAttr && llvm::isa<TensormapSwizzleModeAttr>(*newValAttr)))
3187 return emitOpError(invalidNewValAttr());
3188 break;
3189 case NVVM::TensormapField::SWIZZLE_ATOMICITY:
3190 if (!(newValAttr && llvm::isa<TensormapSwizzleAtomicityAttr>(*newValAttr)))
3191 return emitOpError(invalidNewValAttr());
3192 break;
3193 case NVVM::TensormapField::FILL_MODE:
3194 if (!(newValAttr && llvm::isa<TensormapFillModeAttr>(*newValAttr)))
3195 return emitOpError(invalidNewValAttr());
3196 break;
3197 }
3198
3199 return success();
3200}
3201
3202template <typename OpType>
3203static LogicalResult verifyAddSubFOp(OpType op) {
3204 mlir::NVVM::FPRoundingMode rndMode = op.getRnd();
3205 mlir::NVVM::SaturationMode satMode = op.getSat();
3206 bool isFTZ = op.getFtz();
3207
3208 mlir::Type opType = op.getRes().getType();
3209 mlir::Type opBaseType = isa<VectorType>(opType)
3210 ? cast<VectorType>(opType).getElementType()
3211 : opType;
3212
3213 if (opBaseType.isF64() && (satMode != NVVM::SaturationMode::NONE || isFTZ))
3214 return op.emitOpError("FTZ and saturation are not supported for "
3215 "additions/subtractions involving f64 type");
3216
3217 if (opBaseType.isF16() && !(rndMode == NVVM::FPRoundingMode::RN ||
3218 rndMode == NVVM::FPRoundingMode::NONE))
3219 return op.emitOpError("only RN rounding mode is supported for f16 and "
3220 "vector<2xf16> additions/subtractions");
3221
3222 if (opBaseType.isBF16()) {
3223 if (rndMode != NVVM::FPRoundingMode::RN &&
3224 rndMode != NVVM::FPRoundingMode::NONE)
3225 return op.emitOpError("only RN rounding mode is supported for bf16 and "
3226 "vector<2xbf16> additions/subtractions");
3227 if (satMode != NVVM::SaturationMode::NONE || isFTZ)
3228 return op.emitOpError("FTZ and saturation are not supported for bf16 and "
3229 "vector<2xbf16> additions/subtractions");
3230 }
3231
3232 // FIXME: This is a temporary check disallowing lowering to add.rn.ftz.f16(x2)
3233 // PTX instructions since the corresponding LLVM intrinsic is missing. This
3234 // should be removed once the intrinsics for f16 addition (with FTZ only) are
3235 // available.
3236 if (opBaseType.isF16() && isFTZ && satMode == NVVM::SaturationMode::NONE)
3237 return op.emitOpError("FTZ with no saturation is not supported for f16 and "
3238 "vector<2xf16> additions/subtractions");
3239
3240 return success();
3241}
3242
3243LogicalResult NVVM::AddFOp::verify() { return verifyAddSubFOp<AddFOp>(*this); }
3244
3245LogicalResult NVVM::SubFOp::verify() { return verifyAddSubFOp<SubFOp>(*this); }
3246
3247LogicalResult NVVM::FmaOp::verify() {
3248 auto opType = getRes().getType();
3249 mlir::NVVM::FPRoundingMode rndMode = getRnd();
3250 mlir::NVVM::SaturationMode satMode = getSat();
3251 bool isFTZ = getFtz();
3252 bool isRelu = getRelu();
3253 bool hasOOB = getOob();
3254
3255 auto getBaseFType = [](Type type) -> Type {
3256 if (isa<VectorType>(type))
3257 return cast<VectorType>(type).getElementType();
3258 return type;
3259 };
3260
3261 auto opBaseType = getBaseFType(opType);
3262
3263 if (rndMode == NVVM::FPRoundingMode::NONE)
3264 return emitOpError("rounding mode must be specified");
3265
3266 if (isRelu && satMode == NVVM::SaturationMode::SAT)
3267 return emitOpError("relu and saturation are not supported together");
3268
3269 if (hasOOB && (satMode == NVVM::SaturationMode::SAT || isFTZ))
3270 return emitOpError("oob is not supported with saturation or FTZ");
3271
3272 if (!(opBaseType.isF16() || opBaseType.isBF16()) && (isRelu || hasOOB))
3273 return emitOpError("relu and oob are only supported for f16 and bf16");
3274
3275 if (opBaseType.isF64() && (satMode != NVVM::SaturationMode::NONE || isFTZ))
3276 return emitOpError("FTZ and saturation are not supported for f64 type");
3277
3278 if (opBaseType.isF16() && rndMode != NVVM::FPRoundingMode::RN)
3279 return emitOpError(
3280 "only RN rounding mode is supported for f16 and vector<2xf16>");
3281
3282 if (opBaseType.isBF16()) {
3283 if (rndMode != NVVM::FPRoundingMode::RN)
3284 return emitOpError(
3285 "only RN rounding mode is supported for bf16 and vector<2xbf16>");
3286 if (satMode != NVVM::SaturationMode::NONE || isFTZ)
3287 return emitOpError(
3288 "FTZ and saturation are not supported for bf16 and vector<2xbf16>");
3289 }
3290
3291 return success();
3292}
3293
3294LogicalResult NVVM::SqrtOp::verify() {
3295 if (getRnd() == NVVM::FPRoundingMode::NONE)
3296 return emitOpError("rounding mode cannot be None");
3297
3298 if (getRes().getType().isF64() && getFtz())
3299 return emitOpError("FTZ is not supported for f64");
3300
3301 return success();
3302}
3303
3304LogicalResult NVVM::DivFOp::verify() {
3305 bool isApprox = getApprox();
3306 bool isFull = getFull();
3307 bool isF64 = getRes().getType().isF64();
3308 bool isFtz = getFtz();
3309 NVVM::FPRoundingMode rndMode = getRnd();
3310
3311 if (isApprox && isFull)
3312 return emitOpError("'approx' and 'full' are mutually exclusive");
3313
3314 if (isApprox || isFull) {
3315 if (isF64)
3316 return emitOpError("'approx' and 'full' forms are f32-only");
3317 if (rndMode != NVVM::FPRoundingMode::NONE)
3318 return emitOpError(
3319 "'approx' and 'full' forms do not accept a rounding mode");
3320 return success();
3321 }
3322
3323 // Rounded form below.
3324 if (rndMode == NVVM::FPRoundingMode::NONE)
3325 return emitOpError("rounding mode cannot be None for the rounded divide");
3326 if (isF64 && isFtz)
3327 return emitOpError("FTZ is not supported for f64");
3328
3329 return success();
3330}
3331
3332/// Packs the given `field` into the `result`.
3333/// The `result` is 64-bits and each `field` can be 32-bits or narrower.
3334static llvm::Value *
3335packValInto64Bits(llvm::IRBuilderBase &builder,
3336 llvm::Value *result, // the `result` (unset bits are zero)
3337 llvm::Value *field, // `field` to pack into `result`
3338 unsigned sizeInBits, // Size of `field` in bits
3339 unsigned start) { // Starting bit within `result`
3340 field = builder.CreateZExtOrBitCast(field, builder.getInt32Ty());
3341
3342 unsigned mask = (sizeInBits < 32 ? ((1u << sizeInBits) - 1) : 0xffffffffu);
3343 if (mask != 0xffffffffu)
3344 field = builder.CreateAnd(field, builder.getInt32(mask));
3345
3346 field = builder.CreateZExtOrBitCast(field, builder.getInt64Ty());
3347 field = builder.CreateShl(field, start);
3348
3349 return builder.CreateOr(result, field);
3350}
3351
3352void Tcgen05MmaSmemDescOp::createSmemDescriptor(Operation &op,
3354 llvm::IRBuilderBase &builder) {
3355 auto thisOp = cast<NVVM::Tcgen05MmaSmemDescOp>(op);
3356 llvm::Value *smemDesc = builder.getInt64(0);
3357
3358 smemDesc = packValInto64Bits(builder, smemDesc,
3359 mt.lookupValue(thisOp.getStartAddr()), 14, 0);
3360 smemDesc = packValInto64Bits(
3361 builder, smemDesc, mt.lookupValue(thisOp.getLeadingDimOffset()), 14, 16);
3362 smemDesc = packValInto64Bits(
3363 builder, smemDesc, mt.lookupValue(thisOp.getStrideDimOffset()), 14, 32);
3364
3365 smemDesc = packValInto64Bits(builder, smemDesc, builder.getInt32(1), 3, 46);
3366 smemDesc = packValInto64Bits(builder, smemDesc,
3367 mt.lookupValue(thisOp.getBaseOffset()), 3, 49);
3368 smemDesc = packValInto64Bits(
3369 builder, smemDesc, mt.lookupValue(thisOp.getLeadingDimMode()), 1, 52);
3370 smemDesc = packValInto64Bits(builder, smemDesc,
3371 mt.lookupValue(thisOp.getSwizzleMode()), 3, 61);
3372
3373 mt.mapValue(thisOp.getRes()) = smemDesc;
3374}
3375
3376//===----------------------------------------------------------------------===//
3377// getPtx methods
3378//===----------------------------------------------------------------------===//
3379
3380std::string NVVM::MBarrierInitOp::getPtx() {
3381 bool isShared = isPtrInSharedCTASpace(getAddr());
3382 return isShared ? std::string("mbarrier.init.shared.b64 [%0], %1;")
3383 : std::string("mbarrier.init.b64 [%0], %1;");
3384}
3385
3386std::string NVVM::MBarrierArriveExpectTxOp::getPtx() {
3387 bool isShared = isPtrInSharedCTASpace(getAddr());
3388 return isShared
3389 ? std::string("mbarrier.arrive.expect_tx.shared.b64 _, [%0], %1;")
3390 : std::string("mbarrier.arrive.expect_tx.b64 _, [%0], %1;");
3391}
3392
3393std::string NVVM::MBarrierTryWaitParityOp::getPtx() {
3394 bool isShared = isPtrInSharedCTASpace(getAddr());
3395 llvm::StringRef space = isShared ? ".shared" : "";
3396
3397 return llvm::formatv("{\n\t"
3398 ".reg .pred P1; \n\t"
3399 "LAB_WAIT: \n\t"
3400 "mbarrier.try_wait.parity{0}.b64 P1, [%0], %1, %2; \n\t"
3401 "@P1 bra.uni DONE; \n\t"
3402 "bra.uni LAB_WAIT; \n\t"
3403 "DONE: \n\t"
3404 "}",
3405 space);
3406}
3407
3408//===----------------------------------------------------------------------===//
3409// Canonicalization patterns
3410//===----------------------------------------------------------------------===//
3411
3414
3415 LogicalResult matchAndRewrite(SubFOp op,
3416 PatternRewriter &rewriter) const override {
3417 Location loc = op.getLoc();
3418 Value negRhs =
3419 LLVM::FNegOp::create(rewriter, loc, op.getRhs().getType(), op.getRhs());
3420
3421 rewriter.replaceOpWithNewOp<AddFOp>(op, op.getType(), op.getLhs(), negRhs,
3422 op.getRnd(), op.getSat(), op.getFtz());
3423 return success();
3424 }
3425};
3426
3427void SubFOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
3428 MLIRContext *context) {
3429 patterns.add<ConvertFsubToFnegFadd>(context);
3430}
3431
3432//===----------------------------------------------------------------------===//
3433// getIntrinsicID/getIntrinsicIDAndArgs methods
3434//===----------------------------------------------------------------------===//
3435
3436/// Maps the (aligned, hasCount) pair to the `@llvm.nvvm.barrier.cta.sync.*`
3437/// intrinsic ID.
3438static llvm::Intrinsic::ID getBarrierSyncIntrinsic(bool aligned,
3439 bool hasCount) {
3440 if (hasCount) {
3441 return aligned ? llvm::Intrinsic::nvvm_barrier_cta_sync_aligned_count
3442 : llvm::Intrinsic::nvvm_barrier_cta_sync_count;
3443 }
3444 return aligned ? llvm::Intrinsic::nvvm_barrier_cta_sync_aligned_all
3445 : llvm::Intrinsic::nvvm_barrier_cta_sync_all;
3446}
3447
3448/// Maps the (aligned, kind) pair to the `@llvm.nvvm.barrier.cta.red.*`
3449/// intrinsic ID.
3450static llvm::Intrinsic::ID
3451getBarrierReductionIntrinsic(bool aligned, NVVM::BarrierReduction kind) {
3452 switch (kind) {
3453 case NVVM::BarrierReduction::AND:
3454 return aligned ? llvm::Intrinsic::nvvm_barrier_cta_red_and_aligned_all
3455 : llvm::Intrinsic::nvvm_barrier_cta_red_and_all;
3456 case NVVM::BarrierReduction::OR:
3457 return aligned ? llvm::Intrinsic::nvvm_barrier_cta_red_or_aligned_all
3458 : llvm::Intrinsic::nvvm_barrier_cta_red_or_all;
3459 case NVVM::BarrierReduction::POPC:
3460 return aligned ? llvm::Intrinsic::nvvm_barrier_cta_red_popc_aligned_all
3461 : llvm::Intrinsic::nvvm_barrier_cta_red_popc_all;
3462 }
3463 llvm_unreachable("unknown BarrierReduction kind");
3464}
3465
3466mlir::NVVM::IDArgPair NVVM::BarrierOp::getIntrinsicIDAndArgs(
3467 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
3468 auto thisOp = cast<NVVM::BarrierOp>(op);
3469 llvm::Value *barrierId = thisOp.getBarrierId()
3470 ? mt.lookupValue(thisOp.getBarrierId())
3471 : builder.getInt32(0);
3472 bool hasCount = static_cast<bool>(thisOp.getNumberOfThreads());
3473 llvm::Intrinsic::ID id =
3474 getBarrierSyncIntrinsic(thisOp.getAligned(), hasCount);
3475 llvm::SmallVector<llvm::Value *> args = {barrierId};
3476 if (hasCount)
3477 args.push_back(mt.lookupValue(thisOp.getNumberOfThreads()));
3478 return {id, std::move(args)};
3479}
3480
3481mlir::NVVM::IDArgPair NVVM::BarrierArriveOp::getIntrinsicIDAndArgs(
3482 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
3483 auto thisOp = cast<NVVM::BarrierArriveOp>(op);
3484 llvm::Value *barrierId = thisOp.getBarrierId()
3485 ? mt.lookupValue(thisOp.getBarrierId())
3486 : builder.getInt32(0);
3487 llvm::Value *numThreads = mt.lookupValue(thisOp.getNumberOfThreads());
3488 llvm::Intrinsic::ID id =
3489 thisOp.getAligned()
3490 ? llvm::Intrinsic::nvvm_barrier_cta_arrive_aligned_count
3491 : llvm::Intrinsic::nvvm_barrier_cta_arrive_count;
3492 return {id, {barrierId, numThreads}};
3493}
3494
3495mlir::NVVM::IDArgPair NVVM::BarrierReductionOp::getIntrinsicIDAndArgs(
3496 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
3497 auto thisOp = cast<NVVM::BarrierReductionOp>(op);
3498 llvm::Intrinsic::ID id = getBarrierReductionIntrinsic(
3499 thisOp.getAligned(), thisOp.getReductionOp());
3500 llvm::Value *barrierId = thisOp.getBarrierId()
3501 ? mt.lookupValue(thisOp.getBarrierId())
3502 : builder.getInt32(0);
3504 barrierId,
3505 builder.CreateICmpNE(mt.lookupValue(thisOp.getReductionPredicate()),
3506 builder.getInt32(0))};
3507 return {id, std::move(args)};
3508}
3509
3511CosOp::getIntrinsicIDAndArgs(Operation &op, LLVM::ModuleTranslation &mt,
3512 llvm::IRBuilderBase &builder) {
3513 auto thisOp = cast<NVVM::CosOp>(op);
3514 llvm::Intrinsic::ID id = thisOp.getFtz()
3515 ? llvm::Intrinsic::nvvm_cos_approx_ftz_f
3516 : llvm::Intrinsic::nvvm_cos_approx_f;
3517 return {id, {mt.lookupValue(thisOp.getSrc())}};
3518}
3519
3521SinOp::getIntrinsicIDAndArgs(Operation &op, LLVM::ModuleTranslation &mt,
3522 llvm::IRBuilderBase &builder) {
3523 auto thisOp = cast<NVVM::SinOp>(op);
3524 llvm::Intrinsic::ID id = thisOp.getFtz()
3525 ? llvm::Intrinsic::nvvm_sin_approx_ftz_f
3526 : llvm::Intrinsic::nvvm_sin_approx_f;
3527 return {id, {mt.lookupValue(thisOp.getSrc())}};
3528}
3529
3531Log2Op::getIntrinsicIDAndArgs(Operation &op, LLVM::ModuleTranslation &mt,
3532 llvm::IRBuilderBase &builder) {
3533 auto thisOp = cast<NVVM::Log2Op>(op);
3534 llvm::Intrinsic::ID id = thisOp.getFtz()
3535 ? llvm::Intrinsic::nvvm_lg2_approx_ftz_f
3536 : llvm::Intrinsic::nvvm_lg2_approx_f;
3537 return {id, {mt.lookupValue(thisOp.getSrc())}};
3538}
3539
3541Ex2Op::getIntrinsicIDAndArgs(Operation &op, LLVM::ModuleTranslation &mt,
3542 llvm::IRBuilderBase &builder) {
3543 auto thisOp = cast<NVVM::Ex2Op>(op);
3544 llvm::Intrinsic::ID id = thisOp.getFtz()
3545 ? llvm::Intrinsic::nvvm_ex2_approx_ftz
3546 : llvm::Intrinsic::nvvm_ex2_approx;
3547 return {id, {mt.lookupValue(thisOp.getSrc())}};
3548}
3549
3551RsqrtOp::getIntrinsicIDAndArgs(Operation &op, LLVM::ModuleTranslation &mt,
3552 llvm::IRBuilderBase &builder) {
3553 auto thisOp = cast<NVVM::RsqrtOp>(op);
3554 Type t = thisOp.getRes().getType();
3555 bool isFtz = thisOp.getFtz();
3556
3557 llvm::Intrinsic::ID id = [&] {
3558 if (t.isF32()) {
3559 return isFtz ? llvm::Intrinsic::nvvm_rsqrt_approx_ftz_f
3560 : llvm::Intrinsic::nvvm_rsqrt_approx_f;
3561 }
3562 // f64
3563 return isFtz ? llvm::Intrinsic::nvvm_rsqrt_approx_ftz_d
3564 : llvm::Intrinsic::nvvm_rsqrt_approx_d;
3565 }();
3566
3567 return {id, {mt.lookupValue(thisOp.getSrc())}};
3568}
3569
3571SqrtOp::getIntrinsicIDAndArgs(Operation &op, LLVM::ModuleTranslation &mt,
3572 llvm::IRBuilderBase &builder) {
3573 auto thisOp = cast<NVVM::SqrtOp>(op);
3574 Type t = thisOp.getRes().getType();
3575 NVVM::FPRoundingMode rndMode = thisOp.getRnd();
3576 bool isFtz = thisOp.getFtz();
3577
3578 // RM is one of RN/RM/RP/RZ (verifier rejects NONE).
3579 // Subtracting 1 maps RN=1..RZ=4 to 0..3.
3580 unsigned rndIndex = static_cast<unsigned>(rndMode) - 1;
3581
3582 static constexpr llvm::Intrinsic::ID f32IDs[] = {
3583 llvm::Intrinsic::nvvm_sqrt_rn_f,
3584 llvm::Intrinsic::nvvm_sqrt_rm_f,
3585 llvm::Intrinsic::nvvm_sqrt_rp_f,
3586 llvm::Intrinsic::nvvm_sqrt_rz_f,
3587 };
3588 static constexpr llvm::Intrinsic::ID f32FTZIDs[] = {
3589 llvm::Intrinsic::nvvm_sqrt_rn_ftz_f,
3590 llvm::Intrinsic::nvvm_sqrt_rm_ftz_f,
3591 llvm::Intrinsic::nvvm_sqrt_rp_ftz_f,
3592 llvm::Intrinsic::nvvm_sqrt_rz_ftz_f,
3593 };
3594 static constexpr llvm::Intrinsic::ID f64IDs[] = {
3595 llvm::Intrinsic::nvvm_sqrt_rn_d,
3596 llvm::Intrinsic::nvvm_sqrt_rm_d,
3597 llvm::Intrinsic::nvvm_sqrt_rp_d,
3598 llvm::Intrinsic::nvvm_sqrt_rz_d,
3599 };
3600
3601 llvm::Intrinsic::ID id =
3602 t.isF32() ? (isFtz ? f32FTZIDs[rndIndex] : f32IDs[rndIndex])
3603 : f64IDs[rndIndex];
3604
3605 return {id, {mt.lookupValue(thisOp.getSrc())}};
3606}
3607
3609SqrtApproxOp::getIntrinsicIDAndArgs(Operation &op, LLVM::ModuleTranslation &mt,
3610 llvm::IRBuilderBase &builder) {
3611 auto thisOp = cast<NVVM::SqrtApproxOp>(op);
3612 llvm::Intrinsic::ID id = thisOp.getFtz()
3613 ? llvm::Intrinsic::nvvm_sqrt_approx_ftz_f
3614 : llvm::Intrinsic::nvvm_sqrt_approx_f;
3615 return {id, {mt.lookupValue(thisOp.getSrc())}};
3616}
3617
3619DivFOp::getIntrinsicIDAndArgs(Operation &op, LLVM::ModuleTranslation &mt,
3620 llvm::IRBuilderBase &builder) {
3621 auto thisOp = cast<NVVM::DivFOp>(op);
3622 bool isFtz = thisOp.getFtz();
3623
3624 llvm::Intrinsic::ID id;
3625
3626 if (thisOp.getApprox()) {
3627 id = isFtz ? llvm::Intrinsic::nvvm_div_approx_ftz_f
3628 : llvm::Intrinsic::nvvm_div_approx_f;
3629 } else if (thisOp.getFull()) {
3630 // Intrinsic Naming quirk: int_nvvm_div_full has no `_f` suffix (unlike
3631 // approx).
3632 id = isFtz ? llvm::Intrinsic::nvvm_div_full_ftz
3633 : llvm::Intrinsic::nvvm_div_full;
3634 } else {
3635 // Rounded form — three 4-entry tables indexed by (rndMode - 1).
3636 unsigned rndIndex = static_cast<unsigned>(thisOp.getRnd()) - 1;
3637
3638 static constexpr llvm::Intrinsic::ID f32IDs[] = {
3639 llvm::Intrinsic::nvvm_div_rn_f,
3640 llvm::Intrinsic::nvvm_div_rm_f,
3641 llvm::Intrinsic::nvvm_div_rp_f,
3642 llvm::Intrinsic::nvvm_div_rz_f,
3643 };
3644 static constexpr llvm::Intrinsic::ID f32FTZIDs[] = {
3645 llvm::Intrinsic::nvvm_div_rn_ftz_f,
3646 llvm::Intrinsic::nvvm_div_rm_ftz_f,
3647 llvm::Intrinsic::nvvm_div_rp_ftz_f,
3648 llvm::Intrinsic::nvvm_div_rz_ftz_f,
3649 };
3650 static constexpr llvm::Intrinsic::ID f64IDs[] = {
3651 llvm::Intrinsic::nvvm_div_rn_d,
3652 llvm::Intrinsic::nvvm_div_rm_d,
3653 llvm::Intrinsic::nvvm_div_rp_d,
3654 llvm::Intrinsic::nvvm_div_rz_d,
3655 };
3656 Type t = thisOp.getRes().getType();
3657 id = t.isF32() ? (isFtz ? f32FTZIDs[rndIndex] : f32IDs[rndIndex])
3658 : f64IDs[rndIndex];
3659 }
3660
3661 return {id,
3662 {mt.lookupValue(thisOp.getLhs()), mt.lookupValue(thisOp.getRhs())}};
3663}
3664
3666PMEventOp::getIntrinsicIDAndArgs(Operation &op, LLVM::ModuleTranslation &mt,
3667 llvm::IRBuilderBase &builder) {
3668 auto thisOp = cast<NVVM::PMEventOp>(op);
3669 llvm::Type *i16Ty = llvm::Type::getInt16Ty(mt.getLLVMContext());
3670
3671 // With event-id, mask is generated as (1 << event-id)
3672 llvm::Value *maskVal;
3673 if (auto eventAttr = thisOp.getEventIdAttr()) {
3674 uint16_t mask = static_cast<uint16_t>(1u << eventAttr.getInt());
3675 maskVal = llvm::ConstantInt::get(i16Ty, mask);
3676 } else {
3677 maskVal =
3678 llvm::ConstantInt::get(i16Ty, thisOp.getMaskedEventIdAttr().getValue());
3679 }
3680
3681 return {llvm::Intrinsic::nvvm_pm_event_mask, {maskVal}};
3682}
3683
3684mlir::NVVM::IDArgPair MBarrierInitOp::getIntrinsicIDAndArgs(
3685 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
3686 auto thisOp = cast<NVVM::MBarrierInitOp>(op);
3687 bool isShared = isPtrInSharedCTASpace(thisOp.getAddr());
3688 llvm::Intrinsic::ID id = isShared ? llvm::Intrinsic::nvvm_mbarrier_init_shared
3689 : llvm::Intrinsic::nvvm_mbarrier_init;
3690
3691 // Fill the Intrinsic Args
3693 args.push_back(mt.lookupValue(thisOp.getAddr()));
3694 args.push_back(mt.lookupValue(thisOp.getCount()));
3695
3696 return {id, std::move(args)};
3697}
3698
3699mlir::NVVM::IDArgPair MBarrierInvalOp::getIntrinsicIDAndArgs(
3700 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
3701 auto thisOp = cast<NVVM::MBarrierInvalOp>(op);
3702 bool isShared = isPtrInSharedCTASpace(thisOp.getAddr());
3703 llvm::Intrinsic::ID id = isShared
3704 ? llvm::Intrinsic::nvvm_mbarrier_inval_shared
3705 : llvm::Intrinsic::nvvm_mbarrier_inval;
3706
3707 return {id, {mt.lookupValue(thisOp.getAddr())}};
3708}
3709
3710mlir::NVVM::IDArgPair MBarrierExpectTxOp::getIntrinsicIDAndArgs(
3711 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
3712 auto thisOp = cast<NVVM::MBarrierExpectTxOp>(op);
3713
3714 bool isClusterSpace = isPtrInSharedClusterSpace(thisOp.getAddr());
3715 bool isClusterScope = thisOp.getScope() == NVVM::MemScopeKind::CLUSTER;
3716 // bit-0: Space
3717 // bit-1: Scope
3718 size_t index = ((isClusterScope ? 1 : 0) << 1) | (isClusterSpace ? 1 : 0);
3719
3720 static constexpr llvm::Intrinsic::ID IDs[] = {
3721 llvm::Intrinsic::nvvm_mbarrier_expect_tx_scope_cta_space_cta,
3722 llvm::Intrinsic::nvvm_mbarrier_expect_tx_scope_cta_space_cluster,
3723 llvm::Intrinsic::nvvm_mbarrier_expect_tx_scope_cluster_space_cta,
3724 llvm::Intrinsic::nvvm_mbarrier_expect_tx_scope_cluster_space_cluster};
3725
3726 // Fill the Intrinsic Args
3728 args.push_back(mt.lookupValue(thisOp.getAddr()));
3729 args.push_back(mt.lookupValue(thisOp.getTxcount()));
3730
3731 return {IDs[index], std::move(args)};
3732}
3733
3734mlir::NVVM::IDArgPair MBarrierCompleteTxOp::getIntrinsicIDAndArgs(
3735 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
3736 auto thisOp = cast<NVVM::MBarrierCompleteTxOp>(op);
3737
3738 bool isClusterSpace = isPtrInSharedClusterSpace(thisOp.getAddr());
3739 bool isClusterScope = thisOp.getScope() == NVVM::MemScopeKind::CLUSTER;
3740 // bit-0: Space
3741 // bit-1: Scope
3742 size_t index = ((isClusterScope ? 1 : 0) << 1) | (isClusterSpace ? 1 : 0);
3743
3744 static constexpr llvm::Intrinsic::ID IDs[] = {
3745 llvm::Intrinsic::nvvm_mbarrier_complete_tx_scope_cta_space_cta,
3746 llvm::Intrinsic::nvvm_mbarrier_complete_tx_scope_cta_space_cluster,
3747 llvm::Intrinsic::nvvm_mbarrier_complete_tx_scope_cluster_space_cta,
3748 llvm::Intrinsic::nvvm_mbarrier_complete_tx_scope_cluster_space_cluster};
3749
3750 // Fill the Intrinsic Args
3752 args.push_back(mt.lookupValue(thisOp.getAddr()));
3753 args.push_back(mt.lookupValue(thisOp.getTxcount()));
3754
3755 return {IDs[index], std::move(args)};
3756}
3757
3758mlir::NVVM::IDArgPair MBarrierArriveOp::getIntrinsicIDAndArgs(
3759 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
3760 auto thisOp = cast<NVVM::MBarrierArriveOp>(op);
3761
3762 bool isClusterSpace = isPtrInSharedClusterSpace(thisOp.getAddr());
3763 bool isClusterScope = thisOp.getScope() == NVVM::MemScopeKind::CLUSTER;
3764 // bit-0: Space
3765 // bit-1: Scope
3766 size_t index = ((isClusterScope ? 1 : 0) << 1) | (isClusterSpace ? 1 : 0);
3767
3768 static constexpr llvm::Intrinsic::ID IDs[] = {
3769 llvm::Intrinsic::nvvm_mbarrier_arrive_scope_cta_space_cta,
3770 llvm::Intrinsic::nvvm_mbarrier_arrive_scope_cta_space_cluster,
3771 llvm::Intrinsic::nvvm_mbarrier_arrive_scope_cluster_space_cta,
3772 llvm::Intrinsic::nvvm_mbarrier_arrive_scope_cluster_space_cluster};
3773 static constexpr llvm::Intrinsic::ID relaxedIDs[] = {
3774 llvm::Intrinsic::nvvm_mbarrier_arrive_relaxed_scope_cta_space_cta,
3775 llvm::Intrinsic::nvvm_mbarrier_arrive_relaxed_scope_cta_space_cluster,
3776 llvm::Intrinsic::nvvm_mbarrier_arrive_relaxed_scope_cluster_space_cta,
3777 llvm::Intrinsic::
3778 nvvm_mbarrier_arrive_relaxed_scope_cluster_space_cluster};
3779 auto id = thisOp.getRelaxed() ? relaxedIDs[index] : IDs[index];
3780
3781 // Tidy-up the Intrinsic Args
3782 bool needCast = isPtrInGenericSpace(thisOp.getAddr());
3783 llvm::Value *mbar = mt.lookupValue(thisOp.getAddr());
3784 if (needCast)
3785 mbar = castPtrToAddrSpace(builder, mbar, NVVMMemorySpace::Shared);
3786
3787 // We have the most basic mbarrier.arrive supported on sm_80.
3788 // It supports: Space=cta, scope=cta, No relaxed, No explicit count.
3789 // So, only for this combination use the legacy intrinsic.
3790 bool hasCount = static_cast<bool>(thisOp.getCount());
3791 if (!hasCount &&
3792 (id == llvm::Intrinsic::nvvm_mbarrier_arrive_scope_cta_space_cta))
3793 return {llvm::Intrinsic::nvvm_mbarrier_arrive_shared, {mbar}};
3794
3795 // When count is not explicitly specified, the default is 1.
3796 llvm::LLVMContext &ctx = mt.getLLVMContext();
3797 llvm::Value *count =
3798 hasCount ? mt.lookupValue(thisOp.getCount())
3799 : llvm::ConstantInt::get(llvm::Type::getInt32Ty(ctx), 1);
3800 return {id, {mbar, count}};
3801}
3802
3803mlir::NVVM::IDArgPair MBarrierArriveDropOp::getIntrinsicIDAndArgs(
3804 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
3805 auto thisOp = cast<NVVM::MBarrierArriveDropOp>(op);
3806
3807 bool isClusterSpace = isPtrInSharedClusterSpace(thisOp.getAddr());
3808 bool isClusterScope = thisOp.getScope() == NVVM::MemScopeKind::CLUSTER;
3809 // bit-0: Space
3810 // bit-1: Scope
3811 size_t index = ((isClusterScope ? 1 : 0) << 1) | (isClusterSpace ? 1 : 0);
3812
3813 static constexpr llvm::Intrinsic::ID IDs[] = {
3814 llvm::Intrinsic::nvvm_mbarrier_arrive_drop_scope_cta_space_cta,
3815 llvm::Intrinsic::nvvm_mbarrier_arrive_drop_scope_cta_space_cluster,
3816 llvm::Intrinsic::nvvm_mbarrier_arrive_drop_scope_cluster_space_cta,
3817 llvm::Intrinsic::nvvm_mbarrier_arrive_drop_scope_cluster_space_cluster};
3818 static constexpr llvm::Intrinsic::ID relaxedIDs[] = {
3819 llvm::Intrinsic::nvvm_mbarrier_arrive_drop_relaxed_scope_cta_space_cta,
3820 llvm::Intrinsic::
3821 nvvm_mbarrier_arrive_drop_relaxed_scope_cta_space_cluster,
3822 llvm::Intrinsic::
3823 nvvm_mbarrier_arrive_drop_relaxed_scope_cluster_space_cta,
3824 llvm::Intrinsic::
3825 nvvm_mbarrier_arrive_drop_relaxed_scope_cluster_space_cluster};
3826 auto id = thisOp.getRelaxed() ? relaxedIDs[index] : IDs[index];
3827
3828 // Tidy-up the Intrinsic Args
3829 bool needCast = isPtrInGenericSpace(thisOp.getAddr());
3830 llvm::Value *mbar = mt.lookupValue(thisOp.getAddr());
3831 if (needCast)
3832 mbar = castPtrToAddrSpace(builder, mbar, NVVMMemorySpace::Shared);
3833
3834 // When count is not explicitly specified, the default is 1.
3835 llvm::LLVMContext &ctx = mt.getLLVMContext();
3836 bool hasCount = static_cast<bool>(thisOp.getCount());
3837 llvm::Value *count =
3838 hasCount ? mt.lookupValue(thisOp.getCount())
3839 : llvm::ConstantInt::get(llvm::Type::getInt32Ty(ctx), 1);
3840
3841 return {id, {mbar, count}};
3842}
3843
3844bool MBarrierArriveExpectTxOp::getAsmValues(
3845 RewriterBase &rewriter,
3846 llvm::SmallVectorImpl<std::pair<mlir::Value, mlir::NVVM::PTXRegisterMod>>
3847 &asmValues) {
3848 // Add all the operands but not the attrs to the asmValues list.
3849 // The attrs here are used to generate the right variants for
3850 // intrinsics-lowering. So, we ignore them while generating inline-PTX.
3851 for (auto val : getOperands())
3852 asmValues.push_back({val, mlir::NVVM::PTXRegisterMod::Read});
3853
3854 return false;
3855}
3856
3857mlir::NVVM::IDArgPair MBarrierArriveExpectTxOp::getIntrinsicIDAndArgs(
3858 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
3859 auto thisOp = cast<NVVM::MBarrierArriveExpectTxOp>(op);
3860
3861 bool isClusterSpace = isPtrInSharedClusterSpace(thisOp.getAddr());
3862 bool isClusterScope = thisOp.getScope() == NVVM::MemScopeKind::CLUSTER;
3863 // bit-0: Space
3864 // bit-1: Scope
3865 size_t index = ((isClusterScope ? 1 : 0) << 1) | (isClusterSpace ? 1 : 0);
3866
3867 // clang-format off
3868 static constexpr llvm::Intrinsic::ID IDs[] = {
3869 llvm::Intrinsic::nvvm_mbarrier_arrive_expect_tx_scope_cta_space_cta,
3870 llvm::Intrinsic::nvvm_mbarrier_arrive_expect_tx_scope_cta_space_cluster,
3871 llvm::Intrinsic::nvvm_mbarrier_arrive_expect_tx_scope_cluster_space_cta,
3872 llvm::Intrinsic::nvvm_mbarrier_arrive_expect_tx_scope_cluster_space_cluster};
3873 static constexpr llvm::Intrinsic::ID relaxedIDs[] = {
3874 llvm::Intrinsic::nvvm_mbarrier_arrive_expect_tx_relaxed_scope_cta_space_cta,
3875 llvm::Intrinsic::nvvm_mbarrier_arrive_expect_tx_relaxed_scope_cta_space_cluster,
3876 llvm::Intrinsic::nvvm_mbarrier_arrive_expect_tx_relaxed_scope_cluster_space_cta,
3877 llvm::Intrinsic::nvvm_mbarrier_arrive_expect_tx_relaxed_scope_cluster_space_cluster};
3878 // clang-format on
3879 auto id = thisOp.getRelaxed() ? relaxedIDs[index] : IDs[index];
3880
3881 // Tidy-up the Intrinsic Args
3882 llvm::Value *txcount = mt.lookupValue(thisOp.getTxcount());
3883 llvm::Value *mbar = mt.lookupValue(thisOp.getAddr());
3884 bool needCast = isPtrInGenericSpace(thisOp.getAddr());
3885 if (needCast)
3886 mbar = castPtrToAddrSpace(builder, mbar, NVVMMemorySpace::Shared);
3887
3888 return {id, {mbar, txcount}};
3889}
3890
3891mlir::NVVM::IDArgPair MBarrierArriveDropExpectTxOp::getIntrinsicIDAndArgs(
3892 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
3893 auto thisOp = cast<NVVM::MBarrierArriveDropExpectTxOp>(op);
3894
3895 bool isClusterSpace = isPtrInSharedClusterSpace(thisOp.getAddr());
3896 bool isClusterScope = thisOp.getScope() == NVVM::MemScopeKind::CLUSTER;
3897 // bit-0: Space
3898 // bit-1: Scope
3899 size_t index = ((isClusterScope ? 1 : 0) << 1) | (isClusterSpace ? 1 : 0);
3900
3901 // clang-format off
3902 static constexpr llvm::Intrinsic::ID IDs[] = {
3903 llvm::Intrinsic::nvvm_mbarrier_arrive_drop_expect_tx_scope_cta_space_cta,
3904 llvm::Intrinsic::nvvm_mbarrier_arrive_drop_expect_tx_scope_cta_space_cluster,
3905 llvm::Intrinsic::nvvm_mbarrier_arrive_drop_expect_tx_scope_cluster_space_cta,
3906 llvm::Intrinsic::nvvm_mbarrier_arrive_drop_expect_tx_scope_cluster_space_cluster};
3907 static constexpr llvm::Intrinsic::ID relaxedIDs[] = {
3908 llvm::Intrinsic::nvvm_mbarrier_arrive_drop_expect_tx_relaxed_scope_cta_space_cta,
3909 llvm::Intrinsic::nvvm_mbarrier_arrive_drop_expect_tx_relaxed_scope_cta_space_cluster,
3910 llvm::Intrinsic::nvvm_mbarrier_arrive_drop_expect_tx_relaxed_scope_cluster_space_cta,
3911 llvm::Intrinsic::nvvm_mbarrier_arrive_drop_expect_tx_relaxed_scope_cluster_space_cluster};
3912 // clang-format on
3913 auto id = thisOp.getRelaxed() ? relaxedIDs[index] : IDs[index];
3914
3915 // Tidy-up the Intrinsic Args
3916 llvm::Value *txcount = mt.lookupValue(thisOp.getTxcount());
3917 llvm::Value *mbar = mt.lookupValue(thisOp.getAddr());
3918 bool needCast = isPtrInGenericSpace(thisOp.getAddr());
3919 if (needCast)
3920 mbar = castPtrToAddrSpace(builder, mbar, NVVMMemorySpace::Shared);
3921
3922 return {id, {mbar, txcount}};
3923}
3924
3925mlir::NVVM::IDArgPair MBarrierArriveNocompleteOp::getIntrinsicIDAndArgs(
3926 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
3927 auto thisOp = cast<NVVM::MBarrierArriveNocompleteOp>(op);
3928 bool isShared = isPtrInSharedCTASpace(thisOp.getAddr());
3929 llvm::Intrinsic::ID id =
3930 isShared ? llvm::Intrinsic::nvvm_mbarrier_arrive_noComplete_shared
3931 : llvm::Intrinsic::nvvm_mbarrier_arrive_noComplete;
3932 // Fill the Intrinsic Args
3934 args.push_back(mt.lookupValue(thisOp.getAddr()));
3935 args.push_back(mt.lookupValue(thisOp.getCount()));
3936
3937 return {id, std::move(args)};
3938}
3939
3940mlir::NVVM::IDArgPair MBarrierArriveDropNocompleteOp::getIntrinsicIDAndArgs(
3941 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
3942 auto thisOp = cast<NVVM::MBarrierArriveDropNocompleteOp>(op);
3943 bool isShared = isPtrInSharedCTASpace(thisOp.getAddr());
3944 llvm::Intrinsic::ID id =
3945 isShared ? llvm::Intrinsic::nvvm_mbarrier_arrive_drop_noComplete_shared
3946 : llvm::Intrinsic::nvvm_mbarrier_arrive_drop_noComplete;
3947 // Fill the Intrinsic Args
3949 args.push_back(mt.lookupValue(thisOp.getAddr()));
3950 args.push_back(mt.lookupValue(thisOp.getCount()));
3951
3952 return {id, std::move(args)};
3953}
3954
3955mlir::NVVM::IDArgPair MBarrierTestWaitOp::getIntrinsicIDAndArgs(
3956 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
3957 auto thisOp = cast<NVVM::MBarrierTestWaitOp>(op);
3958 bool isPhaseParity = thisOp.getStateOrPhase().getType().isInteger(32);
3959 bool isClusterScope = thisOp.getScope() == NVVM::MemScopeKind::CLUSTER;
3960 // bit-0: isPhaseParity
3961 // bit-1: Scope
3962 size_t index = ((isClusterScope ? 1 : 0) << 1) | (isPhaseParity ? 1 : 0);
3963
3964 // clang-format off
3965 static constexpr llvm::Intrinsic::ID IDs[] = {
3966 llvm::Intrinsic::nvvm_mbarrier_test_wait_scope_cta_space_cta,
3967 llvm::Intrinsic::nvvm_mbarrier_test_wait_parity_scope_cta_space_cta,
3968 llvm::Intrinsic::nvvm_mbarrier_test_wait_scope_cluster_space_cta,
3969 llvm::Intrinsic::nvvm_mbarrier_test_wait_parity_scope_cluster_space_cta};
3970 static constexpr llvm::Intrinsic::ID relaxedIDs[] = {
3971 llvm::Intrinsic::nvvm_mbarrier_test_wait_relaxed_scope_cta_space_cta,
3972 llvm::Intrinsic::nvvm_mbarrier_test_wait_parity_relaxed_scope_cta_space_cta,
3973 llvm::Intrinsic::nvvm_mbarrier_test_wait_relaxed_scope_cluster_space_cta,
3974 llvm::Intrinsic::nvvm_mbarrier_test_wait_parity_relaxed_scope_cluster_space_cta};
3975 // clang-format on
3976 auto id = thisOp.getRelaxed() ? relaxedIDs[index] : IDs[index];
3977
3978 // Tidy-up the Intrinsic Args
3979 llvm::Value *mbar = mt.lookupValue(thisOp.getAddr());
3980 llvm::Value *input = mt.lookupValue(thisOp.getStateOrPhase());
3981 bool needCast = isPtrInGenericSpace(thisOp.getAddr());
3982 if (needCast)
3983 mbar = castPtrToAddrSpace(builder, mbar, NVVMMemorySpace::Shared);
3984
3985 return {id, {mbar, input}};
3986}
3987
3988mlir::NVVM::IDArgPair MBarrierTryWaitOp::getIntrinsicIDAndArgs(
3989 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
3990 auto thisOp = cast<NVVM::MBarrierTryWaitOp>(op);
3991 bool isPhaseParity = thisOp.getStateOrPhase().getType().isInteger(32);
3992 bool isClusterScope = thisOp.getScope() == NVVM::MemScopeKind::CLUSTER;
3993 bool hasTicks = static_cast<bool>(thisOp.getTicks());
3994 // bit-0: isPhaseParity
3995 // bit-1: Scope
3996 // bit-2: hasTicks
3997 size_t index = ((hasTicks ? 1 : 0) << 2) | ((isClusterScope ? 1 : 0) << 1) |
3998 (isPhaseParity ? 1 : 0);
3999
4000 // clang-format off
4001 static constexpr llvm::Intrinsic::ID IDs[] = {
4002 llvm::Intrinsic::nvvm_mbarrier_try_wait_scope_cta_space_cta,
4003 llvm::Intrinsic::nvvm_mbarrier_try_wait_parity_scope_cta_space_cta,
4004 llvm::Intrinsic::nvvm_mbarrier_try_wait_scope_cluster_space_cta,
4005 llvm::Intrinsic::nvvm_mbarrier_try_wait_parity_scope_cluster_space_cta,
4006 llvm::Intrinsic::nvvm_mbarrier_try_wait_tl_scope_cta_space_cta,
4007 llvm::Intrinsic::nvvm_mbarrier_try_wait_parity_tl_scope_cta_space_cta,
4008 llvm::Intrinsic::nvvm_mbarrier_try_wait_tl_scope_cluster_space_cta,
4009 llvm::Intrinsic::nvvm_mbarrier_try_wait_parity_tl_scope_cluster_space_cta};
4010 static constexpr llvm::Intrinsic::ID relaxedIDs[] = {
4011 llvm::Intrinsic::nvvm_mbarrier_try_wait_relaxed_scope_cta_space_cta,
4012 llvm::Intrinsic::nvvm_mbarrier_try_wait_parity_relaxed_scope_cta_space_cta,
4013 llvm::Intrinsic::nvvm_mbarrier_try_wait_relaxed_scope_cluster_space_cta,
4014 llvm::Intrinsic::nvvm_mbarrier_try_wait_parity_relaxed_scope_cluster_space_cta,
4015 llvm::Intrinsic::nvvm_mbarrier_try_wait_tl_relaxed_scope_cta_space_cta,
4016 llvm::Intrinsic::nvvm_mbarrier_try_wait_parity_tl_relaxed_scope_cta_space_cta,
4017 llvm::Intrinsic::nvvm_mbarrier_try_wait_tl_relaxed_scope_cluster_space_cta,
4018 llvm::Intrinsic::nvvm_mbarrier_try_wait_parity_tl_relaxed_scope_cluster_space_cta};
4019 // clang-format on
4020 auto id = thisOp.getRelaxed() ? relaxedIDs[index] : IDs[index];
4021
4022 // Tidy-up the mbarrier pointer
4023 llvm::Value *mbar = mt.lookupValue(thisOp.getAddr());
4024 bool needCast = isPtrInGenericSpace(thisOp.getAddr());
4025 if (needCast)
4026 mbar = castPtrToAddrSpace(builder, mbar, NVVMMemorySpace::Shared);
4027
4028 // Fill the Intrinsic Args
4030 args.push_back(mbar);
4031 args.push_back(mt.lookupValue(thisOp.getStateOrPhase()));
4032 if (hasTicks)
4033 args.push_back(mt.lookupValue(thisOp.getTicks()));
4034
4035 return {id, std::move(args)};
4036}
4037
4038mlir::NVVM::IDArgPair CpAsyncMBarrierArriveOp::getIntrinsicIDAndArgs(
4039 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
4040 auto thisOp = cast<NVVM::CpAsyncMBarrierArriveOp>(op);
4041 bool isShared = isPtrInSharedCTASpace(thisOp.getAddr());
4042
4043 llvm::Intrinsic::ID id;
4044 if (thisOp.getNoinc()) {
4045 id = isShared ? llvm::Intrinsic::nvvm_cp_async_mbarrier_arrive_noinc_shared
4046 : llvm::Intrinsic::nvvm_cp_async_mbarrier_arrive_noinc;
4047 } else {
4048 id = isShared ? llvm::Intrinsic::nvvm_cp_async_mbarrier_arrive_shared
4049 : llvm::Intrinsic::nvvm_cp_async_mbarrier_arrive;
4050 }
4051
4052 return {id, {mt.lookupValue(thisOp.getAddr())}};
4053}
4054
4056MovMatrixOp::getIntrinsicIDAndArgs(Operation &op, LLVM::ModuleTranslation &mt,
4057 llvm::IRBuilderBase &builder) {
4058 auto thisOp = cast<NVVM::MovMatrixOp>(op);
4059 return {llvm::Intrinsic::nvvm_movmatrix_sync_aligned_m8n8_trans_b16,
4060 {mt.lookupValue(thisOp.getSrc())}};
4061}
4062
4063#define CP_ASYNC_ID_IMPL(mod, size, suffix) \
4064 llvm::Intrinsic::nvvm_cp_async_##mod##_shared_global_##size##suffix
4065
4066#define GET_CP_ASYNC_ID(mod, size, has_cpsize) \
4067 has_cpsize ? CP_ASYNC_ID_IMPL(mod, size, _s) : CP_ASYNC_ID_IMPL(mod, size, )
4068
4069llvm::Intrinsic::ID
4070CpAsyncOp::getIntrinsicIDAndArgs(Operation &op, LLVM::ModuleTranslation &mt,
4072 llvm::Intrinsic::ID id;
4073
4074 auto cpAsyncOp = cast<NVVM::CpAsyncOp>(op);
4075 bool hasCpSize = static_cast<bool>(cpAsyncOp.getCpSize());
4076 switch (cpAsyncOp.getSize()) {
4077 case 4:
4078 id = GET_CP_ASYNC_ID(ca, 4, hasCpSize);
4079 break;
4080 case 8:
4081 id = GET_CP_ASYNC_ID(ca, 8, hasCpSize);
4082 break;
4083 case 16:
4084 id = (cpAsyncOp.getModifier() == NVVM::LoadCacheModifierKind::CG)
4085 ? GET_CP_ASYNC_ID(cg, 16, hasCpSize)
4086 : GET_CP_ASYNC_ID(ca, 16, hasCpSize);
4087 break;
4088 default:
4089 llvm_unreachable("Invalid copy size in CpAsyncOp.");
4090 }
4091
4092 // Fill the Intrinsic Args
4093 args.push_back(mt.lookupValue(cpAsyncOp.getDst()));
4094 args.push_back(mt.lookupValue(cpAsyncOp.getSrc()));
4095 if (hasCpSize)
4096 args.push_back(mt.lookupValue(cpAsyncOp.getCpSize()));
4097
4098 return id;
4099}
4100
4101mlir::NVVM::IDArgPair CpAsyncBulkPrefetchOp::getIntrinsicIDAndArgs(
4102 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
4103 auto thisOp = cast<NVVM::CpAsyncBulkPrefetchOp>(op);
4105 llvm::Intrinsic::ID id = llvm::Intrinsic::nvvm_cp_async_bulk_prefetch_L2;
4106
4107 // Fill the Intrinsic Args
4108 args.push_back(mt.lookupValue(thisOp.getSrcMem()));
4109 args.push_back(mt.lookupValue(thisOp.getSize()));
4110
4111 mlir::Value cacheHint = thisOp.getL2CacheHint();
4112 const bool hasCacheHint = static_cast<bool>(cacheHint);
4113 llvm::Value *i64Unused =
4114 llvm::ConstantInt::get(llvm::Type::getInt64Ty(mt.getLLVMContext()), 0);
4115 args.push_back(hasCacheHint ? mt.lookupValue(cacheHint) : i64Unused);
4116 args.push_back(builder.getInt1(hasCacheHint));
4117
4118 return {id, std::move(args)};
4119}
4120
4121mlir::NVVM::IDArgPair CpAsyncBulkGlobalToSharedClusterOp::getIntrinsicIDAndArgs(
4122 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
4123 auto thisOp = cast<NVVM::CpAsyncBulkGlobalToSharedClusterOp>(op);
4125
4126 // Fill the Intrinsic Args: dst, mbar, src, size.
4127 args.push_back(mt.lookupValue(thisOp.getDstMem()));
4128 args.push_back(mt.lookupValue(thisOp.getMbar()));
4129 args.push_back(mt.lookupValue(thisOp.getSrcMem()));
4130 args.push_back(mt.lookupValue(thisOp.getSize()));
4131
4132 // Multicast mask for shared::cluster only, if available.
4133 mlir::Value multicastMask = thisOp.getMulticastMask();
4134 const bool hasMulticastMask = static_cast<bool>(multicastMask);
4135 const bool isSharedCTA = isPtrInSharedCTASpace(thisOp.getDstMem());
4136 if (!isSharedCTA) {
4137 llvm::Value *i16Unused = llvm::ConstantInt::get(builder.getInt16Ty(), 0);
4138 args.push_back(hasMulticastMask ? mt.lookupValue(multicastMask)
4139 : i16Unused);
4140 }
4141
4142 // Cache hint, if available.
4143 mlir::Value cacheHint = thisOp.getL2CacheHint();
4144 const bool hasCacheHint = static_cast<bool>(cacheHint);
4145 llvm::Value *i64Unused = llvm::ConstantInt::get(builder.getInt64Ty(), 0);
4146 args.push_back(hasCacheHint ? mt.lookupValue(cacheHint) : i64Unused);
4147
4148 // Flag arguments for multicast and cachehint.
4149 if (!isSharedCTA)
4150 args.push_back(builder.getInt1(hasMulticastMask));
4151 args.push_back(builder.getInt1(hasCacheHint));
4152
4153 llvm::Intrinsic::ID id =
4154 isSharedCTA
4155 ? llvm::Intrinsic::nvvm_cp_async_bulk_global_to_shared_cta
4156 : llvm::Intrinsic::nvvm_cp_async_bulk_global_to_shared_cluster;
4157
4158 return {id, std::move(args)};
4159}
4160
4161mlir::NVVM::IDArgPair CpAsyncBulkSharedCTAToGlobalOp::getIntrinsicIDAndArgs(
4162 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
4163 auto thisOp = cast<NVVM::CpAsyncBulkSharedCTAToGlobalOp>(op);
4165 llvm::Intrinsic::ID id =
4166 llvm::Intrinsic::nvvm_cp_async_bulk_shared_cta_to_global;
4167
4168 // Fill the Intrinsic Args
4169 args.push_back(mt.lookupValue(thisOp.getDstMem()));
4170 args.push_back(mt.lookupValue(thisOp.getSrcMem()));
4171 args.push_back(mt.lookupValue(thisOp.getSize()));
4172
4173 mlir::Value cacheHint = thisOp.getL2CacheHint();
4174 const bool hasCacheHint = static_cast<bool>(cacheHint);
4175 llvm::Value *i64Unused =
4176 llvm::ConstantInt::get(llvm::Type::getInt64Ty(mt.getLLVMContext()), 0);
4177 args.push_back(hasCacheHint ? mt.lookupValue(cacheHint) : i64Unused);
4178 args.push_back(builder.getInt1(hasCacheHint));
4179
4180 // Choose the bytemask variant
4181 if (mlir::Value byteMask = thisOp.getByteMask()) {
4182 args.push_back(mt.lookupValue(byteMask));
4183 id = llvm::Intrinsic::nvvm_cp_async_bulk_shared_cta_to_global_bytemask;
4184 }
4185
4186 return {id, std::move(args)};
4187}
4188
4189bool CpAsyncBulkTensorGlobalToSharedClusterOp::getAsmValues(
4190 RewriterBase &rewriter,
4191 llvm::SmallVectorImpl<std::pair<mlir::Value, mlir::NVVM::PTXRegisterMod>>
4192 &asmValues) {
4193 // Add all the operands but not the attrs to the asmValues list.
4194 // The attrs here are used to generate the right variants for
4195 // intrinsics-lowering. So, we ignore them while generating inline-PTX.
4196 for (auto val : getOperands())
4197 asmValues.push_back({val, mlir::NVVM::PTXRegisterMod::Read});
4198
4199 return false;
4200}
4201
4203CpAsyncBulkTensorGlobalToSharedClusterOp::getIntrinsicIDAndArgs(
4204 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
4205 auto thisOp = cast<NVVM::CpAsyncBulkTensorGlobalToSharedClusterOp>(op);
4206 const bool isCTAOnly = thisOp.getIsCTAOnly();
4208
4209 // Fill the Intrinsic Args
4210 args.push_back(mt.lookupValue(thisOp.getDstMem()));
4211 args.push_back(mt.lookupValue(thisOp.getMbar()));
4212 args.push_back(mt.lookupValue(thisOp.getTmaDescriptor()));
4213
4214 // Coordinates and im2col-offsets
4215 for (mlir::Value v : thisOp.getCoordinates())
4216 args.push_back(mt.lookupValue(v));
4217 for (mlir::Value v : thisOp.getIm2colOffsets())
4218 args.push_back(mt.lookupValue(v));
4219
4220 // MulticastMask, if available
4221 mlir::Value mcMask = thisOp.getMulticastMask();
4222 const bool hasMC = static_cast<bool>(mcMask);
4223 llvm::Value *i16Zero =
4224 llvm::ConstantInt::get(llvm::Type::getInt16Ty(mt.getLLVMContext()), 0);
4225
4226 // CacheHint, if available
4227 mlir::Value cacheHint = thisOp.getL2CacheHint();
4228 const bool hasCacheHint = static_cast<bool>(cacheHint);
4229 llvm::Value *i64Zero =
4230 llvm::ConstantInt::get(llvm::Type::getInt64Ty(mt.getLLVMContext()), 0);
4231
4232 // Flag argument CTAGroup
4233 // CTA_1/2 is mapped to values 1 and 2 for the intrinsics.
4234 // Hence, the +1 to getGroup().
4235 const int32_t val =
4236 thisOp.getGroup() ? (static_cast<int32_t>(*thisOp.getGroup()) + 1) : 0;
4237 llvm::Value *cg =
4238 llvm::ConstantInt::get(llvm::Type::getInt32Ty(mt.getLLVMContext()), val);
4239
4240 if (!isCTAOnly) {
4241 // For shared::cluster, all the arguments that we build are applicable.
4242 args.push_back(hasMC ? mt.lookupValue(mcMask) : i16Zero);
4243 args.push_back(hasCacheHint ? mt.lookupValue(cacheHint) : i64Zero);
4244 args.push_back(builder.getInt1(hasMC));
4245 args.push_back(builder.getInt1(hasCacheHint));
4246 args.push_back(cg);
4247 } else {
4248 // For shared::cta, only cache-hint is applicable.
4249 args.push_back(hasCacheHint ? mt.lookupValue(cacheHint) : i64Zero);
4250 args.push_back(builder.getInt1(hasCacheHint));
4251 }
4252
4253 constexpr size_t numDims = 5; // 1D to 5D
4254 constexpr size_t numModes = 5; // Tile, Im2col, w, w_128, gather4
4255 using rowTy = std::array<llvm::Intrinsic::ID, numDims + 1>;
4256 using TableTy = std::array<rowTy, numModes>;
4257 static constexpr TableTy IDTable{
4258 {{notIntrinsic, llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_tile_1d,
4259 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_tile_2d,
4260 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_tile_3d,
4261 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_tile_4d,
4262 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_tile_5d},
4264 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_im2col_3d,
4265 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_im2col_4d,
4266 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_im2col_5d},
4268 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_im2col_w_3d,
4269 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_im2col_w_4d,
4270 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_im2col_w_5d},
4272 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_im2col_w_128_3d,
4273 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_im2col_w_128_4d,
4274 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_im2col_w_128_5d},
4276 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_tile_gather4_2d}}};
4277
4278 static constexpr TableTy IDTableCTA{
4279 {{notIntrinsic,
4280 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_cta_tile_1d,
4281 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_cta_tile_2d,
4282 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_cta_tile_3d,
4283 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_cta_tile_4d,
4284 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_cta_tile_5d},
4286 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_cta_im2col_3d,
4287 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_cta_im2col_4d,
4288 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_cta_im2col_5d},
4290 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_cta_im2col_w_3d,
4291 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_cta_im2col_w_4d,
4292 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_cta_im2col_w_5d},
4294 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_cta_im2col_w_128_3d,
4295 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_cta_im2col_w_128_4d,
4296 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_cta_im2col_w_128_5d},
4298 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_g2s_cta_tile_gather4_2d}}};
4299
4300 static_assert(
4301 (getMaxEnumValForTMALoadMode() == std::size(IDTable) - 1) &&
4302 (getMaxEnumValForTMALoadMode() == std::size(IDTableCTA) - 1),
4303 "TMALoadModes must match number of rows in IDTable and IDTableCTA");
4304 size_t mode = static_cast<size_t>(thisOp.getMode());
4305 size_t dim = thisOp.getCoordinates().size();
4306 auto id = isCTAOnly ? IDTableCTA[mode][dim] : IDTable[mode][dim];
4307 assert(id != notIntrinsic &&
4308 "Invalid intrinsic for CpAsyncBulkTensorGlobalToSharedClusterOp.");
4309
4310 return {id, std::move(args)};
4311}
4312
4313mlir::NVVM::IDArgPair CpAsyncBulkTensorPrefetchOp::getIntrinsicIDAndArgs(
4314 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
4315 auto thisOp = cast<NVVM::CpAsyncBulkTensorPrefetchOp>(op);
4317
4318 // Fill the Intrinsic Args
4319 args.push_back(mt.lookupValue(thisOp.getTmaDescriptor()));
4320
4321 for (auto v : thisOp.getCoordinates())
4322 args.push_back(mt.lookupValue(v));
4323 for (auto v : thisOp.getIm2colOffsets())
4324 args.push_back(mt.lookupValue(v));
4325
4326 mlir::Value cacheHint = thisOp.getL2CacheHint();
4327 const bool hasCacheHint = static_cast<bool>(cacheHint);
4328 llvm::Value *i64Unused =
4329 llvm::ConstantInt::get(llvm::Type::getInt64Ty(mt.getLLVMContext()), 0);
4330 args.push_back(hasCacheHint ? mt.lookupValue(cacheHint) : i64Unused);
4331 args.push_back(builder.getInt1(hasCacheHint));
4332
4333 const unsigned NI = llvm::Intrinsic::not_intrinsic;
4334 static constexpr llvm::Intrinsic::ID IDTable[][6] = {
4335 {NI, llvm::Intrinsic::nvvm_cp_async_bulk_tensor_prefetch_tile_1d,
4336 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_prefetch_tile_2d,
4337 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_prefetch_tile_3d,
4338 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_prefetch_tile_4d,
4339 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_prefetch_tile_5d},
4340 {NI, NI, NI,
4341 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_prefetch_im2col_3d,
4342 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_prefetch_im2col_4d,
4343 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_prefetch_im2col_5d},
4344 {NI, NI, NI,
4345 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_prefetch_im2col_w_3d,
4346 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_prefetch_im2col_w_4d,
4347 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_prefetch_im2col_w_5d},
4348 {NI, NI, NI,
4349 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_prefetch_im2col_w_128_3d,
4350 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_prefetch_im2col_w_128_4d,
4351 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_prefetch_im2col_w_128_5d},
4352 {NI, NI, NI, NI, NI,
4353 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_prefetch_tile_gather4_2d}};
4354
4355 static_assert(getMaxEnumValForTMALoadMode() == std::size(IDTable) - 1,
4356 "TMALoadModes must match number of rows in IDTable");
4357 size_t mode = static_cast<size_t>(thisOp.getMode());
4358 size_t dim = thisOp.getCoordinates().size();
4359 llvm::Intrinsic::ID id = IDTable[mode][dim];
4360 if (id == llvm::Intrinsic::not_intrinsic)
4361 llvm_unreachable("Invalid intrinsic for CpAsyncBulkTensorPrefetchOp.");
4362
4363 return {id, std::move(args)};
4364}
4365
4367CpAsyncBulkTensorSharedCTAToGlobalOp::getIntrinsicIDAndArgs(
4368 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
4369 auto thisOp = cast<NVVM::CpAsyncBulkTensorSharedCTAToGlobalOp>(op);
4371
4372 // Fill the Intrinsic Args
4373 args.push_back(mt.lookupValue(thisOp.getSrcMem()));
4374 args.push_back(mt.lookupValue(thisOp.getTmaDescriptor()));
4375
4376 for (auto v : thisOp.getCoordinates())
4377 args.push_back(mt.lookupValue(v));
4378
4379 mlir::Value cacheHint = thisOp.getL2CacheHint();
4380 const bool hasCacheHint = static_cast<bool>(cacheHint);
4381 llvm::Value *i64Unused =
4382 llvm::ConstantInt::get(llvm::Type::getInt64Ty(mt.getLLVMContext()), 0);
4383 args.push_back(hasCacheHint ? mt.lookupValue(cacheHint) : i64Unused);
4384 args.push_back(builder.getInt1(hasCacheHint));
4385
4386 const unsigned NI = llvm::Intrinsic::not_intrinsic;
4387 static constexpr llvm::Intrinsic::ID IDTable[][6] = {
4388 {NI, llvm::Intrinsic::nvvm_cp_async_bulk_tensor_s2g_tile_1d,
4389 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_s2g_tile_2d,
4390 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_s2g_tile_3d,
4391 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_s2g_tile_4d,
4392 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_s2g_tile_5d},
4393 {NI, NI, NI, llvm::Intrinsic::nvvm_cp_async_bulk_tensor_s2g_im2col_3d,
4394 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_s2g_im2col_4d,
4395 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_s2g_im2col_5d},
4396 {NI, NI, NI, NI, NI,
4397 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_s2g_tile_scatter4_2d}};
4398
4399 static_assert(getMaxEnumValForTMAStoreMode() == std::size(IDTable) - 1,
4400 "TMAStoreModes must match number of rows in IDTable");
4401 size_t mode = static_cast<size_t>(thisOp.getMode());
4402 size_t dim = thisOp.getCoordinates().size();
4403 llvm::Intrinsic::ID id = IDTable[mode][dim];
4404 if (id == llvm::Intrinsic::not_intrinsic)
4405 llvm_unreachable(
4406 "Invalid intrinsic for CpAsyncBulkTensorSharedCTAToGlobalOp.");
4407
4408 return {id, std::move(args)};
4409}
4410
4411NVVM::IDArgPair CpAsyncBulkTensorReduceOp::getIntrinsicIDAndArgs(
4412 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
4413 auto thisOp = cast<NVVM::CpAsyncBulkTensorReduceOp>(op);
4414 llvm::LLVMContext &ctx = mt.getLLVMContext();
4415
4417
4418 // Arguments to the intrinsic:
4419 // shared_mem_ptr, tmaDesc, tensorDims
4420 // cache_hint(if applicable) and flag(boolean)
4421 args.push_back(mt.lookupValue(thisOp.getSrcMem()));
4422 args.push_back(mt.lookupValue(thisOp.getTmaDescriptor()));
4423
4424 for (Value v : thisOp.getCoordinates())
4425 args.push_back(mt.lookupValue(v));
4426
4427 mlir::Value cacheHint = thisOp.getL2CacheHint();
4428 const bool hasCacheHint = static_cast<bool>(cacheHint);
4429 llvm::Value *i64ZeroValue =
4430 llvm::ConstantInt::get(llvm::Type::getInt64Ty(ctx), 0);
4431 args.push_back(hasCacheHint ? mt.lookupValue(cacheHint) : i64ZeroValue);
4432 args.push_back(builder.getInt1(hasCacheHint));
4433
4434 const llvm::Intrinsic::ID notIntrinsic = llvm::Intrinsic::not_intrinsic;
4435
4436 constexpr unsigned numRedKinds = 8; // ADD, MIN, MAX, INC, DEC, AND, OR, XOR
4437 constexpr unsigned numLayouts = 2; // TILE, IM2COL
4438 constexpr unsigned maxDim = 5; // 1D to 5D
4439 using row = std::array<llvm::Intrinsic::ID, maxDim + 1>;
4440 using layoutTable = std::array<row, numLayouts>;
4441 using fullTable = std::array<layoutTable, numRedKinds>;
4442 static constexpr fullTable IDTable{
4443 {// RedTy::ADD
4444 {{{{notIntrinsic,
4445 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_add_tile_1d,
4446 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_add_tile_2d,
4447 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_add_tile_3d,
4448 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_add_tile_4d,
4449 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_add_tile_5d}},
4451 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_add_im2col_3d,
4452 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_add_im2col_4d,
4453 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_add_im2col_5d}}}},
4454 // RedTy::MIN
4455 {{{{notIntrinsic,
4456 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_min_tile_1d,
4457 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_min_tile_2d,
4458 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_min_tile_3d,
4459 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_min_tile_4d,
4460 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_min_tile_5d}},
4462 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_min_im2col_3d,
4463 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_min_im2col_4d,
4464 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_min_im2col_5d}}}},
4465 // RedTy::MAX
4466 {{{{notIntrinsic,
4467 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_max_tile_1d,
4468 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_max_tile_2d,
4469 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_max_tile_3d,
4470 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_max_tile_4d,
4471 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_max_tile_5d}},
4473 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_max_im2col_3d,
4474 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_max_im2col_4d,
4475 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_max_im2col_5d}}}},
4476 // RedTy::INC
4477 {{{{notIntrinsic,
4478 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_inc_tile_1d,
4479 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_inc_tile_2d,
4480 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_inc_tile_3d,
4481 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_inc_tile_4d,
4482 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_inc_tile_5d}},
4484 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_inc_im2col_3d,
4485 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_inc_im2col_4d,
4486 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_inc_im2col_5d}}}},
4487 // RedTy::DEC
4488 {{{{notIntrinsic,
4489 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_dec_tile_1d,
4490 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_dec_tile_2d,
4491 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_dec_tile_3d,
4492 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_dec_tile_4d,
4493 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_dec_tile_5d}},
4495 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_dec_im2col_3d,
4496 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_dec_im2col_4d,
4497 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_dec_im2col_5d}}}},
4498 // RedTy::AND
4499 {{{{notIntrinsic,
4500 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_and_tile_1d,
4501 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_and_tile_2d,
4502 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_and_tile_3d,
4503 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_and_tile_4d,
4504 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_and_tile_5d}},
4506 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_and_im2col_3d,
4507 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_and_im2col_4d,
4508 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_and_im2col_5d}}}},
4509 // RedTy::OR
4510 {{{{notIntrinsic,
4511 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_or_tile_1d,
4512 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_or_tile_2d,
4513 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_or_tile_3d,
4514 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_or_tile_4d,
4515 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_or_tile_5d}},
4517 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_or_im2col_3d,
4518 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_or_im2col_4d,
4519 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_or_im2col_5d}}}},
4520 // RedTy::XOR
4521 {{{{notIntrinsic,
4522 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_xor_tile_1d,
4523 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_xor_tile_2d,
4524 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_xor_tile_3d,
4525 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_xor_tile_4d,
4526 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_xor_tile_5d}},
4528 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_xor_im2col_3d,
4529 llvm::Intrinsic::nvvm_cp_async_bulk_tensor_reduce_xor_im2col_4d,
4530 llvm::Intrinsic::
4531 nvvm_cp_async_bulk_tensor_reduce_xor_im2col_5d}}}}}};
4532
4533 static_assert(getMaxEnumValForTMAReduxKind() == std::size(IDTable) - 1,
4534 "TMAReduxKinds must match number of rows in IDTable");
4535
4536 size_t redKind = static_cast<size_t>(thisOp.getRedKind());
4537 size_t mode = static_cast<size_t>(thisOp.getMode());
4538 size_t dim = thisOp.getCoordinates().size();
4539
4540 assert(redKind < IDTable.size() &&
4541 "Invalid redKind for CpAsyncBulkTensorReduceOp");
4542 assert(mode < IDTable[redKind].size() &&
4543 "Invalid mode for CpAsyncBulkTensorReduceOp");
4544 assert(dim < IDTable[redKind][mode].size() &&
4545 "Invalid dim for CpAsyncBulkTensorReduceOp");
4546
4547 llvm::Intrinsic::ID intrinsicID = IDTable[redKind][mode][dim];
4548
4549 assert(intrinsicID != notIntrinsic &&
4550 "Invalid intrinsic for CpAsyncBulkTensorReduceOp.");
4551
4552 return {intrinsicID, std::move(args)};
4553}
4554
4555#define _none
4556
4557#define CVT_F2TF32_ID_IMPL(rnd, relu, sf) \
4558 hasRelu ? llvm::Intrinsic::nvvm_f2tf32_##rnd##relu##sf \
4559 : llvm::Intrinsic::nvvm_f2tf32_##rnd##sf
4560
4561#define GET_CVT_F2TF32_ID(rnd, relu, sf) \
4562 hasSatFinite ? CVT_F2TF32_ID_IMPL(rnd, relu, sf) \
4563 : CVT_F2TF32_ID_IMPL(rnd, relu, )
4564
4565llvm::Intrinsic::ID
4566ConvertFloatToTF32Op::getIntrinsicID(NVVM::FPRoundingMode rnd,
4567 NVVM::SaturationMode sat, bool hasRelu) {
4568 using RndMode = NVVM::FPRoundingMode;
4569 bool hasSatFinite = (sat == NVVM::SaturationMode::SATFINITE);
4570 switch (rnd) {
4571 case RndMode::RN:
4572 return GET_CVT_F2TF32_ID(rn, _relu, _satfinite);
4573 case RndMode::RZ:
4574 return GET_CVT_F2TF32_ID(rz, _relu, _satfinite);
4575 case RndMode::RNA:
4576 return GET_CVT_F2TF32_ID(rna, _none, _satfinite);
4577 default:
4578 llvm_unreachable("Invalid RoundingMode for CvtFloatToTF32Op");
4579 }
4580}
4581
4583ConvertF32x2ToF4x2Op::getIntrinsicIDAndArgs(NVVM::ConvertF32x2ToF4x2Op op,
4585 llvm::IRBuilderBase &builder) {
4587 args.push_back(mt.lookupValue(op.getA()));
4588 args.push_back(mt.lookupValue(op.getB()));
4589
4590 bool hasRelu = op.getRelu();
4591
4592 llvm::Intrinsic::ID intId =
4593 hasRelu ? llvm::Intrinsic::nvvm_ff_to_e2m1x2_rn_relu_satfinite
4594 : llvm::Intrinsic::nvvm_ff_to_e2m1x2_rn_satfinite;
4595
4596 return {intId, std::move(args)};
4597}
4598
4599#define GET_F32x2_TO_F6x2_ID(type, has_relu) \
4600 has_relu ? llvm::Intrinsic::nvvm_ff_to_##type##_rn_relu_satfinite \
4601 : llvm::Intrinsic::nvvm_ff_to_##type##_rn_satfinite
4602
4603llvm::Intrinsic::ID ConvertF32x2ToF6x2Op::getIntrinsicID(mlir::Type dstTy,
4604 bool hasRelu) {
4606 .Case([&](mlir::Float6E2M3FNType) {
4607 return GET_F32x2_TO_F6x2_ID(e2m3x2, hasRelu);
4608 })
4609 .Case([&](mlir::Float6E3M2FNType) {
4610 return GET_F32x2_TO_F6x2_ID(e3m2x2, hasRelu);
4611 })
4612 .Default([](mlir::Type) {
4613 llvm_unreachable("Invalid conversion in ConvertF32x2ToF6x2Op");
4614 return llvm::Intrinsic::not_intrinsic;
4615 });
4616}
4617
4619ConvertF16x2ToF4x2Op::getIntrinsicIDAndArgs(NVVM::ConvertF16x2ToF4x2Op &op,
4621 llvm::IRBuilderBase &builder) {
4622 mlir::Type dstTy = op.getDstTy();
4623 bool hasRelu = op.getRelu();
4624
4625 llvm::Intrinsic::ID intId = llvm::Intrinsic::not_intrinsic;
4626
4627 if (llvm::isa<mlir::Float4E2M1FNType>(dstTy))
4628 intId = hasRelu ? llvm::Intrinsic::nvvm_f16x2_to_e2m1x2_rn_relu_satfinite
4629 : llvm::Intrinsic::nvvm_f16x2_to_e2m1x2_rn_satfinite;
4630
4632 args.push_back(mt.lookupValue(op.getSrc()));
4633
4634 return {intId, std::move(args)};
4635}
4636
4638ConvertBF16x2ToF4x2Op::getIntrinsicIDAndArgs(NVVM::ConvertBF16x2ToF4x2Op &op,
4640 llvm::IRBuilderBase &builder) {
4641 mlir::Type dstTy = op.getDstTy();
4642 bool hasRelu = op.getRelu();
4643
4644 llvm::Intrinsic::ID intId = llvm::Intrinsic::not_intrinsic;
4645
4646 if (llvm::isa<mlir::Float4E2M1FNType>(dstTy))
4647 intId = hasRelu ? llvm::Intrinsic::nvvm_bf16x2_to_e2m1x2_rn_relu_satfinite
4648 : llvm::Intrinsic::nvvm_bf16x2_to_e2m1x2_rn_satfinite;
4649
4651 args.push_back(mt.lookupValue(op.getSrc()));
4652
4653 return {intId, std::move(args)};
4654}
4655
4656llvm::Intrinsic::ID ConvertF16x2ToF6x2Op::getIntrinsicID(mlir::Type dstTy,
4657 bool hasRelu) {
4659 .Case<mlir::Float6E2M3FNType>([&](mlir::Float6E2M3FNType) {
4660 return hasRelu ? llvm::Intrinsic::nvvm_f16x2_to_e2m3x2_rn_relu_satfinite
4661 : llvm::Intrinsic::nvvm_f16x2_to_e2m3x2_rn_satfinite;
4662 })
4663 .Case<mlir::Float6E3M2FNType>([&](mlir::Float6E3M2FNType) {
4664 return hasRelu ? llvm::Intrinsic::nvvm_f16x2_to_e3m2x2_rn_relu_satfinite
4665 : llvm::Intrinsic::nvvm_f16x2_to_e3m2x2_rn_satfinite;
4666 })
4667 .Default([](mlir::Type) {
4668 llvm_unreachable("Invalid conversion in ConvertF16x2ToF6x2Op");
4669 return llvm::Intrinsic::not_intrinsic;
4670 });
4671}
4672
4673llvm::Intrinsic::ID ConvertBF16x2ToF6x2Op::getIntrinsicID(mlir::Type dstTy,
4674 bool hasRelu) {
4676 .Case<mlir::Float6E2M3FNType>([&](mlir::Float6E2M3FNType) {
4677 return hasRelu
4678 ? llvm::Intrinsic::nvvm_bf16x2_to_e2m3x2_rn_relu_satfinite
4679 : llvm::Intrinsic::nvvm_bf16x2_to_e2m3x2_rn_satfinite;
4680 })
4681 .Case<mlir::Float6E3M2FNType>([&](mlir::Float6E3M2FNType) {
4682 return hasRelu
4683 ? llvm::Intrinsic::nvvm_bf16x2_to_e3m2x2_rn_relu_satfinite
4684 : llvm::Intrinsic::nvvm_bf16x2_to_e3m2x2_rn_satfinite;
4685 })
4686 .Default([](mlir::Type) {
4687 llvm_unreachable("Invalid conversion in ConvertBF16x2ToF6x2Op");
4688 return llvm::Intrinsic::not_intrinsic;
4689 });
4690}
4691
4692#define GET_F32x2_TO_F8X2_US_ID(rnd, has_satf) \
4693 has_satf ? llvm::Intrinsic::nvvm_ff_to_ue8m0x2_##rnd##_satfinite \
4694 : llvm::Intrinsic::nvvm_ff_to_ue8m0x2_##rnd
4695
4696#define GET_F32x2_TO_F8X2_S_ID(type, has_relu) \
4697 has_relu ? llvm::Intrinsic::nvvm_ff_to_##type##_rn_relu \
4698 : llvm::Intrinsic::nvvm_ff_to_##type##_rn
4699
4700llvm::Intrinsic::ID
4701ConvertF32x2ToF8x2Op::getIntrinsicID(mlir::Type dstTy, NVVM::FPRoundingMode rnd,
4702 NVVM::SaturationMode sat, bool hasRelu) {
4703 bool hasSatFinite = (sat == NVVM::SaturationMode::SATFINITE);
4704 bool hasRoundingModeRZ = (rnd == NVVM::FPRoundingMode::RZ);
4705 bool hasRoundingModeRP = (rnd == NVVM::FPRoundingMode::RP);
4706
4708 .Case([&](mlir::Float8E4M3FNType) {
4709 return GET_F32x2_TO_F8X2_S_ID(e4m3x2, hasRelu);
4710 })
4711 .Case([&](mlir::Float8E5M2Type) {
4712 return GET_F32x2_TO_F8X2_S_ID(e5m2x2, hasRelu);
4713 })
4714 .Case([&](mlir::Float8E8M0FNUType) {
4715 if (hasRoundingModeRZ)
4716 return GET_F32x2_TO_F8X2_US_ID(rz, hasSatFinite);
4717 else if (hasRoundingModeRP)
4718 return GET_F32x2_TO_F8X2_US_ID(rp, hasSatFinite);
4719
4720 llvm_unreachable("Invalid conversion in ConvertF32x2ToF8x2Op");
4721 })
4722 .Default([](mlir::Type) {
4723 llvm_unreachable("Invalid conversion in ConvertF32x2ToF8x2Op");
4724 return llvm::Intrinsic::not_intrinsic;
4725 });
4726}
4727
4728#define GET_F16x2_TO_F8X2_ID(type, has_relu) \
4729 has_relu ? llvm::Intrinsic::nvvm_f16x2_to_##type##_rn_relu \
4730 : llvm::Intrinsic::nvvm_f16x2_to_##type##_rn
4731
4732llvm::Intrinsic::ID ConvertF16x2ToF8x2Op::getIntrinsicID(mlir::Type dstTy,
4733 bool hasRelu) {
4735 .Case([&](mlir::Float8E4M3FNType) {
4736 return GET_F16x2_TO_F8X2_ID(e4m3x2, hasRelu);
4737 })
4738 .Case([&](mlir::Float8E5M2Type) {
4739 return GET_F16x2_TO_F8X2_ID(e5m2x2, hasRelu);
4740 })
4741 .Default([](mlir::Type) {
4742 llvm_unreachable("Invalid conversion in ConvertF16x2ToF8x2Op");
4743 return llvm::Intrinsic::not_intrinsic;
4744 });
4745}
4746
4747llvm::Intrinsic::ID
4748ConvertBF16x2ToF8x2Op::getIntrinsicID(mlir::Type dstTy,
4749 NVVM::FPRoundingMode rnd,
4750 NVVM::SaturationMode sat, bool hasRelu) {
4751 bool hasSatFinite = (sat == NVVM::SaturationMode::SATFINITE);
4752
4753 static constexpr llvm::Intrinsic::ID ue8m0x2IDs[] = {
4754 llvm::Intrinsic::nvvm_bf16x2_to_ue8m0x2_rz,
4755 llvm::Intrinsic::nvvm_bf16x2_to_ue8m0x2_rp,
4756 llvm::Intrinsic::nvvm_bf16x2_to_ue8m0x2_rz_satfinite,
4757 llvm::Intrinsic::nvvm_bf16x2_to_ue8m0x2_rp_satfinite,
4758 };
4759
4761 .Case<mlir::Float8E4M3FNType>([&](mlir::Float8E4M3FNType) {
4762 return hasRelu
4763 ? llvm::Intrinsic::nvvm_bf16x2_to_e4m3x2_rn_relu_satfinite
4764 : llvm::Intrinsic::nvvm_bf16x2_to_e4m3x2_rn_satfinite;
4765 })
4766 .Case<mlir::Float8E5M2Type>([&](mlir::Float8E5M2Type) {
4767 return hasRelu
4768 ? llvm::Intrinsic::nvvm_bf16x2_to_e5m2x2_rn_relu_satfinite
4769 : llvm::Intrinsic::nvvm_bf16x2_to_e5m2x2_rn_satfinite;
4770 })
4771 .Case<mlir::Float8E8M0FNUType>([&](mlir::Float8E8M0FNUType) {
4772 bool hasRoundingModeRP = (rnd == NVVM::FPRoundingMode::RP);
4773 unsigned index = (hasSatFinite << 1) | hasRoundingModeRP;
4774 return ue8m0x2IDs[index];
4775 })
4776 .Default([](mlir::Type) {
4777 llvm_unreachable("Invalid conversion in ConvertBF16x2ToF8x2Op");
4778 return llvm::Intrinsic::not_intrinsic;
4779 });
4780}
4781
4782NVVM::IDArgPair ConvertF8x2ToF16x2Op::getIntrinsicIDAndArgs(
4783 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
4784 auto curOp = cast<NVVM::ConvertF8x2ToF16x2Op>(op);
4785
4786 bool hasRelu = curOp.getRelu();
4787
4788 llvm::Intrinsic::ID intId =
4790 .Case([&](Float8E4M3FNType type) {
4791 return hasRelu ? llvm::Intrinsic::nvvm_e4m3x2_to_f16x2_rn_relu
4792 : llvm::Intrinsic::nvvm_e4m3x2_to_f16x2_rn;
4793 })
4794 .Case([&](Float8E5M2Type type) {
4795 return hasRelu ? llvm::Intrinsic::nvvm_e5m2x2_to_f16x2_rn_relu
4796 : llvm::Intrinsic::nvvm_e5m2x2_to_f16x2_rn;
4797 })
4798 .Default([](mlir::Type type) {
4799 llvm_unreachable("Invalid type for ConvertF8x2ToF16x2Op");
4800 return llvm::Intrinsic::not_intrinsic;
4801 });
4802
4803 llvm::Value *packedI16 =
4804 builder.CreateBitCast(mt.lookupValue(curOp.getSrc()),
4805 llvm::Type::getInt16Ty(builder.getContext()));
4806
4807 return {intId, {packedI16}};
4808}
4809
4810NVVM::IDArgPair ConvertF8x2ToBF16x2Op::getIntrinsicIDAndArgs(
4811 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
4812 auto curOp = cast<NVVM::ConvertF8x2ToBF16x2Op>(op);
4813 bool hasScale = static_cast<bool>(curOp.getScaleFactor());
4814 bool hasSatfinite = curOp.getSat() == NVVM::SaturationMode::SATFINITE;
4815 bool hasRelu = curOp.getRelu();
4816
4817 static constexpr llvm::Intrinsic::ID E4M3Ids[] = {
4818 llvm::Intrinsic::nvvm_e4m3x2_to_bf16x2_rn_scale_n2_ue8m0,
4819 llvm::Intrinsic::nvvm_e4m3x2_to_bf16x2_rn_relu_scale_n2_ue8m0,
4820 llvm::Intrinsic::nvvm_e4m3x2_to_bf16x2_rn_satfinite_scale_n2_ue8m0,
4821 llvm::Intrinsic::nvvm_e4m3x2_to_bf16x2_rn_relu_satfinite_scale_n2_ue8m0,
4822 };
4823
4824 static constexpr llvm::Intrinsic::ID E5M2Ids[] = {
4825 llvm::Intrinsic::nvvm_e5m2x2_to_bf16x2_rn_scale_n2_ue8m0,
4826 llvm::Intrinsic::nvvm_e5m2x2_to_bf16x2_rn_relu_scale_n2_ue8m0,
4827 llvm::Intrinsic::nvvm_e5m2x2_to_bf16x2_rn_satfinite_scale_n2_ue8m0,
4828 llvm::Intrinsic::nvvm_e5m2x2_to_bf16x2_rn_relu_satfinite_scale_n2_ue8m0,
4829 };
4830
4831 llvm::Intrinsic::ID intId =
4833 .Case([&](Float8E8M0FNUType type) {
4834 return llvm::Intrinsic::nvvm_ue8m0x2_to_bf16x2;
4835 })
4836 .Case([&](Float8E4M3FNType type) {
4837 return E4M3Ids[hasSatfinite << 1 | hasRelu];
4838 })
4839 .Case([&](Float8E5M2Type type) {
4840 return E5M2Ids[hasSatfinite << 1 | hasRelu];
4841 })
4842 .Default([](mlir::Type type) {
4843 llvm_unreachable("Invalid type for ConvertF8x2ToBF16x2Op");
4844 return llvm::Intrinsic::not_intrinsic;
4845 });
4846 llvm::Value *packedI16 =
4847 builder.CreateBitCast(mt.lookupValue(curOp.getSrc()),
4848 llvm::Type::getInt16Ty(builder.getContext()));
4849
4851 args.push_back(packedI16);
4852 if (!isa<Float8E8M0FNUType>(curOp.getSrcType()))
4853 args.push_back(
4854 hasScale ? mt.lookupValue(curOp.getScaleFactor())
4855 : builder.getInt16(0x7f7f)); // default scale factor (value of
4856 // 1 for both elements)
4857
4858 return {intId, std::move(args)};
4859}
4860
4861NVVM::IDArgPair ConvertF6x2ToF16x2Op::getIntrinsicIDAndArgs(
4862 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
4863 auto curOp = cast<NVVM::ConvertF6x2ToF16x2Op>(op);
4864
4865 bool hasRelu = curOp.getRelu();
4866
4867 llvm::Intrinsic::ID intId =
4869 .Case([&](Float6E2M3FNType type) {
4870 return hasRelu ? llvm::Intrinsic::nvvm_e2m3x2_to_f16x2_rn_relu
4871 : llvm::Intrinsic::nvvm_e2m3x2_to_f16x2_rn;
4872 })
4873 .Case([&](Float6E3M2FNType type) {
4874 return hasRelu ? llvm::Intrinsic::nvvm_e3m2x2_to_f16x2_rn_relu
4875 : llvm::Intrinsic::nvvm_e3m2x2_to_f16x2_rn;
4876 })
4877 .Default([](mlir::Type type) {
4878 llvm_unreachable("Invalid type for ConvertF6x2ToF16x2Op");
4879 return llvm::Intrinsic::not_intrinsic;
4880 });
4881
4882 llvm::Value *packedI16 =
4883 builder.CreateBitCast(mt.lookupValue(curOp.getSrc()),
4884 llvm::Type::getInt16Ty(builder.getContext()));
4885
4886 return {intId, {packedI16}};
4887}
4888
4889NVVM::IDArgPair ConvertF6x2ToBF16x2Op::getIntrinsicIDAndArgs(
4890 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
4891 auto curOp = cast<NVVM::ConvertF6x2ToBF16x2Op>(op);
4892 bool hasScale = static_cast<bool>(curOp.getScaleFactor());
4893 bool hasSatfinite = curOp.getSat() == NVVM::SaturationMode::SATFINITE;
4894 bool hasRelu = curOp.getRelu();
4895
4896 static constexpr llvm::Intrinsic::ID E2M3Ids[] = {
4897 llvm::Intrinsic::nvvm_e2m3x2_to_bf16x2_rn_scale_n2_ue8m0,
4898 llvm::Intrinsic::nvvm_e2m3x2_to_bf16x2_rn_relu_scale_n2_ue8m0,
4899 llvm::Intrinsic::nvvm_e2m3x2_to_bf16x2_rn_satfinite_scale_n2_ue8m0,
4900 llvm::Intrinsic::nvvm_e2m3x2_to_bf16x2_rn_relu_satfinite_scale_n2_ue8m0,
4901 };
4902
4903 static constexpr llvm::Intrinsic::ID E3M2Ids[] = {
4904 llvm::Intrinsic::nvvm_e3m2x2_to_bf16x2_rn_scale_n2_ue8m0,
4905 llvm::Intrinsic::nvvm_e3m2x2_to_bf16x2_rn_relu_scale_n2_ue8m0,
4906 llvm::Intrinsic::nvvm_e3m2x2_to_bf16x2_rn_satfinite_scale_n2_ue8m0,
4907 llvm::Intrinsic::nvvm_e3m2x2_to_bf16x2_rn_relu_satfinite_scale_n2_ue8m0,
4908 };
4909
4910 unsigned idx = (hasSatfinite << 1) | hasRelu;
4911 llvm::Intrinsic::ID intId =
4913 .Case([&](Float6E2M3FNType type) { return E2M3Ids[idx]; })
4914 .Case([&](Float6E3M2FNType type) { return E3M2Ids[idx]; })
4915 .Default([](mlir::Type type) {
4916 llvm_unreachable("Invalid type for ConvertF6x2ToBF16x2Op");
4917 return llvm::Intrinsic::not_intrinsic;
4918 });
4919
4920 llvm::Value *packedI16 =
4921 builder.CreateBitCast(mt.lookupValue(curOp.getSrc()),
4922 llvm::Type::getInt16Ty(builder.getContext()));
4923
4925 args.push_back(packedI16);
4926 args.push_back(
4927 hasScale
4928 ? mt.lookupValue(curOp.getScaleFactor())
4929 : builder.getInt16(
4930 0x7f7f)); // default scale factor (value of 1 for both elements)
4931
4932 return {intId, std::move(args)};
4933}
4934
4935NVVM::IDArgPair ConvertF4x2ToF16x2Op::getIntrinsicIDAndArgs(
4936 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
4937 auto curOp = cast<NVVM::ConvertF4x2ToF16x2Op>(op);
4938
4939 bool hasRelu = curOp.getRelu();
4940
4941 llvm::Intrinsic::ID intId =
4943 .Case([&](Float4E2M1FNType type) {
4944 return hasRelu ? llvm::Intrinsic::nvvm_e2m1x2_to_f16x2_rn_relu
4945 : llvm::Intrinsic::nvvm_e2m1x2_to_f16x2_rn;
4946 })
4947 .Default([](mlir::Type type) {
4948 llvm_unreachable("Invalid type for ConvertF4x2ToF16x2Op");
4949 return llvm::Intrinsic::not_intrinsic;
4950 });
4951
4952 llvm::Value *extendedI16 =
4953 builder.CreateZExt(mt.lookupValue(curOp.getSrc()),
4954 llvm::Type::getInt16Ty(builder.getContext()));
4955
4956 return {intId, {extendedI16}};
4957}
4958
4959NVVM::IDArgPair ConvertF4x2ToBF16x2Op::getIntrinsicIDAndArgs(
4960 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
4961 auto curOp = cast<NVVM::ConvertF4x2ToBF16x2Op>(op);
4962 bool hasScale = static_cast<bool>(curOp.getScaleFactor());
4963 bool hasSatfinite = curOp.getSat() == NVVM::SaturationMode::SATFINITE;
4964 bool hasRelu = curOp.getRelu();
4965
4966 static constexpr llvm::Intrinsic::ID E2M1Ids[] = {
4967 llvm::Intrinsic::nvvm_e2m1x2_to_bf16x2_rn_scale_n2_ue8m0,
4968 llvm::Intrinsic::nvvm_e2m1x2_to_bf16x2_rn_relu_scale_n2_ue8m0,
4969 llvm::Intrinsic::nvvm_e2m1x2_to_bf16x2_rn_satfinite_scale_n2_ue8m0,
4970 llvm::Intrinsic::nvvm_e2m1x2_to_bf16x2_rn_relu_satfinite_scale_n2_ue8m0,
4971 };
4972
4973 unsigned idx = (hasSatfinite << 1) | hasRelu;
4974 llvm::Intrinsic::ID intId =
4976 .Case([&](Float4E2M1FNType type) { return E2M1Ids[idx]; })
4977 .Default([](mlir::Type type) {
4978 llvm_unreachable("Invalid type for ConvertF4x2ToBF16x2Op");
4979 return llvm::Intrinsic::not_intrinsic;
4980 });
4981
4982 llvm::Value *extendedI16 =
4983 builder.CreateZExt(mt.lookupValue(curOp.getSrc()),
4984 llvm::Type::getInt16Ty(builder.getContext()));
4985
4987 args.push_back(extendedI16);
4988 args.push_back(
4989 hasScale
4990 ? mt.lookupValue(curOp.getScaleFactor())
4991 : builder.getInt16(
4992 0x7f7f)); // default scale factor (value of 1 for both elements)
4993
4994 return {intId, std::move(args)};
4995}
4996
4997NVVM::IDArgPair ConvertF32x2ToS2F6x2Op::getIntrinsicIDAndArgs(
4998 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
4999 auto thisOp = cast<NVVM::ConvertF32x2ToS2F6x2Op>(op);
5000 bool hasRelu = thisOp.getRelu();
5001 bool hasScale = static_cast<bool>(thisOp.getScaleFactor());
5002
5003 llvm::Intrinsic::ID id =
5004 hasRelu
5005 ? llvm::Intrinsic::nvvm_ff_to_s2f6x2_rn_relu_satfinite_scale_n2_ue8m0
5006 : llvm::Intrinsic::nvvm_ff_to_s2f6x2_rn_satfinite_scale_n2_ue8m0;
5007
5008 // Fill the Intrinsic Args
5010 args.push_back(mt.lookupValue(thisOp.getA()));
5011 args.push_back(mt.lookupValue(thisOp.getB()));
5012 args.push_back(hasScale ? mt.lookupValue(thisOp.getScaleFactor())
5013 : builder.getInt16(0x7f7f));
5014 return {id, std::move(args)};
5015}
5016
5017NVVM::IDArgPair ConvertBF16x2ToS2F6x2Op::getIntrinsicIDAndArgs(
5018 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
5019 auto thisOp = cast<NVVM::ConvertBF16x2ToS2F6x2Op>(op);
5020 bool hasRelu = thisOp.getRelu();
5021 bool hasScale = static_cast<bool>(thisOp.getScaleFactor());
5022
5023 llvm::Intrinsic::ID id =
5024 hasRelu
5025 ? llvm::Intrinsic::
5026 nvvm_bf16x2_to_s2f6x2_rn_relu_satfinite_scale_n2_ue8m0
5027 : llvm::Intrinsic::nvvm_bf16x2_to_s2f6x2_rn_satfinite_scale_n2_ue8m0;
5028
5029 // Fill the Intrinsic Args
5031 args.push_back(mt.lookupValue(thisOp.getSrc()));
5032 args.push_back(hasScale ? mt.lookupValue(thisOp.getScaleFactor())
5033 : builder.getInt16(0x7f7f));
5034 return {id, std::move(args)};
5035}
5036
5037NVVM::IDArgPair ConvertS2F6x2ToBF16x2Op::getIntrinsicIDAndArgs(
5038 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
5039 auto thisOp = cast<NVVM::ConvertS2F6x2ToBF16x2Op>(op);
5040 bool hasRelu = thisOp.getRelu();
5041 bool hasScale = static_cast<bool>(thisOp.getScaleFactor());
5042 bool hasSat = thisOp.getSat() == NVVM::SaturationMode::SATFINITE;
5043
5044 static constexpr llvm::Intrinsic::ID ids[] = {
5045 llvm::Intrinsic::nvvm_s2f6x2_to_bf16x2_rn_scale_n2_ue8m0,
5046 llvm::Intrinsic::nvvm_s2f6x2_to_bf16x2_rn_relu_scale_n2_ue8m0,
5047 llvm::Intrinsic::nvvm_s2f6x2_to_bf16x2_rn_satfinite_scale_n2_ue8m0,
5048 llvm::Intrinsic::nvvm_s2f6x2_to_bf16x2_rn_relu_satfinite_scale_n2_ue8m0,
5049 };
5050
5051 unsigned idx = (hasSat << 1) | hasRelu;
5052
5053 // Fill the Intrinsic Args
5055 llvm::Value *packedI16 =
5056 builder.CreateBitCast(mt.lookupValue(thisOp.getSrc()),
5057 llvm::Type::getInt16Ty(builder.getContext()));
5058 args.push_back(packedI16);
5059 args.push_back(hasScale ? mt.lookupValue(thisOp.getScaleFactor())
5060 : builder.getInt16(0x7f7f));
5061
5062 return {ids[idx], std::move(args)};
5063}
5064
5065llvm::Intrinsic::ID
5066Tcgen05AllocOp::getIntrinsicIDAndArgs(Operation &op,
5069 auto curOp = cast<NVVM::Tcgen05AllocOp>(op);
5070 unsigned as = llvm::cast<LLVM::LLVMPointerType>(curOp.getAddr().getType())
5071 .getAddressSpace();
5072 bool isShared = as == NVVMMemorySpace::Shared;
5073 bool is2CTAMode = curOp.getGroup() == CTAGroupKind::CTA_2;
5074
5075 llvm::Intrinsic::ID id;
5076 if (isShared) {
5077 id = is2CTAMode ? llvm::Intrinsic::nvvm_tcgen05_alloc_shared_cg2
5078 : llvm::Intrinsic::nvvm_tcgen05_alloc_shared_cg1;
5079 } else {
5080 id = is2CTAMode ? llvm::Intrinsic::nvvm_tcgen05_alloc_cg2
5081 : llvm::Intrinsic::nvvm_tcgen05_alloc_cg1;
5082 }
5083
5084 // Fill the Intrinsic Args
5085 args.push_back(mt.lookupValue(curOp.getAddr()));
5086 args.push_back(mt.lookupValue(curOp.getNCols()));
5087
5088 return id;
5089}
5090
5091llvm::Intrinsic::ID Tcgen05DeallocOp::getIntrinsicIDAndArgs(
5094 auto curOp = cast<NVVM::Tcgen05DeallocOp>(op);
5095 auto id = (curOp.getGroup() == CTAGroupKind::CTA_1)
5096 ? llvm::Intrinsic::nvvm_tcgen05_dealloc_cg1
5097 : llvm::Intrinsic::nvvm_tcgen05_dealloc_cg2;
5098
5099 // Fill the Intrinsic Args
5100 args.push_back(mt.lookupValue(curOp.getTaddr()));
5101 args.push_back(mt.lookupValue(curOp.getNCols()));
5102
5103 return id;
5104}
5105
5106#define TCGEN05_COMMIT_IMPL(cg, is_shared, mc) \
5107 is_shared ? llvm::Intrinsic::nvvm_tcgen05_commit##mc##_shared##_##cg \
5108 : llvm::Intrinsic::nvvm_tcgen05_commit##mc##_##cg
5109
5110#define GET_TCGEN05_COMMIT_ID(cta_group, is_shared, has_mc) \
5111 has_mc ? TCGEN05_COMMIT_IMPL(cta_group, is_shared, _mc) \
5112 : TCGEN05_COMMIT_IMPL(cta_group, is_shared, )
5113
5114llvm::Intrinsic::ID
5115Tcgen05CommitOp::getIntrinsicIDAndArgs(Operation &op,
5118 auto curOp = cast<NVVM::Tcgen05CommitOp>(op);
5119 unsigned as = llvm::cast<LLVM::LLVMPointerType>(curOp.getAddr().getType())
5120 .getAddressSpace();
5121 bool isShared = as == NVVMMemorySpace::Shared;
5122 bool hasMulticast = static_cast<bool>(curOp.getMulticastMask());
5123 bool is2CTAMode = curOp.getGroup() == CTAGroupKind::CTA_2;
5124
5125 llvm::Intrinsic::ID id =
5126 is2CTAMode ? GET_TCGEN05_COMMIT_ID(cg2, isShared, hasMulticast)
5127 : GET_TCGEN05_COMMIT_ID(cg1, isShared, hasMulticast);
5128
5129 // Fill the Intrinsic Args
5130 args.push_back(mt.lookupValue(curOp.getAddr()));
5131 if (hasMulticast)
5132 args.push_back(mt.lookupValue(curOp.getMulticastMask()));
5133
5134 return id;
5135}
5136
5137#define TCGEN05_CP_IMPL(shape_mc, src_fmt, cg) \
5138 llvm::Intrinsic::nvvm_tcgen05_cp##shape_mc##src_fmt##cg
5139
5140#define TCGEN05_CP_2CTA(shape_mc, src_fmt, is_2cta) \
5141 is_2cta ? TCGEN05_CP_IMPL(shape_mc, src_fmt, _cg2) \
5142 : TCGEN05_CP_IMPL(shape_mc, src_fmt, _cg1)
5143
5144#define GET_TCGEN05_CP_ID(shape_mc, src_fmt, is_2cta) \
5145 [&]() -> auto { \
5146 if ((src_fmt) == Tcgen05CpSrcFormat::B6x16_P32) \
5147 return TCGEN05_CP_2CTA(shape_mc, _b6x16_p32, is_2cta); \
5148 if ((src_fmt) == Tcgen05CpSrcFormat::B4x16_P64) \
5149 return TCGEN05_CP_2CTA(shape_mc, _b4x16_p64, is_2cta); \
5150 return TCGEN05_CP_2CTA(shape_mc, , is_2cta); \
5151 }()
5152
5154ConvertF32x2ToF16x2Op::getIntrinsicIDAndArgs(NVVM::ConvertF32x2ToF16x2Op &op,
5156 llvm::IRBuilderBase &builder) {
5157 static constexpr llvm::Intrinsic::ID rndRNIds[] = {
5158 llvm::Intrinsic::nvvm_ff2f16x2_rn,
5159 llvm::Intrinsic::nvvm_ff2f16x2_rn_relu,
5160 llvm::Intrinsic::nvvm_ff2f16x2_rn_satfinite,
5161 llvm::Intrinsic::nvvm_ff2f16x2_rn_relu_satfinite,
5162 };
5163 static constexpr llvm::Intrinsic::ID rndRZIds[] = {
5164 llvm::Intrinsic::nvvm_ff2f16x2_rz,
5165 llvm::Intrinsic::nvvm_ff2f16x2_rz_relu,
5166 llvm::Intrinsic::nvvm_ff2f16x2_rz_satfinite,
5167 llvm::Intrinsic::nvvm_ff2f16x2_rz_relu_satfinite,
5168 };
5169 static constexpr llvm::Intrinsic::ID rndRSIds[] = {
5170 llvm::Intrinsic::nvvm_ff2f16x2_rs,
5171 llvm::Intrinsic::nvvm_ff2f16x2_rs_relu,
5172 llvm::Intrinsic::nvvm_ff2f16x2_rs_satfinite,
5173 llvm::Intrinsic::nvvm_ff2f16x2_rs_relu_satfinite,
5174 };
5175
5176 unsigned hasRelu = op.getRelu() ? 1 : 0;
5177 unsigned hasSatFinite =
5178 (op.getSat() == NVVM::SaturationMode::SATFINITE) ? 1 : 0;
5179 // idx: bit-0 - relu
5180 // bit-1 - satfinite
5181 unsigned idx = (hasSatFinite << 1) | hasRelu;
5182
5184 args.push_back(mt.lookupValue(op.getSrcHi()));
5185 args.push_back(mt.lookupValue(op.getSrcLo()));
5186 if (op.getRandomBits())
5187 args.push_back(mt.lookupValue(op.getRandomBits()));
5188
5189 switch (op.getRnd()) {
5190 case FPRoundingMode::RN:
5191 return {rndRNIds[idx], std::move(args)};
5192 case FPRoundingMode::RZ:
5193 return {rndRZIds[idx], std::move(args)};
5194 case FPRoundingMode::RS:
5195 return {rndRSIds[idx], std::move(args)};
5196 default:
5197 llvm_unreachable("Invalid rounding mode for ConvertF32x2ToF16x2Op");
5198 }
5199}
5200
5202ConvertF32x2ToBF16x2Op::getIntrinsicIDAndArgs(NVVM::ConvertF32x2ToBF16x2Op &op,
5204 llvm::IRBuilderBase &builder) {
5205 static constexpr llvm::Intrinsic::ID rndRNIds[] = {
5206 llvm::Intrinsic::nvvm_ff2bf16x2_rn,
5207 llvm::Intrinsic::nvvm_ff2bf16x2_rn_relu,
5208 llvm::Intrinsic::nvvm_ff2bf16x2_rn_satfinite,
5209 llvm::Intrinsic::nvvm_ff2bf16x2_rn_relu_satfinite,
5210 };
5211 static constexpr llvm::Intrinsic::ID rndRZIds[] = {
5212 llvm::Intrinsic::nvvm_ff2bf16x2_rz,
5213 llvm::Intrinsic::nvvm_ff2bf16x2_rz_relu,
5214 llvm::Intrinsic::nvvm_ff2bf16x2_rz_satfinite,
5215 llvm::Intrinsic::nvvm_ff2bf16x2_rz_relu_satfinite,
5216 };
5217 static constexpr llvm::Intrinsic::ID rndRSIds[] = {
5218 llvm::Intrinsic::nvvm_ff2bf16x2_rs,
5219 llvm::Intrinsic::nvvm_ff2bf16x2_rs_relu,
5220 llvm::Intrinsic::nvvm_ff2bf16x2_rs_satfinite,
5221 llvm::Intrinsic::nvvm_ff2bf16x2_rs_relu_satfinite,
5222 };
5223
5224 unsigned hasRelu = op.getRelu() ? 1 : 0;
5225 unsigned hasSatFinite =
5226 (op.getSat() == NVVM::SaturationMode::SATFINITE) ? 1 : 0;
5227 // idx: bit-0 - relu
5228 // bit-1 - satfinite
5229 unsigned idx = (hasSatFinite << 1) | hasRelu;
5230
5232 args.push_back(mt.lookupValue(op.getSrcHi()));
5233 args.push_back(mt.lookupValue(op.getSrcLo()));
5234 if (op.getRandomBits())
5235 args.push_back(mt.lookupValue(op.getRandomBits()));
5236
5237 switch (op.getRnd()) {
5238 case FPRoundingMode::RN:
5239 return {rndRNIds[idx], std::move(args)};
5240 case FPRoundingMode::RZ:
5241 return {rndRZIds[idx], std::move(args)};
5242 case FPRoundingMode::RS:
5243 return {rndRSIds[idx], std::move(args)};
5244 default:
5245 llvm_unreachable("Invalid rounding mode for ConvertF32x2ToBF16x2Op");
5246 }
5247}
5248
5249llvm::Intrinsic::ID ConvertF32x4ToF8x4Op::getIntrinsicID() {
5250 mlir::Type dstTy = getDstTy();
5251 bool hasRelu = getRelu();
5252
5254 .Case([&](mlir::Float8E4M3FNType) {
5255 return hasRelu ? llvm::Intrinsic::nvvm_f32x4_to_e4m3x4_rs_relu_satfinite
5256 : llvm::Intrinsic::nvvm_f32x4_to_e4m3x4_rs_satfinite;
5257 })
5258 .Case([&](mlir::Float8E5M2Type) {
5259 return hasRelu ? llvm::Intrinsic::nvvm_f32x4_to_e5m2x4_rs_relu_satfinite
5260 : llvm::Intrinsic::nvvm_f32x4_to_e5m2x4_rs_satfinite;
5261 })
5262 .Default([](mlir::Type) {
5263 llvm_unreachable("Invalid F8 type in ConvertF32x4ToF8x4Op");
5264 return llvm::Intrinsic::not_intrinsic;
5265 });
5266}
5267
5268llvm::Intrinsic::ID ConvertF32x4ToF6x4Op::getIntrinsicID() {
5269 mlir::Type dstTy = getDstTy();
5270 bool hasRelu = getRelu();
5271
5273 .Case([&](mlir::Float6E2M3FNType) {
5274 return hasRelu ? llvm::Intrinsic::nvvm_f32x4_to_e2m3x4_rs_relu_satfinite
5275 : llvm::Intrinsic::nvvm_f32x4_to_e2m3x4_rs_satfinite;
5276 })
5277 .Case([&](mlir::Float6E3M2FNType) {
5278 return hasRelu ? llvm::Intrinsic::nvvm_f32x4_to_e3m2x4_rs_relu_satfinite
5279 : llvm::Intrinsic::nvvm_f32x4_to_e3m2x4_rs_satfinite;
5280 })
5281 .Default([](mlir::Type) {
5282 llvm_unreachable("Invalid F6 type in ConvertF32x4ToF6x4Op");
5283 return llvm::Intrinsic::not_intrinsic;
5284 });
5285}
5286
5287llvm::Intrinsic::ID ConvertF32x4ToF4x4Op::getIntrinsicID() {
5288 mlir::Type dstTy = getDstTy();
5289 bool hasRelu = getRelu();
5290
5292 .Case([&](mlir::Float4E2M1FNType) {
5293 return hasRelu ? llvm::Intrinsic::nvvm_f32x4_to_e2m1x4_rs_relu_satfinite
5294 : llvm::Intrinsic::nvvm_f32x4_to_e2m1x4_rs_satfinite;
5295 })
5296 .Default([](mlir::Type) {
5297 llvm_unreachable("Invalid F4 type in ConvertF32x4ToF4x4Op");
5298 return llvm::Intrinsic::not_intrinsic;
5299 });
5300}
5301
5302llvm::Intrinsic::ID Tcgen05CpOp::getIntrinsicID(Operation &op) {
5303 auto curOp = cast<NVVM::Tcgen05CpOp>(op);
5304 bool is2CTA = curOp.getGroup() == CTAGroupKind::CTA_2;
5305 auto srcFmt = curOp.getSrcFormat();
5306 auto mc = curOp.getMulticast();
5307
5308 switch (curOp.getShape()) {
5309 case Tcgen05CpShape::SHAPE_128x256b:
5310 return GET_TCGEN05_CP_ID(_128x256b, srcFmt, is2CTA);
5311 case Tcgen05CpShape::SHAPE_128x128b:
5312 return GET_TCGEN05_CP_ID(_128x128b, srcFmt, is2CTA);
5313 case Tcgen05CpShape::SHAPE_4x256b:
5314 return GET_TCGEN05_CP_ID(_4x256b, srcFmt, is2CTA);
5315 case Tcgen05CpShape::SHAPE_32x128b:
5316 return GET_TCGEN05_CP_ID(_32x128b_warpx4, srcFmt, is2CTA);
5317 case Tcgen05CpShape::SHAPE_64x128b:
5318 return (mc == Tcgen05CpMulticast::WARPX2_01_23)
5319 ? GET_TCGEN05_CP_ID(_64x128b_warpx2_01_23, srcFmt, is2CTA)
5320 : GET_TCGEN05_CP_ID(_64x128b_warpx2_02_13, srcFmt, is2CTA);
5321 }
5322 llvm_unreachable("Invalid shape in tcgen05 cp Op");
5323}
5324
5325// Returns the valid vector length for a given shape and vector length, the
5326// function models the table mentioned in the tcgen05.{ld, st} Op description
5327static unsigned isValidVectorLength(NVVM::Tcgen05LdStShape shape,
5328 unsigned vecLen) {
5329 if (shape == NVVM::Tcgen05LdStShape::SHAPE_16X128B)
5330 return vecLen >= 2;
5331 if (shape == NVVM::Tcgen05LdStShape::SHAPE_16X256B)
5332 return vecLen >= 4;
5333 return true;
5334}
5335
5336LogicalResult Tcgen05LdOp::verify() {
5337 LogicalResult result = success();
5338 if (getShape() == NVVM::Tcgen05LdStShape::SHAPE_16X32BX2 && !getOffset())
5339 result = emitError("shape 16x32bx2 requires offset argument");
5340
5341 if (getShape() != NVVM::Tcgen05LdStShape::SHAPE_16X32BX2 && getOffset())
5342 result = emitError("offset argument is only supported for shape 16x32bx2");
5343
5344 auto resTy = getRes().getType();
5345 unsigned resLen = isa<VectorType>(resTy)
5346 ? llvm::cast<VectorType>(resTy).getNumElements()
5347 : 1;
5348 if (!isValidVectorLength(getShape(), resLen))
5349 result = emitError(llvm::formatv("invalid result type length {0} for shape "
5350 "{1} in tcgen05.ld Op",
5351 resLen, stringifyEnum(getShape())));
5352
5353 return result;
5354}
5355
5356LogicalResult Tcgen05StOp::verify() {
5357 LogicalResult result = success();
5358 if (getShape() == NVVM::Tcgen05LdStShape::SHAPE_16X32BX2 && !getOffset())
5359 result = emitError("shape 16x32bx2 requires offset argument");
5360
5361 auto valTy = getVal().getType();
5362 unsigned valLen = isa<VectorType>(valTy)
5363 ? llvm::cast<VectorType>(valTy).getNumElements()
5364 : 1;
5365 if (!isValidVectorLength(getShape(), valLen))
5366 result = emitError(llvm::formatv("invalid input length {0} for shape "
5367 "{1} in tcgen05.st Op",
5368 valLen, stringifyEnum(getShape())));
5369
5370 return result;
5371}
5372
5373/// Infer the result ranges for the NVVM SpecialRangeableRegisterOp that might
5374/// have ConstantRangeAttr.
5377 SetIntRangeFn setResultRanges) {
5378 if (auto rangeAttr = op->getAttrOfType<LLVM::ConstantRangeAttr>("range")) {
5379 setResultRanges(result, {rangeAttr.getLower(), rangeAttr.getUpper(),
5380 rangeAttr.getLower(), rangeAttr.getUpper()});
5381 } else {
5382 setResultRanges(result, IntegerValueRange::getMaxRange(result).getValue());
5383 }
5384}
5385
5386/// Verify the range attribute satisfies LLVM ConstantRange constructor
5387/// requirements for NVVM SpecialRangeableRegisterOp.
5388static LogicalResult
5390 std::optional<LLVM::ConstantRangeAttr> rangeAttr) {
5391 if (!rangeAttr)
5392 return success();
5393
5394 const llvm::APInt &lower = rangeAttr->getLower();
5395 const llvm::APInt &upper = rangeAttr->getUpper();
5396
5397 // Check LLVM ConstantRange constructor condition
5398 if (lower == upper && !lower.isMaxValue() && !lower.isMinValue()) {
5399 unsigned bitWidth = lower.getBitWidth();
5400 llvm::APInt minVal = llvm::APInt::getMinValue(bitWidth);
5401 llvm::APInt maxVal = llvm::APInt::getMaxValue(bitWidth);
5402 return op->emitOpError(
5403 "invalid range attribute: Lower == Upper, but they aren't min (")
5404 << llvm::toString(minVal, 10, false) << ") or max ("
5405 << llvm::toString(maxVal, 10, false)
5406 << ") value! This is an invalid constant range.";
5407 }
5408
5409 return success();
5410}
5411
5412static llvm::Value *getAsPackedI32(llvm::Value *arg,
5413 llvm::IRBuilderBase &builder) {
5414 return builder.CreateBitCast(arg,
5415 llvm::Type::getInt32Ty(builder.getContext()));
5416}
5417
5418NVVM::IDArgPair DotAccumulate4WayOp::getIntrinsicIDAndArgs(
5419 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
5420 auto curOp = cast<NVVM::DotAccumulate4WayOp>(op);
5421
5423 args.push_back(getAsPackedI32(mt.lookupValue(curOp.getA()), builder));
5424 args.push_back(getAsPackedI32(mt.lookupValue(curOp.getB()), builder));
5425 args.push_back(mt.lookupValue(curOp.getC()));
5426
5427 bool isASigned = curOp.getAType() == NVVM::DotAccumulateType::SIGNED;
5428 bool isBSigned = curOp.getBType() == NVVM::DotAccumulateType::SIGNED;
5429 unsigned type = (isASigned << 1) | isBSigned;
5430 const llvm::Intrinsic::ID ids[] = {
5431 llvm::Intrinsic::nvvm_idp4a_u_u,
5432 llvm::Intrinsic::nvvm_idp4a_u_s,
5433 llvm::Intrinsic::nvvm_idp4a_s_u,
5434 llvm::Intrinsic::nvvm_idp4a_s_s,
5435 };
5436 return {ids[type], args};
5437}
5438
5439NVVM::IDArgPair DotAccumulate2WayOp::getIntrinsicIDAndArgs(
5440 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
5441 auto curOp = cast<NVVM::DotAccumulate2WayOp>(op);
5442
5444 args.push_back(getAsPackedI32(mt.lookupValue(curOp.getA()), builder));
5445 args.push_back(getAsPackedI32(mt.lookupValue(curOp.getB()), builder));
5446 args.push_back(builder.getInt1(curOp.getBHi()));
5447 args.push_back(mt.lookupValue(curOp.getC()));
5448
5449 bool isASigned = curOp.getAType() == NVVM::DotAccumulateType::SIGNED;
5450 bool isBSigned = curOp.getBType() == NVVM::DotAccumulateType::SIGNED;
5451 unsigned type = (isASigned << 1) | isBSigned;
5452 const llvm::Intrinsic::ID ids[] = {
5453 llvm::Intrinsic::nvvm_idp2a_u_u,
5454 llvm::Intrinsic::nvvm_idp2a_u_s,
5455 llvm::Intrinsic::nvvm_idp2a_s_u,
5456 llvm::Intrinsic::nvvm_idp2a_s_s,
5457 };
5458 return {ids[type], args};
5459}
5460
5461static llvm::Value *getParamCastedAddr(llvm::Value *addr,
5462 llvm::IRBuilderBase &builder) {
5463 return builder.CreateAddrSpaceCast(
5464 addr, builder.getPtrTy(llvm::NVPTXAS::ADDRESS_SPACE_ENTRY_PARAM));
5465}
5466
5468PrefetchOp::getIntrinsicIDAndArgs(NVVM::PrefetchOp &op,
5470 llvm::IRBuilderBase &builder) {
5471 using MemSpace = NVVM::NVVMMemorySpace;
5472 using CacheLevel = NVVM::PrefetchCacheLevel;
5473
5474 std::optional<NVVM::PrefetchCacheLevel> cacheLevel = op.getCacheLevel();
5475 std::optional<NVVM::CacheEvictionPriority> evictPriority =
5476 op.getEvictPriority();
5477 unsigned addressSpace =
5478 llvm::cast<LLVM::LLVMPointerType>(op.getAddr().getType())
5479 .getAddressSpace();
5480
5482 llvm::Value *addr = mt.lookupValue(op.getAddr());
5483 args.push_back(op.getInParamSpace() ? getParamCastedAddr(addr, builder)
5484 : addr);
5485
5486 if (op.getTensormap())
5487 return {llvm::Intrinsic::nvvm_prefetch_tensormap, args};
5488
5489 assert(cacheLevel && "expected cache level for non-tensormap prefetch");
5490
5491 if (op.getUniform() && *cacheLevel == CacheLevel::L1)
5492 return {llvm::Intrinsic::nvvm_prefetchu_L1, args};
5493
5494 if (evictPriority && *cacheLevel == CacheLevel::L2) {
5495 switch (*evictPriority) {
5496 case NVVM::CacheEvictionPriority::EvictLast:
5497 return {llvm::Intrinsic::nvvm_prefetch_global_L2_evict_last, args};
5498 case NVVM::CacheEvictionPriority::EvictNormal:
5499 return {llvm::Intrinsic::nvvm_prefetch_global_L2_evict_normal, args};
5500 default:
5501 llvm_unreachable("Invalid cache eviction priority");
5502 }
5503 }
5504
5505 switch (static_cast<MemSpace>(addressSpace)) {
5506 case MemSpace::Generic:
5507 return *cacheLevel == CacheLevel::L1
5508 ? NVVM::IDArgPair({llvm::Intrinsic::nvvm_prefetch_L1, args})
5509 : NVVM::IDArgPair({llvm::Intrinsic::nvvm_prefetch_L2, args});
5510 case MemSpace::Global:
5511 return *cacheLevel == CacheLevel::L1
5513 {llvm::Intrinsic::nvvm_prefetch_global_L1, args})
5514 : NVVM::IDArgPair(
5515 {llvm::Intrinsic::nvvm_prefetch_global_L2, args});
5516 case MemSpace::Local:
5517 return *cacheLevel == CacheLevel::L1
5519 {llvm::Intrinsic::nvvm_prefetch_local_L1, args})
5520 : NVVM::IDArgPair(
5521 {llvm::Intrinsic::nvvm_prefetch_local_L2, args});
5522 default:
5523 llvm_unreachable("Invalid pointer address space");
5524 }
5525}
5526
5527bool NVVM::InlinePtxOp::getAsmValues(
5528 RewriterBase &rewriter,
5529 llvm::SmallVectorImpl<std::pair<mlir::Value, mlir::NVVM::PTXRegisterMod>>
5530 &asmValues) {
5531 for (auto arg : getReadWriteArgs())
5532 asmValues.push_back({arg, mlir::NVVM::PTXRegisterMod::ReadWrite});
5533 for (auto arg : getResults())
5534 asmValues.push_back({arg, mlir::NVVM::PTXRegisterMod::Write});
5535 for (auto arg : getReadOnlyArgs())
5536 asmValues.push_back({arg, mlir::NVVM::PTXRegisterMod::Read});
5537 if (getPredicate())
5538 asmValues.push_back({getPredicate(), mlir::NVVM::PTXRegisterMod::Read});
5539 return false; // No manual mapping needed
5540}
5541
5542NVVM::IDArgPair ClusterLaunchControlTryCancelOp::getIntrinsicIDAndArgs(
5543 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
5544 auto curOp = cast<NVVM::ClusterLaunchControlTryCancelOp>(op);
5546 args.push_back(mt.lookupValue(curOp.getSmemAddress()));
5547 args.push_back(mt.lookupValue(curOp.getMbarrier()));
5548
5549 llvm::Intrinsic::ID intrinsicID =
5550 curOp.getMulticast()
5551 ? llvm::Intrinsic::
5552 nvvm_clusterlaunchcontrol_try_cancel_async_multicast_shared
5553 : llvm::Intrinsic::nvvm_clusterlaunchcontrol_try_cancel_async_shared;
5554
5555 return {intrinsicID, args};
5556}
5557
5558NVVM::IDArgPair ClusterLaunchControlQueryCancelOp::getIntrinsicIDAndArgs(
5559 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
5560 auto curOp = cast<NVVM::ClusterLaunchControlQueryCancelOp>(op);
5562 args.push_back(mt.lookupValue(curOp.getTryCancelResponse()));
5563
5564 llvm::Intrinsic::ID intrinsicID;
5565
5566 switch (curOp.getQueryType()) {
5567 case NVVM::ClusterLaunchControlQueryType::IS_CANCELED:
5568 intrinsicID =
5569 llvm::Intrinsic::nvvm_clusterlaunchcontrol_query_cancel_is_canceled;
5570 break;
5571 case NVVM::ClusterLaunchControlQueryType::GET_FIRST_CTA_ID_X:
5572 intrinsicID = llvm::Intrinsic::
5573 nvvm_clusterlaunchcontrol_query_cancel_get_first_ctaid_x;
5574 break;
5575 case NVVM::ClusterLaunchControlQueryType::GET_FIRST_CTA_ID_Y:
5576 intrinsicID = llvm::Intrinsic::
5577 nvvm_clusterlaunchcontrol_query_cancel_get_first_ctaid_y;
5578 break;
5579 case NVVM::ClusterLaunchControlQueryType::GET_FIRST_CTA_ID_Z:
5580 intrinsicID = llvm::Intrinsic::
5581 nvvm_clusterlaunchcontrol_query_cancel_get_first_ctaid_z;
5582 break;
5583 }
5584 return {intrinsicID, args};
5585}
5586
5588PermuteOp::getIntrinsicIDAndArgs(Operation &op, LLVM::ModuleTranslation &mt,
5589 llvm::IRBuilderBase &builder) {
5590 auto thisOp = cast<NVVM::PermuteOp>(op);
5591 NVVM::PermuteMode mode = thisOp.getMode();
5592
5593 static constexpr llvm::Intrinsic::ID IDs[] = {
5594 llvm::Intrinsic::nvvm_prmt, llvm::Intrinsic::nvvm_prmt_f4e,
5595 llvm::Intrinsic::nvvm_prmt_b4e, llvm::Intrinsic::nvvm_prmt_rc8,
5596 llvm::Intrinsic::nvvm_prmt_ecl, llvm::Intrinsic::nvvm_prmt_ecr,
5597 llvm::Intrinsic::nvvm_prmt_rc16};
5598
5599 unsigned modeIndex = static_cast<unsigned>(mode);
5601 args.push_back(mt.lookupValue(thisOp.getLo()));
5602
5603 // Only first 3 modes (Default, f4e, b4e) need the hi operand.
5604 if (modeIndex < 3)
5605 args.push_back(mt.lookupValue(thisOp.getHi()));
5606
5607 args.push_back(mt.lookupValue(thisOp.getSelector()));
5608
5609 return {IDs[modeIndex], args};
5610}
5611
5612mlir::NVVM::IDArgPair TensormapReplaceOp::getIntrinsicIDAndArgs(
5613 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
5614 auto thisOp = cast<NVVM::TensormapReplaceOp>(op);
5615
5617 args.push_back(mt.lookupValue(thisOp.getAddr()));
5618 if (thisOp.getOrd())
5619 args.push_back(builder.getInt32(thisOp.getOrd().value()));
5620 if (thisOp.getNewValue())
5621 args.push_back(mt.lookupValue(thisOp.getNewValue()));
5622 if (auto attr = thisOp.getNewValueAttr()) {
5623 auto val =
5625 .Case<TensormapElemtypeAttr, TensormapInterleaveLayoutAttr,
5626 TensormapSwizzleModeAttr, TensormapSwizzleAtomicityAttr,
5627 TensormapFillModeAttr>([](auto attr) {
5628 return static_cast<unsigned>(attr.getValue());
5629 })
5630 .Default([](auto attr) {
5631 llvm_unreachable("Invalid attribute type");
5632 return 0;
5633 });
5634 args.push_back(builder.getInt32(val));
5635 }
5636
5637 static constexpr llvm::Intrinsic::ID IDs[] = {
5638 llvm::Intrinsic::nvvm_tensormap_replace_global_address,
5639 llvm::Intrinsic::nvvm_tensormap_replace_rank,
5640 llvm::Intrinsic::nvvm_tensormap_replace_box_dim,
5641 llvm::Intrinsic::nvvm_tensormap_replace_global_dim,
5642 llvm::Intrinsic::nvvm_tensormap_replace_global_stride,
5643 llvm::Intrinsic::nvvm_tensormap_replace_element_stride,
5644 llvm::Intrinsic::nvvm_tensormap_replace_elemtype,
5645 llvm::Intrinsic::nvvm_tensormap_replace_interleave_layout,
5646 llvm::Intrinsic::nvvm_tensormap_replace_swizzle_mode,
5647 llvm::Intrinsic::nvvm_tensormap_replace_swizzle_atomicity,
5648 llvm::Intrinsic::nvvm_tensormap_replace_fill_mode,
5649 };
5650
5651 unsigned fieldIndex = static_cast<unsigned>(thisOp.getField());
5652
5653 return {IDs[fieldIndex], args};
5654}
5655
5656//===----------------------------------------------------------------------===//
5657// NVVM tcgen05.mma functions
5658//===----------------------------------------------------------------------===//
5659
5661Tcgen05MMAOp::getIntrinsicIDAndArgs(Operation &op, LLVM::ModuleTranslation &mt,
5662 llvm::IRBuilderBase &builder) {
5663
5664 auto thisOp = cast<NVVM::Tcgen05MMAOp>(op);
5666
5667 args.push_back(mt.lookupValue(thisOp.getMatrixD()));
5668
5669 llvm::Value *A = mt.lookupValue(thisOp.getMatrixA());
5670 const bool isATensor = isa<llvm::PointerType>(A->getType());
5671 args.push_back(A);
5672
5673 args.push_back(mt.lookupValue(thisOp.getMatrixB()));
5674 args.push_back(mt.lookupValue(thisOp.getIdesc()));
5675 args.push_back(mt.lookupValue(thisOp.getEnableInputD()));
5676
5677 using EnableAShiftArray = std::array<llvm::Intrinsic::ID, 2>;
5678 using CtaGroupArray = std::array<EnableAShiftArray, 2>;
5679 using IsATensorArray = std::array<CtaGroupArray, 2>;
5680 using HasScaleInputDArray = std::array<IsATensorArray, 2>;
5681 using HasDisableOutputLaneArray = std::array<HasScaleInputDArray, 2>;
5682
5683 // [hasDisableOutputLane][hasScaleInputD][isATensor][CtaGroup][EnableAShift]
5684 static constexpr HasDisableOutputLaneArray tcgen05MMAIDs = {
5685 { // without diable output lane
5686 {{// without scale input D
5687 {{
5688 // shared
5689 {{// cg1
5690 {llvm::Intrinsic::nvvm_tcgen05_mma_shared, notIntrinsic},
5691 // cg2
5692 {llvm::Intrinsic::nvvm_tcgen05_mma_shared, notIntrinsic}}},
5693 {{// tensor
5694 {
5695 // cg1
5696 llvm::Intrinsic::nvvm_tcgen05_mma_tensor,
5697 llvm::Intrinsic::nvvm_tcgen05_mma_tensor_ashift,
5698 },
5699 {
5700 // cg2
5701 llvm::Intrinsic::nvvm_tcgen05_mma_tensor,
5702 llvm::Intrinsic::nvvm_tcgen05_mma_tensor_ashift,
5703 }}},
5704 }},
5705 // with scale input D
5706 {{ // shared
5707 {{// cg1
5708 {llvm::Intrinsic::nvvm_tcgen05_mma_shared_scale_d, notIntrinsic},
5709 // cg2
5710 {llvm::Intrinsic::nvvm_tcgen05_mma_shared_scale_d, notIntrinsic}}},
5711 {{// tensor
5712 {
5713 // cg1
5714 llvm::Intrinsic::nvvm_tcgen05_mma_tensor_scale_d,
5715 llvm::Intrinsic::nvvm_tcgen05_mma_tensor_scale_d_ashift,
5716 },
5717 {
5718 // cg2
5719 llvm::Intrinsic::nvvm_tcgen05_mma_tensor_scale_d,
5720 llvm::Intrinsic::nvvm_tcgen05_mma_tensor_scale_d_ashift,
5721 }}}}}}},
5722 // with disable output lane
5723 {{ // without scale input D
5724 {{ // shared
5725 {{// cg1
5726 {llvm::Intrinsic::nvvm_tcgen05_mma_shared_disable_output_lane_cg1,
5727 notIntrinsic},
5728 // cg2
5729 {llvm::Intrinsic::nvvm_tcgen05_mma_shared_disable_output_lane_cg2,
5730 notIntrinsic}}},
5731 {{// cg1
5732 {
5733 llvm::Intrinsic::
5734 nvvm_tcgen05_mma_tensor_disable_output_lane_cg1,
5735 llvm::Intrinsic::
5736 nvvm_tcgen05_mma_tensor_disable_output_lane_cg1_ashift,
5737 },
5738 // cg2
5739 {
5740 llvm::Intrinsic::
5741 nvvm_tcgen05_mma_tensor_disable_output_lane_cg2,
5742 llvm::Intrinsic::
5743 nvvm_tcgen05_mma_tensor_disable_output_lane_cg2_ashift,
5744 }}}}},
5745 // with scale input D
5746 {{ // shared
5747 {{// cg1
5748 {llvm::Intrinsic::
5749 nvvm_tcgen05_mma_shared_scale_d_disable_output_lane_cg1,
5750 notIntrinsic},
5751 // cg2
5752 {llvm::Intrinsic::
5753 nvvm_tcgen05_mma_shared_scale_d_disable_output_lane_cg2,
5754 notIntrinsic}}},
5755 // tensor
5756 {{// cg1
5757 {llvm::Intrinsic::
5758 nvvm_tcgen05_mma_tensor_scale_d_disable_output_lane_cg1,
5759 llvm::Intrinsic::
5760 nvvm_tcgen05_mma_tensor_scale_d_disable_output_lane_cg1_ashift},
5761 // cg2
5762 {
5763 llvm::Intrinsic::
5764 nvvm_tcgen05_mma_tensor_scale_d_disable_output_lane_cg2,
5765 llvm::Intrinsic::
5766 nvvm_tcgen05_mma_tensor_scale_d_disable_output_lane_cg2_ashift,
5767 }}}}}}}}};
5768
5769 llvm::Value *ScaleInputD = mt.lookupValue(thisOp.getScaleInputD());
5770 bool hasScaleInputD = ScaleInputD != nullptr;
5771
5772 llvm::Value *DisableOutputLane =
5773 mt.lookupValue(thisOp.getDisableOutputLane());
5774 bool hasDisableOutputLane = DisableOutputLane != nullptr;
5775
5776 const unsigned ctaGroup =
5777 static_cast<unsigned>(getNVVMCtaGroupKind(thisOp.getCtaGroup()));
5778
5779 llvm::Intrinsic::ID ID =
5780 tcgen05MMAIDs[hasDisableOutputLane][hasScaleInputD][isATensor]
5781 [ctaGroup - 1][thisOp.getAShift()];
5782
5783 assert(ID != notIntrinsic && "Invalid intrinsic for Tcgen05MMAOp.");
5784
5785 if (hasScaleInputD)
5786 args.push_back(ScaleInputD);
5787
5788 if (hasDisableOutputLane)
5789 args.push_back(DisableOutputLane);
5790
5791 args.push_back(builder.getInt32(static_cast<unsigned>(thisOp.getKind())));
5792
5793 if (!hasDisableOutputLane)
5794 args.push_back(builder.getInt32(ctaGroup));
5795
5796 args.push_back(
5797 builder.getInt32(static_cast<unsigned>(thisOp.getCollectorOp())));
5798
5799 return {ID, args};
5800}
5801
5802static LogicalResult
5803verifyTcgen05MMAOp(bool isATensor, mlir::Value disableOutputLane,
5804 NVVM::CTAGroupKind ctaGroup, bool hasAShift,
5805 NVVM::Tcgen05MMACollectorOp collectorOp, Location loc) {
5806
5807 if (disableOutputLane) {
5808 mlir::VectorType disableOutputLaneType =
5809 cast<mlir::VectorType>(disableOutputLane.getType());
5810 if ((ctaGroup == NVVM::CTAGroupKind::CTA_1 &&
5811 disableOutputLaneType.getNumElements() != 4) ||
5812 (ctaGroup == NVVM::CTAGroupKind::CTA_2 &&
5813 disableOutputLaneType.getNumElements() != 8))
5814 return emitError(loc) << "Disable Output Lane of length "
5815 << disableOutputLaneType.getNumElements()
5816 << " is incompatible with CtaGroupAttr";
5817 }
5818
5819 if (hasAShift && !isATensor)
5820 return emitError(
5821 loc, "A-shift can be applied only when matrix A is in tensor memory");
5822
5823 if (hasAShift == true && (collectorOp == Tcgen05MMACollectorOp::FILL ||
5824 collectorOp == Tcgen05MMACollectorOp::USE))
5825 return emitError(
5826 loc, "Cannot use collector buffer operation fill or use with ashift");
5827
5828 return success();
5829}
5830
5831LogicalResult Tcgen05MMAOp::verify() {
5832 return verifyTcgen05MMAOp(isa<LLVM::LLVMPointerType>(getMatrixA().getType()),
5833 getDisableOutputLane(), getCtaGroup(), getAShift(),
5834 getCollectorOp(), getLoc());
5835}
5836
5837//===----------------------------------------------------------------------===//
5838// NVVM tcgen05.mma.sp functions
5839//===----------------------------------------------------------------------===//
5840
5841mlir::NVVM::IDArgPair Tcgen05MMASparseOp::getIntrinsicIDAndArgs(
5842 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
5843
5844 auto thisOp = cast<NVVM::Tcgen05MMASparseOp>(op);
5846
5847 args.push_back(mt.lookupValue(thisOp.getMatrixD()));
5848
5849 llvm::Value *A = mt.lookupValue(thisOp.getMatrixA());
5850 bool isATensor = isa<llvm::PointerType>(A->getType());
5851 args.push_back(A);
5852
5853 args.push_back(mt.lookupValue(thisOp.getMatrixB()));
5854 args.push_back(mt.lookupValue(thisOp.getIdesc()));
5855 args.push_back(mt.lookupValue(thisOp.getEnableInputD()));
5856 args.push_back(mt.lookupValue(thisOp.getSparseMetadata()));
5857
5858 using EnableAShiftArray = std::array<llvm::Intrinsic::ID, 2>;
5859 using CtaGroupArray = std::array<EnableAShiftArray, 2>;
5860 using IsATensorArray = std::array<CtaGroupArray, 2>;
5861 using HasScaleInputDArray = std::array<IsATensorArray, 2>;
5862 using HasDisableOutputLaneArray = std::array<HasScaleInputDArray, 2>;
5863
5864 // [hasDisableOutputLane][hasScaleInputD][isATensor][CtaGroup][EnableAShift]
5865 static constexpr HasDisableOutputLaneArray tcgen05MMASparseIDs = {
5866 { // without diable output lane
5867 {{// without scale input D
5868 {{
5869 // shared
5870 {{// cg1
5871 {llvm::Intrinsic::nvvm_tcgen05_mma_sp_shared, notIntrinsic},
5872 // cg2
5873 {llvm::Intrinsic::nvvm_tcgen05_mma_sp_shared, notIntrinsic}}},
5874 {{// tensor
5875 {
5876 // cg1
5877 llvm::Intrinsic::nvvm_tcgen05_mma_sp_tensor,
5878 llvm::Intrinsic::nvvm_tcgen05_mma_sp_tensor_ashift,
5879 },
5880 {
5881 // cg2
5882 llvm::Intrinsic::nvvm_tcgen05_mma_sp_tensor,
5883 llvm::Intrinsic::nvvm_tcgen05_mma_sp_tensor_ashift,
5884 }}},
5885 }},
5886 // with scale input D
5887 {{ // shared
5888 {{// cg1
5889 {llvm::Intrinsic::nvvm_tcgen05_mma_sp_shared_scale_d,
5890 notIntrinsic},
5891 // cg2
5892 {llvm::Intrinsic::nvvm_tcgen05_mma_sp_shared_scale_d,
5893 notIntrinsic}}},
5894 {{// tensor
5895 {
5896 // cg1
5897 llvm::Intrinsic::nvvm_tcgen05_mma_sp_tensor_scale_d,
5898 llvm::Intrinsic::nvvm_tcgen05_mma_sp_tensor_scale_d_ashift,
5899 },
5900 {
5901 // cg2
5902 llvm::Intrinsic::nvvm_tcgen05_mma_sp_tensor_scale_d,
5903 llvm::Intrinsic::nvvm_tcgen05_mma_sp_tensor_scale_d_ashift,
5904 }}}}}}},
5905 // with disable output lane
5906 {{ // without scale input D
5907 {{ // shared
5908 {{// cg1
5909 {llvm::Intrinsic::
5910 nvvm_tcgen05_mma_sp_shared_disable_output_lane_cg1,
5911 notIntrinsic},
5912 // cg2
5913 {llvm::Intrinsic::
5914 nvvm_tcgen05_mma_sp_shared_disable_output_lane_cg2,
5915 notIntrinsic}}},
5916 {{// cg1
5917 {
5918 llvm::Intrinsic::
5919 nvvm_tcgen05_mma_sp_tensor_disable_output_lane_cg1,
5920 llvm::Intrinsic::
5921 nvvm_tcgen05_mma_sp_tensor_disable_output_lane_cg1_ashift,
5922 },
5923 // cg2
5924 {
5925 llvm::Intrinsic::
5926 nvvm_tcgen05_mma_sp_tensor_disable_output_lane_cg2,
5927 llvm::Intrinsic::
5928 nvvm_tcgen05_mma_sp_tensor_disable_output_lane_cg2_ashift,
5929 }}}}},
5930 // with scale input D
5931 {{ // shared
5932 {{// cg1
5933 {llvm::Intrinsic::
5934 nvvm_tcgen05_mma_sp_shared_scale_d_disable_output_lane_cg1,
5935 notIntrinsic},
5936 // cg2
5937 {llvm::Intrinsic::
5938 nvvm_tcgen05_mma_sp_shared_scale_d_disable_output_lane_cg2,
5939 notIntrinsic}}},
5940 // tensor
5941 {{// cg1
5942 {llvm::Intrinsic::
5943 nvvm_tcgen05_mma_sp_tensor_scale_d_disable_output_lane_cg1,
5944 llvm::Intrinsic::
5945 nvvm_tcgen05_mma_sp_tensor_scale_d_disable_output_lane_cg1_ashift},
5946 // cg2
5947 {
5948 llvm::Intrinsic::
5949 nvvm_tcgen05_mma_sp_tensor_scale_d_disable_output_lane_cg2,
5950 llvm::Intrinsic::
5951 nvvm_tcgen05_mma_sp_tensor_scale_d_disable_output_lane_cg2_ashift,
5952 }}}}}}}}};
5953
5954 llvm::Value *ScaleInputD = mt.lookupValue(thisOp.getScaleInputD());
5955 bool hasScaleInputD = ScaleInputD != nullptr;
5956
5957 llvm::Value *DisableOutputLane =
5958 mt.lookupValue(thisOp.getDisableOutputLane());
5959 bool hasDisableOutputLane = DisableOutputLane != nullptr;
5960
5961 unsigned ctaGroup =
5962 static_cast<unsigned>(getNVVMCtaGroupKind(thisOp.getCtaGroup()));
5963
5964 llvm::Intrinsic::ID ID =
5965 tcgen05MMASparseIDs[hasDisableOutputLane][hasScaleInputD][isATensor]
5966 [ctaGroup - 1][thisOp.getAShift()];
5967
5968 assert(ID != notIntrinsic && "Invalid intrinsic for Tcgen05MMASparseOp.");
5969
5970 if (hasScaleInputD)
5971 args.push_back(ScaleInputD);
5972
5973 if (hasDisableOutputLane)
5974 args.push_back(DisableOutputLane);
5975
5976 args.push_back(builder.getInt32(static_cast<unsigned>(thisOp.getKind())));
5977
5978 if (!hasDisableOutputLane)
5979 args.push_back(builder.getInt32(ctaGroup));
5980
5981 args.push_back(
5982 builder.getInt32(static_cast<unsigned>(thisOp.getCollectorOp())));
5983
5984 return {ID, args};
5985}
5986
5987LogicalResult Tcgen05MMASparseOp::verify() {
5988 return verifyTcgen05MMAOp(isa<LLVM::LLVMPointerType>(getMatrixA().getType()),
5989 getDisableOutputLane(), getCtaGroup(), getAShift(),
5990 getCollectorOp(), getLoc());
5991}
5992
5993//===----------------------------------------------------------------------===//
5994// NVVM tcgen05.mma.block_scale functions
5995//===----------------------------------------------------------------------===//
5996
5997mlir::NVVM::IDArgPair Tcgen05MMABlockScaleOp::getIntrinsicIDAndArgs(
5998 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
5999
6000 auto thisOp = cast<NVVM::Tcgen05MMABlockScaleOp>(op);
6002
6003 args.push_back(mt.lookupValue(thisOp.getMatrixD()));
6004
6005 llvm::Value *A = mt.lookupValue(thisOp.getMatrixA());
6006 bool isATensor = isa<llvm::PointerType>(A->getType());
6007 args.push_back(A);
6008
6009 args.push_back(mt.lookupValue(thisOp.getMatrixB()));
6010 args.push_back(mt.lookupValue(thisOp.getIdesc()));
6011 args.push_back(mt.lookupValue(thisOp.getEnableInputD()));
6012 args.push_back(mt.lookupValue(thisOp.getScaleA()));
6013 args.push_back(mt.lookupValue(thisOp.getScaleB()));
6014 args.push_back(builder.getInt32(
6015 static_cast<unsigned>(getNVVMCtaGroupKind(thisOp.getCtaGroup()))));
6016 args.push_back(
6017 builder.getInt32(static_cast<unsigned>(thisOp.getCollectorOp())));
6018
6019 auto kind = thisOp.getKind();
6020 auto blockScale = thisOp.getBlockScale();
6021 llvm::Intrinsic::ID ID = [&]() {
6022 if (kind == NVVM::Tcgen05MMAKind::MXF8F6F4) {
6023 if (blockScale == NVVM::Tcgen05MMABlockScale::DEFAULT) {
6024 return isATensor ? llvm::Intrinsic::
6025 nvvm_tcgen05_mma_tensor_mxf8f6f4_block_scale
6026 : llvm::Intrinsic::
6027 nvvm_tcgen05_mma_shared_mxf8f6f4_block_scale;
6028 } else if (blockScale == NVVM::Tcgen05MMABlockScale::BLOCK32) {
6029 return isATensor
6030 ? llvm::Intrinsic::
6031 nvvm_tcgen05_mma_tensor_mxf8f6f4_block_scale_block32
6032 : llvm::Intrinsic::
6033 nvvm_tcgen05_mma_shared_mxf8f6f4_block_scale_block32;
6034 }
6035 } else if (kind == NVVM::Tcgen05MMAKind::MXF4) {
6036 if (blockScale == NVVM::Tcgen05MMABlockScale::DEFAULT) {
6037 return isATensor
6038 ? llvm::Intrinsic::nvvm_tcgen05_mma_tensor_mxf4_block_scale
6039 : llvm::Intrinsic::nvvm_tcgen05_mma_shared_mxf4_block_scale;
6040 } else if (blockScale == NVVM::Tcgen05MMABlockScale::BLOCK32) {
6041 return isATensor ? llvm::Intrinsic::
6042 nvvm_tcgen05_mma_tensor_mxf4_block_scale_block32
6043 : llvm::Intrinsic::
6044 nvvm_tcgen05_mma_shared_mxf4_block_scale_block32;
6045 }
6046 } else if (kind == NVVM::Tcgen05MMAKind::MXF4NVF4) {
6047 if (blockScale == NVVM::Tcgen05MMABlockScale::BLOCK32) {
6048 return isATensor
6049 ? llvm::Intrinsic::
6050 nvvm_tcgen05_mma_tensor_mxf4nvf4_block_scale_block32
6051 : llvm::Intrinsic::
6052 nvvm_tcgen05_mma_shared_mxf4nvf4_block_scale_block32;
6053
6054 } else if (blockScale == NVVM::Tcgen05MMABlockScale::BLOCK16) {
6055 return isATensor
6056 ? llvm::Intrinsic::
6057 nvvm_tcgen05_mma_tensor_mxf4nvf4_block_scale_block16
6058 : llvm::Intrinsic::
6059 nvvm_tcgen05_mma_shared_mxf4nvf4_block_scale_block16;
6060 }
6061 }
6062 llvm_unreachable("Invalid tcgen05.mma.block_scale attributes");
6063 }();
6064
6065 return {ID, args};
6066}
6067
6068static LogicalResult verifyTcgen05MMABlockScaleOp(
6069 NVVM::Tcgen05MMACollectorOp collectorOp, NVVM::Tcgen05MMAKind kind,
6070 NVVM::Tcgen05MMABlockScale blockScale, Location loc) {
6071 if (blockScale == NVVM::Tcgen05MMABlockScale::DEFAULT &&
6072 kind == NVVM::Tcgen05MMAKind::MXF4NVF4)
6073 return emitError(loc, "mxf4nvf4 requires block scale attribute");
6074
6075 if (blockScale == NVVM::Tcgen05MMABlockScale::BLOCK16 &&
6076 kind != NVVM::Tcgen05MMAKind::MXF4NVF4)
6077 return emitError(loc,
6078 llvm::formatv("{} kind does not support block16 attribute",
6079 stringifyEnum(kind)));
6080
6081 return success();
6082}
6083
6084LogicalResult Tcgen05MMABlockScaleOp::verify() {
6085 return verifyTcgen05MMABlockScaleOp(getCollectorOp(), getKind(),
6086 getBlockScale(), getLoc());
6087}
6088
6089//===----------------------------------------------------------------------===//
6090// NVVM tcgen05.mma.sp.block_scale functions
6091//===----------------------------------------------------------------------===//
6092
6093mlir::NVVM::IDArgPair Tcgen05MMASparseBlockScaleOp::getIntrinsicIDAndArgs(
6094 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
6095
6096 auto thisOp = cast<NVVM::Tcgen05MMASparseBlockScaleOp>(op);
6098
6099 args.push_back(mt.lookupValue(thisOp.getMatrixD()));
6100
6101 llvm::Value *A = mt.lookupValue(thisOp.getMatrixA());
6102 bool isATensor = isa<llvm::PointerType>(A->getType());
6103 args.push_back(A);
6104
6105 args.push_back(mt.lookupValue(thisOp.getMatrixB()));
6106 args.push_back(mt.lookupValue(thisOp.getIdesc()));
6107 args.push_back(mt.lookupValue(thisOp.getEnableInputD()));
6108 args.push_back(mt.lookupValue(thisOp.getSparseMetadata()));
6109 args.push_back(mt.lookupValue(thisOp.getScaleA()));
6110 args.push_back(mt.lookupValue(thisOp.getScaleB()));
6111 args.push_back(builder.getInt32(
6112 static_cast<unsigned>(getNVVMCtaGroupKind(thisOp.getCtaGroup()))));
6113 args.push_back(
6114 builder.getInt32(static_cast<unsigned>(thisOp.getCollectorOp())));
6115
6116 auto kind = thisOp.getKind();
6117 auto blockScale = thisOp.getBlockScale();
6118 llvm::Intrinsic::ID ID = [&]() {
6119 if (kind == NVVM::Tcgen05MMAKind::MXF8F6F4) {
6120 if (blockScale == NVVM::Tcgen05MMABlockScale::DEFAULT) {
6121 return isATensor ? llvm::Intrinsic::
6122 nvvm_tcgen05_mma_sp_tensor_mxf8f6f4_block_scale
6123 : llvm::Intrinsic::
6124 nvvm_tcgen05_mma_sp_shared_mxf8f6f4_block_scale;
6125 } else if (blockScale == NVVM::Tcgen05MMABlockScale::BLOCK32) {
6126 return isATensor
6127 ? llvm::Intrinsic::
6128 nvvm_tcgen05_mma_sp_tensor_mxf8f6f4_block_scale_block32
6129 : llvm::Intrinsic::
6130 nvvm_tcgen05_mma_sp_shared_mxf8f6f4_block_scale_block32;
6131 }
6132 } else if (kind == NVVM::Tcgen05MMAKind::MXF4) {
6133 if (blockScale == NVVM::Tcgen05MMABlockScale::DEFAULT) {
6134 return isATensor ? llvm::Intrinsic::
6135 nvvm_tcgen05_mma_sp_tensor_mxf4_block_scale
6136 : llvm::Intrinsic::
6137 nvvm_tcgen05_mma_sp_shared_mxf4_block_scale;
6138 } else if (blockScale == NVVM::Tcgen05MMABlockScale::BLOCK32) {
6139 return isATensor
6140 ? llvm::Intrinsic::
6141 nvvm_tcgen05_mma_sp_tensor_mxf4_block_scale_block32
6142 : llvm::Intrinsic::
6143 nvvm_tcgen05_mma_sp_shared_mxf4_block_scale_block32;
6144 }
6145 } else if (kind == NVVM::Tcgen05MMAKind::MXF4NVF4) {
6146 if (blockScale == NVVM::Tcgen05MMABlockScale::BLOCK32) {
6147 return isATensor
6148 ? llvm::Intrinsic::
6149 nvvm_tcgen05_mma_sp_tensor_mxf4nvf4_block_scale_block32
6150 : llvm::Intrinsic::
6151 nvvm_tcgen05_mma_sp_shared_mxf4nvf4_block_scale_block32;
6152
6153 } else if (blockScale == NVVM::Tcgen05MMABlockScale::BLOCK16) {
6154 return isATensor
6155 ? llvm::Intrinsic::
6156 nvvm_tcgen05_mma_sp_tensor_mxf4nvf4_block_scale_block16
6157 : llvm::Intrinsic::
6158 nvvm_tcgen05_mma_sp_shared_mxf4nvf4_block_scale_block16;
6159 }
6160 }
6161 llvm_unreachable("Invalid tcgen05.mma.sp.block_scale attributes");
6162 }();
6163
6164 return {ID, args};
6165}
6166
6167LogicalResult Tcgen05MMASparseBlockScaleOp::verify() {
6168 return verifyTcgen05MMABlockScaleOp(getCollectorOp(), getKind(),
6169 getBlockScale(), getLoc());
6170}
6171
6172//===----------------------------------------------------------------------===//
6173// NVVM tcgen05.mma.ws functions
6174//===----------------------------------------------------------------------===//
6175
6176mlir::NVVM::IDArgPair Tcgen05MMAWsOp::getIntrinsicIDAndArgs(
6177 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
6178
6179 auto thisOp = cast<NVVM::Tcgen05MMAWsOp>(op);
6181
6182 args.push_back(mt.lookupValue(thisOp.getMatrixD()));
6183
6184 llvm::Value *A = mt.lookupValue(thisOp.getMatrixA());
6185 bool isATensor = isa<llvm::PointerType>(A->getType());
6186 args.push_back(A);
6187
6188 args.push_back(mt.lookupValue(thisOp.getMatrixB()));
6189 args.push_back(mt.lookupValue(thisOp.getIdesc()));
6190 args.push_back(mt.lookupValue(thisOp.getEnableInputD()));
6191
6192 mlir::Value ZeroColMask = thisOp.getZeroColMask();
6193 llvm::Intrinsic::ID ID = notIntrinsic;
6194 if (ZeroColMask) {
6195 args.push_back(mt.lookupValue(ZeroColMask));
6196 ID = isATensor ? llvm::Intrinsic::nvvm_tcgen05_mma_ws_tensor_zero_col_mask
6197 : llvm::Intrinsic::nvvm_tcgen05_mma_ws_shared_zero_col_mask;
6198 } else
6199 ID = isATensor ? llvm::Intrinsic::nvvm_tcgen05_mma_ws_tensor
6200 : llvm::Intrinsic::nvvm_tcgen05_mma_ws_shared;
6201
6202 args.push_back(builder.getInt32(static_cast<unsigned>(thisOp.getKind())));
6203 args.push_back(
6204 builder.getInt32(static_cast<unsigned>(thisOp.getCollectorBBuffer())));
6205 args.push_back(
6206 builder.getInt32(static_cast<unsigned>(thisOp.getCollectorOp())));
6207
6208 return {ID, args};
6209}
6210
6211//===----------------------------------------------------------------------===//
6212// NVVM tcgen05.mma.ws.sp functions
6213//===----------------------------------------------------------------------===//
6214
6215mlir::NVVM::IDArgPair Tcgen05MMAWsSparseOp::getIntrinsicIDAndArgs(
6216 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
6217
6218 auto thisOp = cast<NVVM::Tcgen05MMAWsSparseOp>(op);
6220
6221 args.push_back(mt.lookupValue(thisOp.getMatrixD()));
6222
6223 llvm::Value *A = mt.lookupValue(thisOp.getMatrixA());
6224 bool isATensor = isa<llvm::PointerType>(A->getType());
6225 args.push_back(A);
6226
6227 args.push_back(mt.lookupValue(thisOp.getMatrixB()));
6228 args.push_back(mt.lookupValue(thisOp.getIdesc()));
6229 args.push_back(mt.lookupValue(thisOp.getEnableInputD()));
6230 args.push_back(mt.lookupValue(thisOp.getSparseMetadata()));
6231
6232 mlir::Value ZeroColMask = thisOp.getZeroColMask();
6233 llvm::Intrinsic::ID ID = notIntrinsic;
6234 if (ZeroColMask) {
6235 args.push_back(mt.lookupValue(ZeroColMask));
6236 ID = isATensor
6237 ? llvm::Intrinsic::nvvm_tcgen05_mma_ws_sp_tensor_zero_col_mask
6238 : llvm::Intrinsic::nvvm_tcgen05_mma_ws_sp_shared_zero_col_mask;
6239 } else
6240 ID = isATensor ? llvm::Intrinsic::nvvm_tcgen05_mma_ws_sp_tensor
6241 : llvm::Intrinsic::nvvm_tcgen05_mma_ws_sp_shared;
6242
6243 args.push_back(builder.getInt32(static_cast<unsigned>(thisOp.getKind())));
6244 args.push_back(
6245 builder.getInt32(static_cast<unsigned>(thisOp.getCollectorBBuffer())));
6246 args.push_back(
6247 builder.getInt32(static_cast<unsigned>(thisOp.getCollectorOp())));
6248
6249 return {ID, args};
6250}
6251
6252//===----------------------------------------------------------------------===//
6253// NVVM tcgen05.ld.red functions
6254//===----------------------------------------------------------------------===//
6255
6256#define TCGEN05LDRED(SHAPE, NUM, TYPE) \
6257 llvm::Intrinsic::nvvm_tcgen05_ld_red_##SHAPE##_##NUM##_##TYPE
6258
6259mlir::NVVM::IDArgPair NVVM::Tcgen05LdRedOp::getIntrinsicIDAndArgs(
6260 Operation &op, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder) {
6261 auto thisOp = cast<NVVM::Tcgen05LdRedOp>(op);
6263
6264 mlir::VectorType VecResTy =
6265 cast<mlir::VectorType>(thisOp.getData().getType());
6266 unsigned Num = VecResTy.getNumElements();
6267 bool IsFloat = thisOp.getRedVal().getType().isF32();
6268
6269 llvm::Intrinsic::ID Shape32x32b[][2] = {
6271 {TCGEN05LDRED(32x32b, x2, i32), TCGEN05LDRED(32x32b, x2, f32)},
6272 {TCGEN05LDRED(32x32b, x4, i32), TCGEN05LDRED(32x32b, x4, f32)},
6273 {TCGEN05LDRED(32x32b, x8, i32), TCGEN05LDRED(32x32b, x8, f32)},
6274 {TCGEN05LDRED(32x32b, x16, i32), TCGEN05LDRED(32x32b, x16, f32)},
6275 {TCGEN05LDRED(32x32b, x32, i32), TCGEN05LDRED(32x32b, x32, f32)},
6276 {TCGEN05LDRED(32x32b, x64, i32), TCGEN05LDRED(32x32b, x64, f32)},
6277 {TCGEN05LDRED(32x32b, x128, i32), TCGEN05LDRED(32x32b, x128, f32)},
6278 };
6279
6280 llvm::Intrinsic::ID Shape16x32bx2[][2] = {
6282 {TCGEN05LDRED(16x32bx2, x2, i32), TCGEN05LDRED(16x32bx2, x2, f32)},
6283 {TCGEN05LDRED(16x32bx2, x4, i32), TCGEN05LDRED(16x32bx2, x4, f32)},
6284 {TCGEN05LDRED(16x32bx2, x8, i32), TCGEN05LDRED(16x32bx2, x8, f32)},
6285 {TCGEN05LDRED(16x32bx2, x16, i32), TCGEN05LDRED(16x32bx2, x16, f32)},
6286 {TCGEN05LDRED(16x32bx2, x32, i32), TCGEN05LDRED(16x32bx2, x32, f32)},
6287 {TCGEN05LDRED(16x32bx2, x64, i32), TCGEN05LDRED(16x32bx2, x64, f32)},
6288 {TCGEN05LDRED(16x32bx2, x128, i32), TCGEN05LDRED(16x32bx2, x128, f32)},
6289 };
6290
6291 NVVM::Tcgen05LdStShape shape = thisOp.getShape();
6292 unsigned ID = [&]() {
6293 // `num` contains the length of vector and log2 of `num` returns the index
6294 // into the shape array
6295 unsigned idx = std::log2(Num);
6296 switch (shape) {
6297 case NVVM::Tcgen05LdStShape::SHAPE_32X32B:
6298 return Shape32x32b[idx][IsFloat];
6299 case NVVM::Tcgen05LdStShape::SHAPE_16X32BX2:
6300 return Shape16x32bx2[idx][IsFloat];
6301 default:
6302 llvm_unreachable("unhandled tcgen05.ld lowering");
6303 }
6304 }();
6305
6306 args.push_back(mt.lookupValue(thisOp.getAddr()));
6307
6308 if (shape == NVVM::Tcgen05LdStShape::SHAPE_16X32BX2)
6309 args.push_back(mt.lookupValue(thisOp.getOffset()));
6310
6311 args.push_back(
6312 builder.getInt32(thisOp.getOp() == NVVM::ReductionKind::MIN ? 0 : 1));
6313
6314 if (IsFloat) {
6315 args.push_back(builder.getInt1(static_cast<unsigned>(thisOp.getAbs())));
6316 args.push_back(builder.getInt1(static_cast<unsigned>(thisOp.getNan())));
6317 }
6318 return {ID, args};
6319}
6320
6321LogicalResult Tcgen05LdRedOp::verify() {
6322 VectorType data = cast<VectorType>(getData().getType());
6323 Type redVal = getRedVal().getType();
6324
6325 if (data.getElementType() != redVal)
6326 return emitError(
6327 "type of reduction value and element type of vector data should match");
6328
6329 if (getOp() != NVVM::ReductionKind::MIN &&
6330 getOp() != NVVM::ReductionKind::MAX)
6331 return emitError("only min and max reduction kinds are supported");
6332
6333 if (redVal.isInteger() && (getAbs() || getNan())) {
6334 return emitError("abs or nan is only applicable for f32 type");
6335 }
6336 return success();
6337}
6338
6339//===----------------------------------------------------------------------===//
6340// NVVMDialect initialization, type parsing, and registration.
6341//===----------------------------------------------------------------------===//
6342
6343namespace {
6344struct NVVMInlinerInterface final : DialectInlinerInterface {
6345 using DialectInlinerInterface::DialectInlinerInterface;
6346 bool isLegalToInline(Operation *, Region *, bool, IRMapping &) const final {
6347 return true;
6348 }
6349};
6350} // namespace
6351
6352// TODO: This should be the llvm.nvvm dialect once this is supported.
6353void NVVMDialect::initialize() {
6354 addOperations<
6355#define GET_OP_LIST
6356#include "mlir/Dialect/LLVMIR/NVVMOps.cpp.inc"
6357 >();
6358 addAttributes<
6359#define GET_ATTRDEF_LIST
6360#include "mlir/Dialect/LLVMIR/NVVMOpsAttributes.cpp.inc"
6361 >();
6362
6363 // Support unknown operations because not all NVVM operations are
6364 // registered.
6365 allowUnknownOperations();
6366 addInterfaces<NVVMInlinerInterface>();
6367 declarePromisedInterface<ConvertToLLVMPatternInterface, NVVMDialect>();
6368 declarePromisedInterface<gpu::TargetAttrInterface, NVVMTargetAttr>();
6369}
6370
6371LogicalResult NVVMDialect::verifyOperationAttribute(Operation *op,
6372 NamedAttribute attr) {
6373 StringAttr attrName = attr.getName();
6374 // Kernel function attribute should be attached to functions.
6375 if (attrName == NVVMDialect::getKernelFuncAttrName()) {
6376 if (!isa<LLVM::LLVMFuncOp>(op)) {
6377 return op->emitError() << "'" << NVVMDialect::getKernelFuncAttrName()
6378 << "' attribute attached to unexpected op";
6379 }
6380 }
6381 // If maxntid / reqntid / cluster_dim exist, it must be an array with max 3
6382 // dim
6383 if (attrName == NVVMDialect::getMaxntidAttrName() ||
6384 attrName == NVVMDialect::getReqntidAttrName() ||
6385 attrName == NVVMDialect::getClusterDimAttrName()) {
6386 auto values = llvm::dyn_cast<DenseI32ArrayAttr>(attr.getValue());
6387 if (!values || values.empty() || values.size() > 3) {
6388 return op->emitError()
6389 << "'" << attrName
6390 << "' attribute must be integer array with maximum 3 index";
6391 }
6392 }
6393 // If minctasm / maxnreg / cluster_max_blocks exist, it must be an integer
6394 // attribute
6395 if (attrName == NVVMDialect::getMinctasmAttrName() ||
6396 attrName == NVVMDialect::getMaxnregAttrName() ||
6397 attrName == NVVMDialect::getClusterMaxBlocksAttrName()) {
6398 if (!llvm::dyn_cast<IntegerAttr>(attr.getValue())) {
6399 return op->emitError()
6400 << "'" << attrName << "' attribute must be integer constant";
6401 }
6402 }
6403 // blocksareclusters must be used along with reqntid and cluster_dim
6404 if (attrName == NVVMDialect::getBlocksAreClustersAttrName()) {
6405 if (!op->hasAttr(NVVMDialect::getReqntidAttrName()) ||
6406 !op->hasAttr(NVVMDialect::getClusterDimAttrName())) {
6407 return op->emitError()
6408 << "'" << attrName << "' attribute must be used along with " << "'"
6409 << NVVMDialect::getReqntidAttrName() << "' and " << "'"
6410 << NVVMDialect::getClusterDimAttrName() << "'";
6411 }
6412 }
6413
6414 return success();
6415}
6416
6417LogicalResult NVVMDialect::verifyRegionArgAttribute(Operation *op,
6418 unsigned regionIndex,
6419 unsigned argIndex,
6420 NamedAttribute argAttr) {
6421 auto funcOp = dyn_cast<FunctionOpInterface>(op);
6422 if (!funcOp)
6423 return success();
6424
6425 bool isKernel = op->hasAttr(NVVMDialect::getKernelFuncAttrName());
6426 StringAttr attrName = argAttr.getName();
6427 if (attrName == NVVM::NVVMDialect::getGridConstantAttrName()) {
6428 if (!isKernel) {
6429 return op->emitError()
6430 << "'" << attrName
6431 << "' attribute must be present only on kernel arguments";
6432 }
6433 if (!isa<UnitAttr>(argAttr.getValue()))
6434 return op->emitError() << "'" << attrName << "' must be a unit attribute";
6435 if (!funcOp.getArgAttr(argIndex, LLVM::LLVMDialect::getByValAttrName())) {
6436 return op->emitError()
6437 << "'" << attrName
6438 << "' attribute requires the argument to also have attribute '"
6439 << LLVM::LLVMDialect::getByValAttrName() << "'";
6440 }
6441 }
6442
6443 return success();
6444}
6445
6446//===----------------------------------------------------------------------===//
6447// NVVM Address Space Attr
6448//===----------------------------------------------------------------------===//
6449
6450unsigned NVVMMemorySpaceAttr::getAddressSpace() const {
6451 return static_cast<unsigned>(getValue());
6452}
6453
6454bool NVVMMemorySpaceAttr::isValidLoad(
6455 Type type, ptr::AtomicOrdering ordering, std::optional<int64_t> alignment,
6456 const ::mlir::DataLayout *dataLayout,
6458 return LLVM::detail::isValidLoadStoreImpl(type, ordering, alignment,
6459 dataLayout, emitError);
6460}
6461
6462bool NVVMMemorySpaceAttr::isValidStore(
6463 Type type, ptr::AtomicOrdering ordering, std::optional<int64_t> alignment,
6464 const ::mlir::DataLayout *dataLayout,
6466 return LLVM::detail::isValidLoadStoreImpl(type, ordering, alignment,
6467 dataLayout, emitError);
6468}
6469
6470bool NVVMMemorySpaceAttr::isValidAtomicOp(
6471 ptr::AtomicBinOp op, Type type, ptr::AtomicOrdering ordering,
6472 std::optional<int64_t> alignment, const ::mlir::DataLayout *dataLayout,
6474 // TODO: update this method once `ptr.atomic_rmw` is implemented.
6475 assert(false && "unimplemented, see TODO in the source.");
6476 return false;
6477}
6478
6479bool NVVMMemorySpaceAttr::isValidAtomicXchg(
6480 Type type, ptr::AtomicOrdering successOrdering,
6481 ptr::AtomicOrdering failureOrdering, std::optional<int64_t> alignment,
6482 const ::mlir::DataLayout *dataLayout,
6484 // TODO: update this method once `ptr.atomic_cmpxchg` is implemented.
6485 assert(false && "unimplemented, see TODO in the source.");
6486 return false;
6487}
6488
6489bool NVVMMemorySpaceAttr::isValidAddrSpaceCast(
6490 Type tgt, Type src, function_ref<InFlightDiagnostic()> emitError) const {
6491 // TODO: update this method once the `ptr.addrspace_cast` op is added to the
6492 // dialect.
6493 assert(false && "unimplemented, see TODO in the source.");
6494 return false;
6495}
6496
6497bool NVVMMemorySpaceAttr::isValidPtrIntCast(
6498 Type intLikeTy, Type ptrLikeTy,
6500 // TODO: update this method once the int-cast ops are added to the `ptr`
6501 // dialect.
6502 assert(false && "unimplemented, see TODO in the source.");
6503 return false;
6504}
6505
6506//===----------------------------------------------------------------------===//
6507// NVVM target attribute.
6508//===----------------------------------------------------------------------===//
6509LogicalResult
6510NVVMTargetAttr::verify(function_ref<InFlightDiagnostic()> emitError,
6511 int optLevel, StringRef triple, StringRef chip,
6512 StringRef features, DictionaryAttr flags,
6513 ArrayAttr files, bool verifyTarget) {
6514 if (optLevel < 0 || optLevel > 3) {
6515 emitError() << "The optimization level must be a number between 0 and 3.";
6516 return failure();
6517 }
6518 if (triple.empty()) {
6519 emitError() << "The target triple cannot be empty.";
6520 return failure();
6521 }
6522 if (chip.empty()) {
6523 emitError() << "The target chip cannot be empty.";
6524 return failure();
6525 }
6526 if (files && !llvm::all_of(files, [](::mlir::Attribute attr) {
6527 return mlir::isa_and_nonnull<StringAttr>(attr);
6528 })) {
6529 emitError() << "All the elements in the `link` array must be strings.";
6530 return failure();
6531 }
6532 return success();
6533}
6534
6535LogicalResult NVVMTargetAttr::verifyTarget(Operation *gpuModule) {
6536 if (!getVerifyTarget())
6537 return success();
6538
6539 auto gpuModuleOp = llvm::dyn_cast<gpu::GPUModuleOp>(gpuModule);
6540 if (!gpuModuleOp) {
6541 return emitError(gpuModule->getLoc(),
6542 "NVVM target attribute must be attached to a GPU module");
6543 }
6544
6545 const unsigned targetFullSmVersion =
6547 if (!NVVMCheckSMVersion::isMinimumSMVersion(targetFullSmVersion)) {
6548 return emitError(gpuModule->getLoc(),
6549 "Minimum NVVM target SM version is sm_20");
6550 }
6551
6552 if (gpuModuleOp
6553 ->walk([&](Operation *op) {
6554 if (auto reqOp = llvm::dyn_cast<NVVM::RequiresSMInterface>(op)) {
6555 const NVVMCheckSMVersion requirement =
6556 reqOp.getRequiredMinSMVersion();
6557 if (!requirement.isCompatibleWith(targetFullSmVersion)) {
6558 op->emitOpError() << "is not supported on " << getChip();
6559 return WalkResult::interrupt();
6560 }
6561 }
6562 return WalkResult::advance();
6563 })
6564 .wasInterrupted())
6565 return failure();
6566
6567 return success();
6568}
6569
6570#define GET_OP_CLASSES
6571#include "mlir/Dialect/LLVMIR/NVVMOps.cpp.inc"
6572
6573#define GET_ATTRDEF_CLASSES
6574#include "mlir/Dialect/LLVMIR/NVVMOpsAttributes.cpp.inc"
for(Operation *op :ops)
return success()
p<< " : "<< getMemRefType()<< ", "<< getType();}static LogicalResult verifyVectorMemoryOp(Operation *op, MemRefType memrefType, VectorType vectorType) { if(memrefType.getElementType() !=vectorType.getElementType()) return op-> emitOpError("requires memref and vector types of the same elemental type")
Given a list of lists of parsed operands, populates uniqueOperands with unique operands.
static bool isLegalToInline(InlinerInterface &interface, Region *src, Region *insertRegion, bool shouldCloneInlinedRegion, IRMapping &valueMapping)
Utility to check that all of the operations within 'src' can be inlined.
ArrayAttr()
b getContext())
#define GET_TCGEN05_CP_ID(shape_mc, src_fmt, is_2cta)
static LogicalResult verifyTMALoadParams(size_t tensorDims, size_t numIm2colOff, TMALoadMode mode, Location loc)
static LogicalResult verifyTcgen05MMAOp(bool isATensor, mlir::Value disableOutputLane, NVVM::CTAGroupKind ctaGroup, bool hasAShift, NVVM::Tcgen05MMACollectorOp collectorOp, Location loc)
#define _none
static bool isPtrInAddrSpace(mlir::Value ptr, NVVMMemorySpace targetAS)
static bool isCompatibleReturnTypesOptionalResult(TypeRange inferred, TypeRange actual)
For ops with optional results, allow the user to omit the result even when inference would produce on...
static bool isPtrInSharedCTASpace(mlir::Value ptr)
static LogicalResult isAllowedSizeN(int sizeN, NVVM::WGMMATypes typeA)
static llvm::nvvm::CTAGroupKind getNVVMCtaGroupKind(NVVM::CTAGroupKind ctaGroup)
static void addInferredMultiplicandTypes(MLIRContext *ctx, OperationState &result, ValueRange operandA, ValueRange operandB, std::optional< std::array< MMATypes, 2 > > multiplicandPtxTypes)
#define GET_CVT_F2TF32_ID(rnd, relu, sf)
static void addBlockScaleProperties(OpBuilder &builder, OperationState &result, ArrayRef< int64_t > shape, ScaleVecSize scaleVecSize, BlockScaleFormat blockScaleFormat, MMABlockScaleKind kind)
#define GET_F32x2_TO_F8X2_US_ID(rnd, has_satf)
static llvm::Value * getParamCastedAddr(llvm::Value *addr, llvm::IRBuilderBase &builder)
static LogicalResult verifyAddSubFOp(OpType op)
static LogicalResult verifyTcgen05MMABlockScaleOp(NVVM::Tcgen05MMACollectorOp collectorOp, NVVM::Tcgen05MMAKind kind, NVVM::Tcgen05MMABlockScale blockScale, Location loc)
static llvm::Value * packValInto64Bits(llvm::IRBuilderBase &builder, llvm::Value *result, llvm::Value *field, unsigned sizeInBits, unsigned start)
Packs the given field into the result.
static void printOperandList(OpAsmPrinter &p, StringRef name, ArrayRef< Value > operands)
#define GET_F32x2_TO_F6x2_ID(type, has_relu)
static llvm::Value * getAsPackedI32(llvm::Value *arg, llvm::IRBuilderBase &builder)
#define GET_F16x2_TO_F8X2_ID(type, has_relu)
static LogicalResult verifyMBarrierArriveLikeOp(Operation *op, Value addr, NVVM::MemScopeKind scope, Value retVal=nullptr)
static llvm::Value * castPtrToAddrSpace(llvm::IRBuilderBase &builder, llvm::Value *ptr, NVVMMemorySpace targetAS)
static LogicalResult isAllowedWGMMADataType(NVVM::WGMMATypes typeD, NVVM::WGMMATypes typeA, NVVM::WGMMATypes typeB)
static llvm::Intrinsic::ID getBarrierReductionIntrinsic(bool aligned, NVVM::BarrierReduction kind)
Maps the (aligned, kind) pair to the @llvm.nvvm.barrier.cta.red.
static void inferAndSetMultiplicandTypes(MLIRContext *ctx, NamedAttrList &attrs, const SmallVectorImpl< Type > &operandTypes)
static LogicalResult parseMmaOperand(OpAsmParser &parser, StringRef operandName, SmallVectorImpl< OpAsmParser::UnresolvedOperand > &regs)
static std::pair< mlir::Type, unsigned > inferMMATypeFromMNK(NVVM::MMATypes type, NVVM::MMAFrag frag, int m, int n, int k, MLIRContext *context)
static bool isInt8PtxType(MMATypes type)
#define TCGEN05LDRED(SHAPE, NUM, TYPE)
static bool isInt4PtxType(MMATypes type)
static bool isIntegerPtxType(MMATypes type)
#define GET_F32x2_TO_F8X2_S_ID(type, has_relu)
static MMATypes inferPtxTypeFromResult(OpTy op)
static LogicalResult verifyConstantRangeAttr(Operation *op, std::optional< LLVM::ConstantRangeAttr > rangeAttr)
Verify the range attribute satisfies LLVM ConstantRange constructor requirements for NVVM SpecialRang...
static LogicalResult parseMmaTypeSignature(OpAsmParser &parser, SmallVectorImpl< Type > &operandTypes)
static FailureOr< int > getAllowedSizeK(NVVM::WGMMATypes typeA)
static bool isPtrInSharedClusterSpace(mlir::Value ptr)
#define GET_CP_ASYNC_ID(mod, size, has_cpsize)
static unsigned isValidVectorLength(NVVM::Tcgen05LdStShape shape, unsigned vecLen)
#define GET_TCGEN05_COMMIT_ID(cta_group, is_shared, has_mc)
static LogicalResult verifyConvertF32x2ToFP16x2Op(Twine dstType, FPRoundingMode rnd, bool hasRandomBits, Operation *op)
static void nvvmInferResultRanges(Operation *op, Value result, ArrayRef<::mlir::ConstantIntRanges > argRanges, SetIntRangeFn setResultRanges)
Infer the result ranges for the NVVM SpecialRangeableRegisterOp that might have ConstantRangeAttr.
static LogicalResult cpAsyncBulkTensorCommonVerifier(size_t tensorDims, bool isIm2Col, size_t numIm2ColOffsets, Location loc)
static bool isPtrInGenericSpace(mlir::Value ptr)
static void processOperandFragments(Op &op, std::array< MMAOperandFragment, 3 > &frags, SmallVectorImpl< Type > &regTypes, SmallVectorImpl< StringRef > &ignoreAttrNames)
static llvm::Intrinsic::ID getBarrierSyncIntrinsic(bool aligned, bool hasCount)
Maps the (aligned, hasCount) pair to the @llvm.nvvm.barrier.cta.sync.
static constexpr unsigned notIntrinsic
static LogicalResult inferMBarrierArriveResultTypes(MLIRContext *context, Value addr, SmallVectorImpl< Type > &inferredReturnTypes)
Only shared_cluster (ptr<7>) produces zero results; all other address spaces (including generic) retu...
static ArrayRef< int64_t > getShape(Type type)
Returns the shape of the given type.
Definition Traits.cpp:117
@ OptionalSquare
Square brackets supporting zero or more ops, or nothing.
virtual Builder & getBuilder() const =0
Return a builder which provides useful access to MLIRContext, global objects like types and attribute...
virtual ParseResult parseCommaSeparatedList(Delimiter delimiter, function_ref< ParseResult()> parseElementFn, StringRef contextMessage=StringRef())=0
Parse a list of comma-separated items with an optional delimiter.
virtual ParseResult parseOptionalAttrDict(NamedAttrList &result)=0
Parse a named dictionary into 'result' if it is present.
MLIRContext * getContext() const
virtual ParseResult parseRParen()=0
Parse a ) token.
virtual InFlightDiagnostic emitError(SMLoc loc, const Twine &message={})=0
Emit a diagnostic at the specified location and return failure.
virtual SMLoc getCurrentLocation()=0
Get the location of the next token and store it into the argument.
virtual ParseResult parseColon()=0
Parse a : token.
virtual SMLoc getNameLoc() const =0
Return the location of the original name token.
virtual ParseResult parseArrow()=0
Parse a '->' token.
virtual ParseResult parseLParen()=0
Parse a ( token.
virtual ParseResult parseType(Type &result)=0
Parse a type.
virtual ParseResult parseArrowTypeList(SmallVectorImpl< Type > &result)=0
Parse an arrow followed by a type list.
ParseResult parseTypeList(SmallVectorImpl< Type > &result)
Parse a type list.
ParseResult parseKeyword(StringRef keyword)
Parse a given keyword.
void printArrowTypeList(TypeRange &&types)
This class is a general helper class for creating context-global objects like types,...
Definition Builders.h:51
IntegerType getI16Type()
Definition Builders.cpp:65
UnitAttr getUnitAttr()
Definition Builders.cpp:102
DenseI32ArrayAttr getDenseI32ArrayAttr(ArrayRef< int32_t > values)
Definition Builders.cpp:167
IntegerType getI32Type()
Definition Builders.cpp:67
IntegerType getIntegerType(unsigned width)
Definition Builders.cpp:71
MLIRContext * getContext() const
Definition Builders.h:56
Attr getAttr(Args &&...args)
Get or construct an instance of the attribute Attr with provided arguments.
Definition Builders.h:100
This class represents a diagnostic that is inflight and set to be reported.
static IntegerValueRange getMaxRange(Value value)
Create a maximal range ([0, uint_max(t)] / [int_min(t), int_max(t)]) range that is used to mark the v...
Implementation class for module translation.
llvm::Value * lookupValue(Value value) const
Finds an LLVM IR value corresponding to the given MLIR value.
void mapValue(Value mlir, llvm::Value *llvm)
Stores the mapping between an MLIR value and its LLVM IR counterpart.
llvm::LLVMContext & getLLVMContext() const
Returns the LLVM context in which the IR is being constructed.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
NamedAttrList is array of NamedAttributes that tracks whether it is sorted and does some basic work t...
std::optional< NamedAttribute > getNamed(StringRef name) const
Return the specified named attribute if present, std::nullopt otherwise.
Attribute get(StringAttr name) const
Return the specified attribute if present, null otherwise.
Attribute set(StringAttr name, Attribute value)
If the an attribute exists with the specified name, change it to the new value.
NamedAttribute represents a combination of a name and an Attribute value.
Definition Attributes.h:164
StringAttr getName() const
Return the name of the attribute.
Attribute getValue() const
Return the value of the attribute.
Definition Attributes.h:179
The OpAsmParser has methods for interacting with the asm parser: parsing things from it,...
ParseResult resolveOperands(Operands &&operands, Type type, SmallVectorImpl< Value > &result)
Resolve a list of operands to SSA values, emitting an error on failure, or appending the results to t...
virtual ParseResult parseOperandList(SmallVectorImpl< UnresolvedOperand > &result, Delimiter delimiter=Delimiter::None, bool allowResultNumber=true, int requiredOperandCount=-1)=0
Parse zero or more SSA comma-separated operand references with a specified surrounding delimiter,...
This is a pure-virtual base class that exposes the asmprinter hooks necessary to implement a custom p...
void printOperands(const ContainerType &container)
Print a comma separated list of operands.
virtual void printOptionalAttrDict(ArrayRef< NamedAttribute > attrs, ArrayRef< StringRef > elidedAttrs={})=0
If the specified operation has attributes, print out an attribute dictionary with their values.
This class helps build Operations.
Definition Builders.h:209
This provides public APIs that all operations should have.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
AttrClass getAttrOfType(StringAttr name)
Definition Operation.h:575
bool hasAttr(StringAttr name)
Return true if the operation has an attribute with the provided name, false otherwise.
Definition Operation.h:585
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
InFlightDiagnostic emitError(const Twine &message={})
Emit an error about fatal conditions with this operation, reporting up to any diagnostic handlers tha...
InFlightDiagnostic emitOpError(const Twine &message={})
Emit an error with the op name prefixed, like "'dim' op " which is convenient for verifiers.
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
This class provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:40
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isF64() const
Definition Types.cpp:41
MLIRContext * getContext() const
Return the MLIRContext in which this type was uniqued.
Definition Types.cpp:35
bool isF32() const
Definition Types.cpp:40
bool isInteger() const
Return true if this is an integer type (with the specified width).
Definition Types.cpp:58
bool isF16() const
Definition Types.cpp:38
bool isBF16() const
Definition Types.cpp:37
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
static WalkResult advance()
Definition WalkResult.h:47
static WalkResult interrupt()
Definition WalkResult.h:46
bool isValidLoadStoreImpl(Type type, ptr::AtomicOrdering ordering, std::optional< int64_t > alignment, const ::mlir::DataLayout *dataLayout, function_ref< InFlightDiagnostic()> emitError)
Checks whether the given type is an LLVM type that can be loaded or stored.
Definition LLVMAttrs.cpp:60
SmallVector< int64_t, 4 > getCoordinates(ArrayRef< int64_t > basis, unsigned linearIndex)
@ Write
Write register with '=' modifier.
@ ReadWrite
ReadWrite register with '+' modifier.
@ Read
Read register with no modifier.
std::pair< mlir::Type, unsigned > inferMMAType(mlir::NVVM::MMATypes type, mlir::NVVM::MMAFrag frag, int nRow, int nCol, mlir::MLIRContext *context)
Return the element type and number of elements associated with a wmma matrix of given chracteristics.
std::pair< llvm::Intrinsic::ID, llvm::SmallVector< llvm::Value * > > IDArgPair
A pair type of LLVM's Intrinsic ID and args (which are llvm values).
Definition NVVMDialect.h:53
void walk(Operation *op, function_ref< void(Region *)> callback, WalkOrder order)
Walk all of the regions, blocks, or operations nested under (and including) the given operation.
Definition Visitors.h:102
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
uint64_t getN(LevelType lt)
Definition Enums.h:442
uint64_t getM(LevelType lt)
Definition Enums.h:443
Include the generated interface declarations.
llvm::function_ref< void(Value, const ConstantIntRanges &)> SetIntRangeFn
The type of the setResultRanges callback provided to ops implementing InferIntRangeInterface.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:307
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
llvm::function_ref< Fn > function_ref
Definition LLVM.h:147
LogicalResult matchAndRewrite(SubFOp op, PatternRewriter &rewriter) const override
static bool isMinimumSMVersion(unsigned fullSmVersion)
static unsigned getTargetFullSmVersionFromStr(StringRef smVersionString)
bool isCompatibleWith(const unsigned &targetFullSmVersion) const
OpRewritePattern(MLIRContext *context, PatternBenefit benefit=1, ArrayRef< StringRef > generatedNames={})
This represents an operation in an abstracted form, suitable for use with the builder APIs.