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