MLIR 24.0.0git
NVVMToLLVMIRTranslation.cpp
Go to the documentation of this file.
1//===- NVVMToLLVMIRTranslation.cpp - Translate NVVM to LLVM IR ------------===//
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 implements a translation between the MLIR NVVM dialect and
10// LLVM IR.
11//
12//===----------------------------------------------------------------------===//
13
16#include "mlir/IR/Operation.h"
18
19#include "llvm/ADT/StringExtras.h"
20#include "llvm/ADT/iterator_range.h"
21#include "llvm/IR/IRBuilder.h"
22#include "llvm/IR/IntrinsicsNVPTX.h"
23#include "llvm/Support/FormatVariadic.h"
24#include "llvm/Support/NVVMAttributes.h"
25#include <cmath>
26
27using namespace mlir;
28using namespace mlir::LLVM;
30
31#define REDUX_F32_ID_IMPL(op, abs, hasNaN) \
32 hasNaN ? llvm::Intrinsic::nvvm_redux_sync_f##op##abs##_NaN \
33 : llvm::Intrinsic::nvvm_redux_sync_f##op##abs
34
35#define GET_REDUX_F32_ID(op, hasAbs, hasNaN) \
36 hasAbs ? REDUX_F32_ID_IMPL(op, _abs, hasNaN) : REDUX_F32_ID_IMPL(op, , hasNaN)
37
38static llvm::Intrinsic::ID getReduxIntrinsicId(llvm::Type *resultType,
39 NVVM::ReductionKind kind,
40 bool hasAbs, bool hasNaN) {
41 switch (kind) {
42 case NVVM::ReductionKind::ADD:
43 return llvm::Intrinsic::nvvm_redux_sync_add;
44 case NVVM::ReductionKind::UMAX:
45 return llvm::Intrinsic::nvvm_redux_sync_umax;
46 case NVVM::ReductionKind::UMIN:
47 return llvm::Intrinsic::nvvm_redux_sync_umin;
48 case NVVM::ReductionKind::AND:
49 return llvm::Intrinsic::nvvm_redux_sync_and;
50 case NVVM::ReductionKind::OR:
51 return llvm::Intrinsic::nvvm_redux_sync_or;
52 case NVVM::ReductionKind::XOR:
53 return llvm::Intrinsic::nvvm_redux_sync_xor;
54 case NVVM::ReductionKind::MAX:
55 return llvm::Intrinsic::nvvm_redux_sync_max;
56 case NVVM::ReductionKind::MIN:
57 return llvm::Intrinsic::nvvm_redux_sync_min;
58 case NVVM::ReductionKind::FMIN:
59 return GET_REDUX_F32_ID(min, hasAbs, hasNaN);
60 case NVVM::ReductionKind::FMAX:
61 return GET_REDUX_F32_ID(max, hasAbs, hasNaN);
62 }
63 llvm_unreachable("unknown reduction kind");
64}
65
66static llvm::Intrinsic::ID getShflIntrinsicId(llvm::Type *resultType,
67 NVVM::ShflKind kind,
68 bool withPredicate) {
69
70 if (withPredicate) {
71 resultType = cast<llvm::StructType>(resultType)->getElementType(0);
72 switch (kind) {
73 case NVVM::ShflKind::bfly:
74 return resultType->isFloatTy()
75 ? llvm::Intrinsic::nvvm_shfl_sync_bfly_f32p
76 : llvm::Intrinsic::nvvm_shfl_sync_bfly_i32p;
77 case NVVM::ShflKind::up:
78 return resultType->isFloatTy() ? llvm::Intrinsic::nvvm_shfl_sync_up_f32p
79 : llvm::Intrinsic::nvvm_shfl_sync_up_i32p;
80 case NVVM::ShflKind::down:
81 return resultType->isFloatTy()
82 ? llvm::Intrinsic::nvvm_shfl_sync_down_f32p
83 : llvm::Intrinsic::nvvm_shfl_sync_down_i32p;
84 case NVVM::ShflKind::idx:
85 return resultType->isFloatTy() ? llvm::Intrinsic::nvvm_shfl_sync_idx_f32p
86 : llvm::Intrinsic::nvvm_shfl_sync_idx_i32p;
87 }
88 } else {
89 switch (kind) {
90 case NVVM::ShflKind::bfly:
91 return resultType->isFloatTy() ? llvm::Intrinsic::nvvm_shfl_sync_bfly_f32
92 : llvm::Intrinsic::nvvm_shfl_sync_bfly_i32;
93 case NVVM::ShflKind::up:
94 return resultType->isFloatTy() ? llvm::Intrinsic::nvvm_shfl_sync_up_f32
95 : llvm::Intrinsic::nvvm_shfl_sync_up_i32;
96 case NVVM::ShflKind::down:
97 return resultType->isFloatTy() ? llvm::Intrinsic::nvvm_shfl_sync_down_f32
98 : llvm::Intrinsic::nvvm_shfl_sync_down_i32;
99 case NVVM::ShflKind::idx:
100 return resultType->isFloatTy() ? llvm::Intrinsic::nvvm_shfl_sync_idx_f32
101 : llvm::Intrinsic::nvvm_shfl_sync_idx_i32;
102 }
103 }
104 llvm_unreachable("unknown shuffle kind");
105}
106
107static llvm::Intrinsic::ID getMatchSyncIntrinsicId(Type valType,
108 NVVM::MatchSyncKind kind) {
109 switch (kind) {
110 case NVVM::MatchSyncKind::any:
111 return valType.isInteger(32) ? llvm::Intrinsic::nvvm_match_any_sync_i32
112 : llvm::Intrinsic::nvvm_match_any_sync_i64;
113 case NVVM::MatchSyncKind::all:
114 // match.all instruction has two variants -- one returns a single value,
115 // another returns a pair {value, predicate}. We currently only implement
116 // the latter as that's the variant exposed by CUDA API.
117 return valType.isInteger(32) ? llvm::Intrinsic::nvvm_match_all_sync_i32p
118 : llvm::Intrinsic::nvvm_match_all_sync_i64p;
119 }
120 llvm_unreachable("unsupported match sync kind");
121}
122
123static llvm::Intrinsic::ID getVoteSyncIntrinsicId(NVVM::VoteSyncKind kind) {
124 switch (kind) {
125 case NVVM::VoteSyncKind::any:
126 return llvm::Intrinsic::nvvm_vote_any_sync;
127 case NVVM::VoteSyncKind::all:
128 return llvm::Intrinsic::nvvm_vote_all_sync;
129 case NVVM::VoteSyncKind::ballot:
130 return llvm::Intrinsic::nvvm_vote_ballot_sync;
131 case NVVM::VoteSyncKind::uni:
132 return llvm::Intrinsic::nvvm_vote_uni_sync;
133 }
134 llvm_unreachable("unsupported vote kind");
135}
136
137static llvm::Intrinsic::ID
138getLdMatrixIntrinsicId(NVVM::MMALayout layout, int32_t num,
139 NVVM::LdStMatrixShapeAttr shape,
140 NVVM::LdStMatrixEltType eltType) {
141 if (shape.getM() == 8 && shape.getN() == 8) {
142 switch (num) {
143 case 1:
144 return (layout == NVVM::MMALayout::row)
145 ? llvm::Intrinsic::nvvm_ldmatrix_sync_aligned_m8n8_x1_b16
146 : llvm::Intrinsic::
147 nvvm_ldmatrix_sync_aligned_m8n8_x1_trans_b16;
148 case 2:
149 return (layout == NVVM::MMALayout::row)
150 ? llvm::Intrinsic::nvvm_ldmatrix_sync_aligned_m8n8_x2_b16
151 : llvm::Intrinsic::
152 nvvm_ldmatrix_sync_aligned_m8n8_x2_trans_b16;
153 case 4:
154 return (layout == NVVM::MMALayout::row)
155 ? llvm::Intrinsic::nvvm_ldmatrix_sync_aligned_m8n8_x4_b16
156 : llvm::Intrinsic::
157 nvvm_ldmatrix_sync_aligned_m8n8_x4_trans_b16;
158 }
159 } else if (shape.getM() == 8 && shape.getN() == 16) {
160 if (eltType == NVVM::LdStMatrixEltType::B8X16_B6X16_P32) {
161 switch (num) {
162 case 1:
163 return llvm::Intrinsic::
164 nvvm_ldmatrix_sync_aligned_m8n16_x1_b8x16_b6x16_p32;
165 case 2:
166 return llvm::Intrinsic::
167 nvvm_ldmatrix_sync_aligned_m8n16_x2_b8x16_b6x16_p32;
168 case 4:
169 return llvm::Intrinsic::
170 nvvm_ldmatrix_sync_aligned_m8n16_x4_b8x16_b6x16_p32;
171 }
172 } else if (eltType == NVVM::LdStMatrixEltType::B8X16_B4X16_P64) {
173 switch (num) {
174 case 1:
175 return llvm::Intrinsic::
176 nvvm_ldmatrix_sync_aligned_m8n16_x1_b8x16_b4x16_p64;
177 case 2:
178 return llvm::Intrinsic::
179 nvvm_ldmatrix_sync_aligned_m8n16_x2_b8x16_b4x16_p64;
180 case 4:
181 return llvm::Intrinsic::
182 nvvm_ldmatrix_sync_aligned_m8n16_x4_b8x16_b4x16_p64;
183 }
184 }
185 } else if (shape.getM() == 16 && shape.getN() == 16) {
186 if (eltType == NVVM::LdStMatrixEltType::B8) {
187 switch (num) {
188 case 1:
189 return llvm::Intrinsic::nvvm_ldmatrix_sync_aligned_m16n16_x1_trans_b8;
190 case 2:
191 return llvm::Intrinsic::nvvm_ldmatrix_sync_aligned_m16n16_x2_trans_b8;
192 }
193 } else if (eltType == NVVM::LdStMatrixEltType::B8X16_B6X16_P32) {
194 switch (num) {
195 case 1:
196 return llvm::Intrinsic::
197 nvvm_ldmatrix_sync_aligned_m16n16_x1_trans_b8x16_b6x16_p32;
198 case 2:
199 return llvm::Intrinsic::
200 nvvm_ldmatrix_sync_aligned_m16n16_x2_trans_b8x16_b6x16_p32;
201 }
202 } else if (eltType == NVVM::LdStMatrixEltType::B8X16_B4X16_P64) {
203 switch (num) {
204 case 1:
205 return llvm::Intrinsic::
206 nvvm_ldmatrix_sync_aligned_m16n16_x1_trans_b8x16_b4x16_p64;
207 case 2:
208 return llvm::Intrinsic::
209 nvvm_ldmatrix_sync_aligned_m16n16_x2_trans_b8x16_b4x16_p64;
210 }
211 }
212 }
213 llvm_unreachable("unknown ldmatrix kind");
214}
215
216/// Return the intrinsic ID associated with stmatrix for the given paramters.
217static llvm::Intrinsic::ID
218getStMatrixIntrinsicId(NVVM::MMALayout layout, int32_t num,
219 NVVM::LdStMatrixShapeAttr shape,
220 NVVM::LdStMatrixEltType eltType) {
221 if (shape.getM() == 8 && shape.getN() == 8) {
222 switch (num) {
223 case 1:
224 return (layout == NVVM::MMALayout::row)
225 ? llvm::Intrinsic::nvvm_stmatrix_sync_aligned_m8n8_x1_b16
226 : llvm::Intrinsic::
227 nvvm_stmatrix_sync_aligned_m8n8_x1_trans_b16;
228 case 2:
229 return (layout == NVVM::MMALayout::row)
230 ? llvm::Intrinsic::nvvm_stmatrix_sync_aligned_m8n8_x2_b16
231 : llvm::Intrinsic::
232 nvvm_stmatrix_sync_aligned_m8n8_x2_trans_b16;
233 case 4:
234 return (layout == NVVM::MMALayout::row)
235 ? llvm::Intrinsic::nvvm_stmatrix_sync_aligned_m8n8_x4_b16
236 : llvm::Intrinsic::
237 nvvm_stmatrix_sync_aligned_m8n8_x4_trans_b16;
238 }
239 } else if (shape.getM() == 16 && shape.getN() == 8) {
240 switch (num) {
241 case 1:
242 return llvm::Intrinsic::nvvm_stmatrix_sync_aligned_m16n8_x1_trans_b8;
243 case 2:
244 return llvm::Intrinsic::nvvm_stmatrix_sync_aligned_m16n8_x2_trans_b8;
245 case 4:
246 return llvm::Intrinsic::nvvm_stmatrix_sync_aligned_m16n8_x4_trans_b8;
247 }
248 }
249 llvm_unreachable("unknown stmatrix kind");
250}
251
252/// Return the intrinsic ID associated with st.bulk for the given address type.
253static llvm::Intrinsic::ID
254getStBulkIntrinsicId(LLVM::LLVMPointerType addrType) {
255 bool isSharedMemory = addrType.getAddressSpace() ==
256 static_cast<unsigned>(NVVM::NVVMMemorySpace::Shared);
257 return isSharedMemory ? llvm::Intrinsic::nvvm_st_bulk_shared_cta
258 : llvm::Intrinsic::nvvm_st_bulk;
259}
260
261static unsigned getUnidirectionalFenceProxyID(NVVM::ProxyKind fromProxy,
262 NVVM::ProxyKind toProxy,
263 NVVM::MemScopeKind scope,
264 bool isRelease) {
265 if (fromProxy == NVVM::ProxyKind::GENERIC &&
266 toProxy == NVVM::ProxyKind::TENSORMAP) {
267 switch (scope) {
268 case NVVM::MemScopeKind::CTA: {
269 if (isRelease)
270 return llvm::Intrinsic::nvvm_fence_proxy_tensormap_generic_release_cta;
271 return llvm::Intrinsic::nvvm_fence_proxy_tensormap_generic_acquire_cta;
272 }
273 case NVVM::MemScopeKind::CLUSTER: {
274 if (isRelease)
275 return llvm::Intrinsic::
276 nvvm_fence_proxy_tensormap_generic_release_cluster;
277 return llvm::Intrinsic::
278 nvvm_fence_proxy_tensormap_generic_acquire_cluster;
279 }
280 case NVVM::MemScopeKind::GPU: {
281 if (isRelease)
282 return llvm::Intrinsic::nvvm_fence_proxy_tensormap_generic_release_gpu;
283 return llvm::Intrinsic::nvvm_fence_proxy_tensormap_generic_acquire_gpu;
284 }
285 case NVVM::MemScopeKind::SYS: {
286 if (isRelease)
287 return llvm::Intrinsic::nvvm_fence_proxy_tensormap_generic_release_sys;
288 return llvm::Intrinsic::nvvm_fence_proxy_tensormap_generic_acquire_sys;
289 }
290 }
291 llvm_unreachable("Unknown scope for uni-directional fence.proxy operation");
292 }
293 llvm_unreachable("Unsupported proxy kinds");
294}
295
296static unsigned getMembarIntrinsicID(NVVM::MemScopeKind scope) {
297 switch (scope) {
298 case NVVM::MemScopeKind::CTA:
299 return llvm::Intrinsic::nvvm_membar_cta;
300 case NVVM::MemScopeKind::CLUSTER:
301 return llvm::Intrinsic::nvvm_fence_sc_cluster;
302 case NVVM::MemScopeKind::GPU:
303 return llvm::Intrinsic::nvvm_membar_gl;
304 case NVVM::MemScopeKind::SYS:
305 return llvm::Intrinsic::nvvm_membar_sys;
306 }
307 llvm_unreachable("Unknown scope for memory barrier");
308}
309
310#define TCGEN05LD(SHAPE, NUM) llvm::Intrinsic::nvvm_tcgen05_ld_##SHAPE##_##NUM
311
312static llvm::Intrinsic::ID
313getTcgen05LdIntrinsicID(mlir::NVVM::Tcgen05LdStShape shape, uint32_t num) {
314 llvm::Intrinsic::ID Shape16x64b[] = {
315 TCGEN05LD(16x64b, x1), TCGEN05LD(16x64b, x2), TCGEN05LD(16x64b, x4),
316 TCGEN05LD(16x64b, x8), TCGEN05LD(16x64b, x16), TCGEN05LD(16x64b, x32),
317 TCGEN05LD(16x64b, x64), TCGEN05LD(16x64b, x128),
318 };
319
320 llvm::Intrinsic::ID Shape16x128b[] = {
321 TCGEN05LD(16x128b, x1), TCGEN05LD(16x128b, x2), TCGEN05LD(16x128b, x4),
322 TCGEN05LD(16x128b, x8), TCGEN05LD(16x128b, x16), TCGEN05LD(16x128b, x32),
323 TCGEN05LD(16x128b, x64),
324 };
325
326 llvm::Intrinsic::ID Shape16x256b[] = {
327 TCGEN05LD(16x256b, x1), TCGEN05LD(16x256b, x2), TCGEN05LD(16x256b, x4),
328 TCGEN05LD(16x256b, x8), TCGEN05LD(16x256b, x16), TCGEN05LD(16x256b, x32),
329 };
330
331 llvm::Intrinsic::ID Shape16x32bx2[] = {
332 TCGEN05LD(16x32bx2, x1), TCGEN05LD(16x32bx2, x2),
333 TCGEN05LD(16x32bx2, x4), TCGEN05LD(16x32bx2, x8),
334 TCGEN05LD(16x32bx2, x16), TCGEN05LD(16x32bx2, x32),
335 TCGEN05LD(16x32bx2, x64), TCGEN05LD(16x32bx2, x128),
336 };
337
338 llvm::Intrinsic::ID Shape32x32b[] = {
339 TCGEN05LD(32x32b, x1), TCGEN05LD(32x32b, x2), TCGEN05LD(32x32b, x4),
340 TCGEN05LD(32x32b, x8), TCGEN05LD(32x32b, x16), TCGEN05LD(32x32b, x32),
341 TCGEN05LD(32x32b, x64), TCGEN05LD(32x32b, x128),
342 };
343
344 // `num` contains the length of vector and log2 of `num` returns the index
345 // into the shape array
346 unsigned Idx = std::log2(num);
347
348 switch (shape) {
349 case NVVM::Tcgen05LdStShape::SHAPE_16X64B:
350 return Shape16x64b[Idx];
351 case NVVM::Tcgen05LdStShape::SHAPE_16X128B:
352 return Shape16x128b[Idx - 1];
353 case NVVM::Tcgen05LdStShape::SHAPE_16X256B:
354 return Shape16x256b[Idx - 2];
355 case NVVM::Tcgen05LdStShape::SHAPE_32X32B:
356 return Shape32x32b[Idx];
357 case NVVM::Tcgen05LdStShape::SHAPE_16X32BX2:
358 return Shape16x32bx2[Idx];
359 }
360 llvm_unreachable("unhandled tcgen05.ld lowering");
361}
362
363#define TCGEN05ST(SHAPE, NUM) llvm::Intrinsic::nvvm_tcgen05_st_##SHAPE##_##NUM
364
365static llvm::Intrinsic::ID
366getTcgen05StIntrinsicID(mlir::NVVM::Tcgen05LdStShape shape, uint32_t num) {
367 llvm::Intrinsic::ID Shape16x64b[] = {
368 TCGEN05ST(16x64b, x1), TCGEN05ST(16x64b, x2), TCGEN05ST(16x64b, x4),
369 TCGEN05ST(16x64b, x8), TCGEN05ST(16x64b, x16), TCGEN05ST(16x64b, x32),
370 TCGEN05ST(16x64b, x64), TCGEN05ST(16x64b, x128),
371 };
372
373 llvm::Intrinsic::ID Shape16x128b[] = {
374 TCGEN05ST(16x128b, x1), TCGEN05ST(16x128b, x2), TCGEN05ST(16x128b, x4),
375 TCGEN05ST(16x128b, x8), TCGEN05ST(16x128b, x16), TCGEN05ST(16x128b, x32),
376 TCGEN05ST(16x128b, x64),
377 };
378
379 llvm::Intrinsic::ID Shape16x256b[] = {
380 TCGEN05ST(16x256b, x1), TCGEN05ST(16x256b, x2), TCGEN05ST(16x256b, x4),
381 TCGEN05ST(16x256b, x8), TCGEN05ST(16x256b, x16), TCGEN05ST(16x256b, x32),
382 };
383
384 llvm::Intrinsic::ID Shape16x32bx2[] = {
385 TCGEN05ST(16x32bx2, x1), TCGEN05ST(16x32bx2, x2),
386 TCGEN05ST(16x32bx2, x4), TCGEN05ST(16x32bx2, x8),
387 TCGEN05ST(16x32bx2, x16), TCGEN05ST(16x32bx2, x32),
388 TCGEN05ST(16x32bx2, x64), TCGEN05ST(16x32bx2, x128),
389 };
390
391 llvm::Intrinsic::ID Shape32x32b[] = {
392 TCGEN05ST(32x32b, x1), TCGEN05ST(32x32b, x2), TCGEN05ST(32x32b, x4),
393 TCGEN05ST(32x32b, x8), TCGEN05ST(32x32b, x16), TCGEN05ST(32x32b, x32),
394 TCGEN05ST(32x32b, x64), TCGEN05ST(32x32b, x128),
395 };
396
397 // `num` contains the length of vector and log2 of `num` returns the index
398 // into the shape array
399 unsigned Idx = std::log2(num);
400
401 switch (shape) {
402 case NVVM::Tcgen05LdStShape::SHAPE_16X64B:
403 return Shape16x64b[Idx];
404 case NVVM::Tcgen05LdStShape::SHAPE_16X128B:
405 return Shape16x128b[Idx - 1];
406 case NVVM::Tcgen05LdStShape::SHAPE_16X256B:
407 return Shape16x256b[Idx - 2];
408 case NVVM::Tcgen05LdStShape::SHAPE_32X32B:
409 return Shape32x32b[Idx];
410 case NVVM::Tcgen05LdStShape::SHAPE_16X32BX2:
411 return Shape16x32bx2[Idx];
412 }
413 llvm_unreachable("unhandled tcgen05.st lowering");
414}
415
416static llvm::Intrinsic::ID getFenceSyncRestrictID(NVVM::MemOrderKind order) {
417 return order == NVVM::MemOrderKind::ACQUIRE
418 ? llvm::Intrinsic::
419 nvvm_fence_acquire_sync_restrict_space_cluster_scope_cluster
420 : llvm::Intrinsic::
421 nvvm_fence_release_sync_restrict_space_cta_scope_cluster;
422}
423
424static llvm::Intrinsic::ID
425getFenceProxyID(NVVM::ProxyKind kind, std::optional<NVVM::SharedSpace> space) {
426 switch (kind) {
427 case NVVM::ProxyKind::alias:
428 return llvm::Intrinsic::nvvm_fence_proxy_alias;
429 case NVVM::ProxyKind::async:
430 return llvm::Intrinsic::nvvm_fence_proxy_async;
431 case NVVM::ProxyKind::async_global:
432 return llvm::Intrinsic::nvvm_fence_proxy_async_global;
433 case NVVM::ProxyKind::async_shared:
434 return *space == NVVM::SharedSpace::shared_cta
435 ? llvm::Intrinsic::nvvm_fence_proxy_async_shared_cta
436 : llvm::Intrinsic::nvvm_fence_proxy_async_shared_cluster;
437 default:
438 llvm_unreachable("unsupported proxy kind");
439 }
440}
441
442static llvm::Intrinsic::ID
443getFenceProxySyncRestrictID(NVVM::MemOrderKind order) {
444 return order == NVVM::MemOrderKind::ACQUIRE
445 ? llvm::Intrinsic::
446 nvvm_fence_proxy_async_generic_acquire_sync_restrict_space_cluster_scope_cluster
447 : llvm::Intrinsic::
448 nvvm_fence_proxy_async_generic_release_sync_restrict_space_cta_scope_cluster;
449}
450
451static llvm::RoundingMode
452getLLVMRoundingModeForFPArith(NVVM::FPRoundingMode rndMode) {
453 switch (rndMode) {
454 case NVVM::FPRoundingMode::RN:
455 return llvm::RoundingMode::NearestTiesToEven;
456 case NVVM::FPRoundingMode::RM:
457 return llvm::RoundingMode::TowardNegative;
458 case NVVM::FPRoundingMode::RP:
459 return llvm::RoundingMode::TowardPositive;
460 case NVVM::FPRoundingMode::RZ:
461 return llvm::RoundingMode::TowardZero;
462 default:
463 // default rounding mode is RN
464 assert(rndMode == NVVM::FPRoundingMode::NONE &&
465 "unsupported rounding mode for nvvm fp arithmetic");
466 return llvm::RoundingMode::NearestTiesToEven;
467 }
468}
469
470// Calls an LLVM intrinsic on the given operands. For f32/f64 vector types,
471// the intrinsic is called per-element and the results are packed back into a
472// vector. If retType is non-null, it is forwarded as the return-type
473// overload to `createIntrinsicCall`.
474static llvm::Value *
475createScalarizedIntrinsicCall(llvm::IRBuilderBase &builder,
476 llvm::Intrinsic::ID IID, llvm::Type *opTypeLLVM,
478 llvm::Type *retType) {
479 if (opTypeLLVM->isVectorTy() && (opTypeLLVM->getScalarType()->isFloatTy() ||
480 opTypeLLVM->getScalarType()->isDoubleTy())) {
481 llvm::Value *result = llvm::PoisonValue::get(
482 llvm::FixedVectorType::get(opTypeLLVM->getScalarType(), 2));
483 for (int64_t i = 0; i < 2; ++i) {
485 for (llvm::Value *op : operands)
486 scalarArgs.push_back(
487 op->getType()->isVectorTy()
488 ? builder.CreateExtractElement(op, builder.getInt32(i))
489 : op);
490 llvm::Value *res = createIntrinsicCall(builder, IID, retType, scalarArgs);
491 result = builder.CreateInsertElement(result, res, builder.getInt32(i));
492 }
493 return result;
494 }
495
496 return createIntrinsicCall(builder, IID, retType, operands);
497}
498
499void NVVM::AddFOp::lowerAddFToLLVMIR(llvm::Value *argLHS, llvm::Value *argRHS,
500 Value res, NVVM::FPRoundingMode rndMode,
501 NVVM::SaturationMode satMode, bool isFTZ,
503 llvm::IRBuilderBase &builder) {
504 llvm::Type *opTypeLLVM = argLHS->getType();
505 bool isSat = satMode != NVVM::SaturationMode::NONE;
506
507 static constexpr llvm::Intrinsic::ID addIDs[2][2] = {
508 {llvm::Intrinsic::nvvm_fadd, llvm::Intrinsic::nvvm_fadd_sat},
509 {llvm::Intrinsic::nvvm_fadd_ftz, llvm::Intrinsic::nvvm_fadd_ftz_sat}};
510
511 llvm::Intrinsic::ID id = addIDs[isFTZ][isSat];
512 llvm::Value *rnd = builder.getInt32(
513 static_cast<int>(getLLVMRoundingModeForFPArith(rndMode)));
514
515 // For f64 vector addition, and f32 vector addition with saturation,
516 // we need to scalarize the intrinsic call.
517 llvm::Type *scalarTypeLLVM = opTypeLLVM->getScalarType();
518 if (opTypeLLVM->isVectorTy() && (scalarTypeLLVM->isDoubleTy() ||
519 (isSat && scalarTypeLLVM->isFloatTy()))) {
520 mt.mapValue(res, createScalarizedIntrinsicCall(builder, id, opTypeLLVM,
521 {argLHS, argRHS, rnd},
522 scalarTypeLLVM));
523 return;
524 }
525
526 mt.mapValue(
527 res, createIntrinsicCall(builder, id, opTypeLLVM, {argLHS, argRHS, rnd}));
528}
529
530void NVVM::FmaOp::lowerFmaToLLVMIR(Operation &op, LLVM::ModuleTranslation &mt,
531 llvm::IRBuilderBase &builder) {
532 auto thisOp = cast<NVVM::FmaOp>(op);
533 mlir::NVVM::FPRoundingMode rndMode = thisOp.getRnd();
534 unsigned rndIndex = static_cast<unsigned>(rndMode) - 1; // 1-4 mapped to 0-3
535 mlir::NVVM::SaturationMode satMode = thisOp.getSat();
536 bool isFTZ = thisOp.getFtz();
537 bool isRelu = thisOp.getRelu();
538 bool isSat = satMode == NVVM::SaturationMode::SAT;
539 bool isOOB = thisOp.getOob();
540
541 mlir::Type opType = thisOp.getRes().getType();
542 llvm::Type *opTypeLLVM = mt.convertType(opType);
543 bool isVectorFma = opTypeLLVM->isVectorTy();
544
545 llvm::Value *argA = mt.lookupValue(thisOp.getA());
546 llvm::Value *argB = mt.lookupValue(thisOp.getB());
547 llvm::Value *argC = mt.lookupValue(thisOp.getC());
548
549 static constexpr llvm::Intrinsic::ID f16IDs[] = {
550 llvm::Intrinsic::nvvm_fma_rn_f16,
551 llvm::Intrinsic::nvvm_fma_rn_f16x2,
552 llvm::Intrinsic::nvvm_fma_rn_ftz_f16,
553 llvm::Intrinsic::nvvm_fma_rn_ftz_f16x2,
554 llvm::Intrinsic::nvvm_fma_rn_sat_f16,
555 llvm::Intrinsic::nvvm_fma_rn_sat_f16x2,
556 llvm::Intrinsic::nvvm_fma_rn_ftz_sat_f16,
557 llvm::Intrinsic::nvvm_fma_rn_ftz_sat_f16x2,
558 llvm::Intrinsic::nvvm_fma_rn_relu_f16,
559 llvm::Intrinsic::nvvm_fma_rn_relu_f16x2,
560 llvm::Intrinsic::nvvm_fma_rn_ftz_relu_f16,
561 llvm::Intrinsic::nvvm_fma_rn_ftz_relu_f16x2};
562
563 static constexpr llvm::Intrinsic::ID bf16IDs[] = {
564 llvm::Intrinsic::nvvm_fma_rn_bf16, llvm::Intrinsic::nvvm_fma_rn_bf16x2,
565 llvm::Intrinsic::nvvm_fma_rn_relu_bf16,
566 llvm::Intrinsic::nvvm_fma_rn_relu_bf16x2};
567
568 static constexpr llvm::Intrinsic::ID f32IDs[] = {
569 llvm::Intrinsic::nvvm_fma_rn_f,
570 llvm::Intrinsic::nvvm_fma_rm_f,
571 llvm::Intrinsic::nvvm_fma_rp_f,
572 llvm::Intrinsic::nvvm_fma_rz_f,
573 llvm::Intrinsic::nvvm_fma_rn_sat_f,
574 llvm::Intrinsic::nvvm_fma_rm_sat_f,
575 llvm::Intrinsic::nvvm_fma_rp_sat_f,
576 llvm::Intrinsic::nvvm_fma_rz_sat_f,
577 llvm::Intrinsic::nvvm_fma_rn_ftz_f,
578 llvm::Intrinsic::nvvm_fma_rm_ftz_f,
579 llvm::Intrinsic::nvvm_fma_rp_ftz_f,
580 llvm::Intrinsic::nvvm_fma_rz_ftz_f,
581 llvm::Intrinsic::nvvm_fma_rn_ftz_sat_f,
582 llvm::Intrinsic::nvvm_fma_rm_ftz_sat_f,
583 llvm::Intrinsic::nvvm_fma_rp_ftz_sat_f,
584 llvm::Intrinsic::nvvm_fma_rz_ftz_sat_f,
585 };
586
587 static constexpr llvm::Intrinsic::ID f64IDs[] = {
588 llvm::Intrinsic::nvvm_fma_rn_d, llvm::Intrinsic::nvvm_fma_rm_d,
589 llvm::Intrinsic::nvvm_fma_rp_d, llvm::Intrinsic::nvvm_fma_rz_d};
590
591 auto fmaIntrinsic = [&](llvm::Intrinsic::ID IID,
592 llvm::Type *retType) -> llvm::Value * {
594 builder, IID, opTypeLLVM, {argA, argB, argC}, /*retType=*/retType);
595 };
596
597 // f16 + f16 -> f16 / vector<2xf16> + vector<2xf16> -> vector<2xf16>
598 if (opTypeLLVM->getScalarType()->isHalfTy()) {
599 llvm::Value *result;
600 if (isOOB) {
601 result = fmaIntrinsic(isRelu ? llvm::Intrinsic::nvvm_fma_rn_oob_relu
602 : llvm::Intrinsic::nvvm_fma_rn_oob,
603 opTypeLLVM);
604 } else {
605 unsigned index =
606 (isRelu << 3) | (isSat << 2) | (isFTZ << 1) |
607 isVectorFma; // Op verifier ensures that this index is valid
608 result = fmaIntrinsic(f16IDs[index], opTypeLLVM);
609 }
610 mt.mapValue(thisOp.getRes(), result);
611 return;
612 }
613
614 // bf16 + bf16 -> bf16 / vector<2xbf16> + vector<2xbf16> -> vector<2xbf16>
615 if (opTypeLLVM->getScalarType()->isBFloatTy()) {
616 llvm::Value *result;
617 if (isOOB) {
618 result = fmaIntrinsic(isRelu ? llvm::Intrinsic::nvvm_fma_rn_oob_relu
619 : llvm::Intrinsic::nvvm_fma_rn_oob,
620 opTypeLLVM);
621 } else {
622 unsigned index = (isRelu << 1) | isVectorFma;
623 result = fmaIntrinsic(bf16IDs[index], opTypeLLVM);
624 }
625 mt.mapValue(thisOp.getRes(), result);
626 return;
627 }
628
629 // f64 + f64 -> f64 / vector<2xf64> + vector<2xf64> -> vector<2xf64>
630 if (opTypeLLVM->getScalarType()->isDoubleTy()) {
631 mt.mapValue(thisOp.getRes(),
632 fmaIntrinsic(f64IDs[rndIndex], opTypeLLVM->getScalarType()));
633 return;
634 }
635
636 // f32 + f32 -> f32 / vector<2xf32> + vector<2xf32> -> vector<2xf32>
637 const unsigned numRndModes = 4; // RN, RM, RP, RZ
638 if (opTypeLLVM->getScalarType()->isFloatTy()) {
639 unsigned index = ((isFTZ << 1) | isSat) * numRndModes + rndIndex;
640 mt.mapValue(thisOp.getRes(),
641 fmaIntrinsic(f32IDs[index], opTypeLLVM->getScalarType()));
642 return;
643 }
644}
645
646namespace {
647/// Implementation of the dialect interface that converts operations belonging
648/// to the NVVM dialect to LLVM IR.
649class NVVMDialectLLVMIRTranslationInterface
650 : public LLVMTranslationDialectInterface {
651public:
652 using LLVMTranslationDialectInterface::LLVMTranslationDialectInterface;
653
654 /// Translates the given operation to LLVM IR using the provided IR builder
655 /// and saving the state in `moduleTranslation`.
656 LogicalResult
657 convertOperation(Operation *op, llvm::IRBuilderBase &builder,
658 LLVM::ModuleTranslation &moduleTranslation) const final {
659 // All NVVM ops are instruction-level and require an active insertion point.
660 // A null insert block means the op is misplaced (e.g., at module scope),
661 // which would otherwise cause a null dereference in createIntrinsicCall.
662 if (!builder.GetInsertBlock())
663 return op->emitOpError(
664 "cannot be translated to LLVM IR without an active insertion "
665 "point; make sure the op is inside a function");
666 Operation &opInst = *op;
667#include "mlir/Dialect/LLVMIR/NVVMConversions.inc"
668
669 return failure();
670 }
671
672 /// Attaches module-level metadata for functions marked as kernels
673 /// and managed annotations for global variables.
674 LogicalResult
675 amendOperation(Operation *op, ArrayRef<llvm::Instruction *> instructions,
676 NamedAttribute attribute,
677 LLVM::ModuleTranslation &moduleTranslation) const final {
678 if (auto globalOp = dyn_cast<LLVM::GlobalOp>(op)) {
679 if (attribute.getName() == NVVM::NVVMDialect::getManagedAttrName()) {
680 auto *gv = cast<llvm::GlobalVariable>(
681 moduleTranslation.lookupGlobal(globalOp));
682 llvm::Module *m = gv->getParent();
683 llvm::LLVMContext &ctx = m->getContext();
684 llvm::NamedMDNode *md = m->getOrInsertNamedMetadata("nvvm.annotations");
685 md->addOperand(llvm::MDNode::get(
686 ctx, {llvm::ConstantAsMetadata::get(gv),
687 llvm::MDString::get(ctx, "managed"),
688 llvm::ConstantAsMetadata::get(llvm::ConstantInt::get(
689 llvm::Type::getInt32Ty(ctx), 1))}));
690 }
691 return success();
692 }
693
694 auto func = dyn_cast<LLVM::LLVMFuncOp>(op);
695 if (!func)
696 return failure();
697 llvm::Function *llvmFunc = moduleTranslation.lookupFunction(func.getName());
698
699 if (attribute.getName() == NVVM::NVVMDialect::getMaxntidAttrName()) {
700 if (!isa<DenseI32ArrayAttr>(attribute.getValue()))
701 return failure();
702 auto values = cast<DenseI32ArrayAttr>(attribute.getValue());
703 const std::string attr = llvm::formatv(
704 "{0:$[,]}", llvm::make_range(values.asArrayRef().begin(),
705 values.asArrayRef().end()));
706 llvmFunc->addFnAttr(llvm::NVVMAttr::MaxNTID, attr);
707 } else if (attribute.getName() == NVVM::NVVMDialect::getReqntidAttrName()) {
708 if (!isa<DenseI32ArrayAttr>(attribute.getValue()))
709 return failure();
710 auto values = cast<DenseI32ArrayAttr>(attribute.getValue());
711 const std::string attr = llvm::formatv(
712 "{0:$[,]}", llvm::make_range(values.asArrayRef().begin(),
713 values.asArrayRef().end()));
714 llvmFunc->addFnAttr(llvm::NVVMAttr::ReqNTID, attr);
715 } else if (attribute.getName() ==
716 NVVM::NVVMDialect::getClusterDimAttrName()) {
717 if (!isa<DenseI32ArrayAttr>(attribute.getValue()))
718 return failure();
719 auto values = cast<DenseI32ArrayAttr>(attribute.getValue());
720 const std::string attr = llvm::formatv(
721 "{0:$[,]}", llvm::make_range(values.asArrayRef().begin(),
722 values.asArrayRef().end()));
723 llvmFunc->addFnAttr(llvm::NVVMAttr::ClusterDim, attr);
724 } else if (attribute.getName() ==
725 NVVM::NVVMDialect::getClusterMaxBlocksAttrName()) {
726 auto value = dyn_cast<IntegerAttr>(attribute.getValue());
727 llvmFunc->addFnAttr(llvm::NVVMAttr::MaxClusterRank,
728 llvm::utostr(value.getInt()));
729 } else if (attribute.getName() ==
730 NVVM::NVVMDialect::getMinctasmAttrName()) {
731 auto value = dyn_cast<IntegerAttr>(attribute.getValue());
732 llvmFunc->addFnAttr(llvm::NVVMAttr::MinCTASm,
733 llvm::utostr(value.getInt()));
734 } else if (attribute.getName() == NVVM::NVVMDialect::getMaxnregAttrName()) {
735 auto value = dyn_cast<IntegerAttr>(attribute.getValue());
736 llvmFunc->addFnAttr(llvm::NVVMAttr::MaxNReg,
737 llvm::utostr(value.getInt()));
738 } else if (attribute.getName() ==
739 NVVM::NVVMDialect::getKernelFuncAttrName()) {
740 llvmFunc->setCallingConv(llvm::CallingConv::PTX_Kernel);
741 } else if (attribute.getName() ==
742 NVVM::NVVMDialect::getBlocksAreClustersAttrName()) {
743 llvmFunc->addFnAttr(llvm::NVVMAttr::BlocksAreClusters);
744 }
745
746 return success();
747 }
748
749 LogicalResult
750 convertParameterAttr(LLVMFuncOp funcOp, int argIdx, NamedAttribute attribute,
751 LLVM::ModuleTranslation &moduleTranslation) const final {
752
753 llvm::LLVMContext &llvmContext = moduleTranslation.getLLVMContext();
754 llvm::Function *llvmFunc =
755 moduleTranslation.lookupFunction(funcOp.getName());
756
757 if (attribute.getName() == NVVM::NVVMDialect::getGridConstantAttrName()) {
758 llvmFunc->addParamAttr(
759 argIdx,
760 llvm::Attribute::get(llvmContext, llvm::NVVMAttr::GridConstant));
761 }
762 return success();
763 }
764};
765} // namespace
766
768 registry.insert<NVVM::NVVMDialect>();
769 registry.addExtension(+[](MLIRContext *ctx, NVVM::NVVMDialect *dialect) {
770 dialect->addInterfaces<NVVMDialectLLVMIRTranslationInterface>();
771 });
772}
773
775 DialectRegistry registry;
777 context.appendDialectRegistry(registry);
778}
return success()
static LogicalResult convertParameterAttr(llvm::AttrBuilder &attrBuilder, llvm::Attribute::AttrKind llvmKind, NamedAttribute namedAttr, ModuleTranslation &moduleTranslation, Location loc)
static llvm::Intrinsic::ID getLdMatrixIntrinsicId(NVVM::MMALayout layout, int32_t num, NVVM::LdStMatrixShapeAttr shape, NVVM::LdStMatrixEltType eltType)
static llvm::Intrinsic::ID getFenceProxyID(NVVM::ProxyKind kind, std::optional< NVVM::SharedSpace > space)
#define GET_REDUX_F32_ID(op, hasAbs, hasNaN)
static llvm::Intrinsic::ID getStMatrixIntrinsicId(NVVM::MMALayout layout, int32_t num, NVVM::LdStMatrixShapeAttr shape, NVVM::LdStMatrixEltType eltType)
Return the intrinsic ID associated with stmatrix for the given paramters.
static llvm::Intrinsic::ID getTcgen05StIntrinsicID(mlir::NVVM::Tcgen05LdStShape shape, uint32_t num)
static llvm::Intrinsic::ID getTcgen05LdIntrinsicID(mlir::NVVM::Tcgen05LdStShape shape, uint32_t num)
static unsigned getMembarIntrinsicID(NVVM::MemScopeKind scope)
static unsigned getUnidirectionalFenceProxyID(NVVM::ProxyKind fromProxy, NVVM::ProxyKind toProxy, NVVM::MemScopeKind scope, bool isRelease)
llvm::CallInst * createIntrinsicCall(llvm::IRBuilderBase &builder, llvm::Intrinsic::ID intrinsic, ArrayRef< llvm::Value * > args={}, ArrayRef< llvm::Type * > tys={})
Creates a call to an LLVM IR intrinsic function with the given arguments.
static llvm::Intrinsic::ID getFenceProxySyncRestrictID(NVVM::MemOrderKind order)
#define TCGEN05ST(SHAPE, NUM)
static llvm::Intrinsic::ID getReduxIntrinsicId(llvm::Type *resultType, NVVM::ReductionKind kind, bool hasAbs, bool hasNaN)
static llvm::RoundingMode getLLVMRoundingModeForFPArith(NVVM::FPRoundingMode rndMode)
static llvm::Value * createScalarizedIntrinsicCall(llvm::IRBuilderBase &builder, llvm::Intrinsic::ID IID, llvm::Type *opTypeLLVM, ArrayRef< llvm::Value * > operands, llvm::Type *retType)
#define TCGEN05LD(SHAPE, NUM)
static llvm::Intrinsic::ID getFenceSyncRestrictID(NVVM::MemOrderKind order)
static llvm::Intrinsic::ID getShflIntrinsicId(llvm::Type *resultType, NVVM::ShflKind kind, bool withPredicate)
static llvm::Intrinsic::ID getVoteSyncIntrinsicId(NVVM::VoteSyncKind kind)
static llvm::Intrinsic::ID getMatchSyncIntrinsicId(Type valType, NVVM::MatchSyncKind kind)
static llvm::Intrinsic::ID getStBulkIntrinsicId(LLVM::LLVMPointerType addrType)
Return the intrinsic ID associated with st.bulk for the given address type.
static Value max(ImplicitLocOpBuilder &builder, Value value, Value bound)
static Value min(ImplicitLocOpBuilder &builder, Value value, Value bound)
static bool isSharedMemory(MemRefType type)
Return true if this is a shared memory memref type.
The DialectRegistry maps a dialect namespace to a constructor for the matching dialect.
bool addExtension(TypeID extensionID, std::unique_ptr< DialectExtensionBase > extension)
Add the given extension to the registry.
Implementation class for module translation.
llvm::Value * lookupValue(Value value) const
Finds an LLVM IR value corresponding to the given MLIR value.
llvm::Type * convertType(Type type)
Converts the type from MLIR LLVM dialect to LLVM.
void mapValue(Value mlir, llvm::Value *llvm)
Stores the mapping between an MLIR value and its LLVM IR counterpart.
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
void appendDialectRegistry(const DialectRegistry &registry)
Append the contents of the given dialect registry to the registry associated with this context.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
InFlightDiagnostic emitOpError(const Twine &message={})
Emit an error with the op name prefixed, like "'dim' op " which is convenient for verifiers.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isInteger() const
Return true if this is an integer type (with the specified width).
Definition Types.cpp:58
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
llvm::CallInst * createIntrinsicCall(llvm::IRBuilderBase &builder, llvm::Intrinsic::ID intrinsic, ArrayRef< llvm::Value * > args={}, ArrayRef< llvm::Type * > tys={})
Creates a call to an LLVM IR intrinsic function with the given arguments.
Include the generated interface declarations.
void registerNVVMDialectTranslation(DialectRegistry &registry)
Register the NVVM dialect and the translation from it to the LLVM IR in the given registry;.