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 } else if (eltType == NVVM::LdStMatrixEltType::S8_S4) {
185 switch (num) {
186 case 1:
187 return llvm::Intrinsic::nvvm_ldmatrix_sync_aligned_m8n16_x1_s8_s4;
188 case 2:
189 return llvm::Intrinsic::nvvm_ldmatrix_sync_aligned_m8n16_x2_s8_s4;
190 case 4:
191 return llvm::Intrinsic::nvvm_ldmatrix_sync_aligned_m8n16_x4_s8_s4;
192 }
193 }
194 } else if (shape.getM() == 16 && shape.getN() == 16) {
195 if (eltType == NVVM::LdStMatrixEltType::B8) {
196 switch (num) {
197 case 1:
198 return llvm::Intrinsic::nvvm_ldmatrix_sync_aligned_m16n16_x1_trans_b8;
199 case 2:
200 return llvm::Intrinsic::nvvm_ldmatrix_sync_aligned_m16n16_x2_trans_b8;
201 }
202 } else if (eltType == NVVM::LdStMatrixEltType::B8X16_B6X16_P32) {
203 switch (num) {
204 case 1:
205 return llvm::Intrinsic::
206 nvvm_ldmatrix_sync_aligned_m16n16_x1_trans_b8x16_b6x16_p32;
207 case 2:
208 return llvm::Intrinsic::
209 nvvm_ldmatrix_sync_aligned_m16n16_x2_trans_b8x16_b6x16_p32;
210 }
211 } else if (eltType == NVVM::LdStMatrixEltType::B8X16_B4X16_P64) {
212 switch (num) {
213 case 1:
214 return llvm::Intrinsic::
215 nvvm_ldmatrix_sync_aligned_m16n16_x1_trans_b8x16_b4x16_p64;
216 case 2:
217 return llvm::Intrinsic::
218 nvvm_ldmatrix_sync_aligned_m16n16_x2_trans_b8x16_b4x16_p64;
219 }
220 }
221 }
222 llvm_unreachable("unknown ldmatrix kind");
223}
224
225/// Return the intrinsic ID associated with stmatrix for the given paramters.
226static llvm::Intrinsic::ID
227getStMatrixIntrinsicId(NVVM::MMALayout layout, int32_t num,
228 NVVM::LdStMatrixShapeAttr shape,
229 NVVM::LdStMatrixEltType eltType) {
230 if (shape.getM() == 8 && shape.getN() == 8) {
231 switch (num) {
232 case 1:
233 return (layout == NVVM::MMALayout::row)
234 ? llvm::Intrinsic::nvvm_stmatrix_sync_aligned_m8n8_x1_b16
235 : llvm::Intrinsic::
236 nvvm_stmatrix_sync_aligned_m8n8_x1_trans_b16;
237 case 2:
238 return (layout == NVVM::MMALayout::row)
239 ? llvm::Intrinsic::nvvm_stmatrix_sync_aligned_m8n8_x2_b16
240 : llvm::Intrinsic::
241 nvvm_stmatrix_sync_aligned_m8n8_x2_trans_b16;
242 case 4:
243 return (layout == NVVM::MMALayout::row)
244 ? llvm::Intrinsic::nvvm_stmatrix_sync_aligned_m8n8_x4_b16
245 : llvm::Intrinsic::
246 nvvm_stmatrix_sync_aligned_m8n8_x4_trans_b16;
247 }
248 } else if (shape.getM() == 16 && shape.getN() == 8) {
249 switch (num) {
250 case 1:
251 return llvm::Intrinsic::nvvm_stmatrix_sync_aligned_m16n8_x1_trans_b8;
252 case 2:
253 return llvm::Intrinsic::nvvm_stmatrix_sync_aligned_m16n8_x2_trans_b8;
254 case 4:
255 return llvm::Intrinsic::nvvm_stmatrix_sync_aligned_m16n8_x4_trans_b8;
256 }
257 }
258 llvm_unreachable("unknown stmatrix kind");
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
499// Lowers a binary FP arithmetic op to the intrinsic selected from `ids`, which
500// is indexed by [ftz][sat].
501static void lowerFloatBinaryOpToLLVMIR(const llvm::Intrinsic::ID (&ids)[2][2],
502 llvm::Value *argLHS, llvm::Value *argRHS,
503 Value res, NVVM::FPRoundingMode rndMode,
504 NVVM::SaturationMode satMode, bool isFTZ,
506 llvm::IRBuilderBase &builder) {
507 llvm::Type *opTypeLLVM = argLHS->getType();
508 bool isSat = satMode != NVVM::SaturationMode::NONE;
509
510 llvm::Intrinsic::ID id = ids[isFTZ][isSat];
511 llvm::Value *rnd = builder.getInt32(
512 static_cast<int>(getLLVMRoundingModeForFPArith(rndMode)));
513
514 // For f64 vectors, and f32 vectors with saturation, we need to scalarize
515 // the intrinsic call.
516 llvm::Type *scalarTypeLLVM = opTypeLLVM->getScalarType();
517 if (opTypeLLVM->isVectorTy() && (scalarTypeLLVM->isDoubleTy() ||
518 (isSat && scalarTypeLLVM->isFloatTy()))) {
519 mt.mapValue(res, createScalarizedIntrinsicCall(builder, id, opTypeLLVM,
520 {argLHS, argRHS, rnd},
521 scalarTypeLLVM));
522 return;
523 }
524
525 mt.mapValue(
526 res, createIntrinsicCall(builder, id, opTypeLLVM, {argLHS, argRHS, rnd}));
527}
528
529void NVVM::AddFOp::lowerAddFToLLVMIR(llvm::Value *argLHS, llvm::Value *argRHS,
530 Value res, NVVM::FPRoundingMode rndMode,
531 NVVM::SaturationMode satMode, bool isFTZ,
533 llvm::IRBuilderBase &builder) {
534 static constexpr llvm::Intrinsic::ID addIDs[2][2] = {
535 {llvm::Intrinsic::nvvm_fadd, llvm::Intrinsic::nvvm_fadd_sat},
536 {llvm::Intrinsic::nvvm_fadd_ftz, llvm::Intrinsic::nvvm_fadd_ftz_sat}};
537 lowerFloatBinaryOpToLLVMIR(addIDs, argLHS, argRHS, res, rndMode, satMode,
538 isFTZ, mt, builder);
539}
540
541void NVVM::MulFOp::lowerMulFToLLVMIR(llvm::Value *argLHS, llvm::Value *argRHS,
542 Value res, NVVM::FPRoundingMode rndMode,
543 NVVM::SaturationMode satMode, bool isFTZ,
545 llvm::IRBuilderBase &builder) {
546 static constexpr llvm::Intrinsic::ID mulIDs[2][2] = {
547 {llvm::Intrinsic::nvvm_fmul, llvm::Intrinsic::nvvm_fmul_sat},
548 {llvm::Intrinsic::nvvm_fmul_ftz, llvm::Intrinsic::nvvm_fmul_ftz_sat}};
549 lowerFloatBinaryOpToLLVMIR(mulIDs, argLHS, argRHS, res, rndMode, satMode,
550 isFTZ, mt, builder);
551}
552
553void NVVM::FmaOp::lowerFmaToLLVMIR(Operation &op, LLVM::ModuleTranslation &mt,
554 llvm::IRBuilderBase &builder) {
555 auto thisOp = cast<NVVM::FmaOp>(op);
556 mlir::NVVM::FPRoundingMode rndMode = thisOp.getRnd();
557 bool isFTZ = thisOp.getFtz();
558 bool isRelu = thisOp.getRelu();
559 bool isSat = thisOp.getSat() == NVVM::SaturationMode::SAT;
560 bool isOOB = thisOp.getOob();
561
562 mlir::Type opType = thisOp.getRes().getType();
563 llvm::Type *opTypeLLVM = mt.convertType(opType);
564
565 llvm::Value *argA = mt.lookupValue(thisOp.getA());
566 llvm::Value *argB = mt.lookupValue(thisOp.getB());
567 llvm::Value *argC = mt.lookupValue(thisOp.getC());
568
569 // The oob variants only support RN and take no rounding-mode operand.
570 if (isOOB) {
571 llvm::Intrinsic::ID id = isRelu ? llvm::Intrinsic::nvvm_fma_rn_oob_relu
572 : llvm::Intrinsic::nvvm_fma_rn_oob;
573 mt.mapValue(thisOp.getRes(), createIntrinsicCall(builder, id, opTypeLLVM,
574 {argA, argB, argC}));
575 return;
576 }
577
578 static constexpr llvm::Intrinsic::ID fmaIDs[2][3] = {
579 {llvm::Intrinsic::nvvm_fma, llvm::Intrinsic::nvvm_fma_sat,
580 llvm::Intrinsic::nvvm_fma_relu},
581 {llvm::Intrinsic::nvvm_fma_ftz, llvm::Intrinsic::nvvm_fma_ftz_sat,
582 llvm::Intrinsic::nvvm_fma_ftz_relu}};
583
584 llvm::Intrinsic::ID id = fmaIDs[isFTZ][isSat ? 1 : (isRelu ? 2 : 0)];
585 llvm::Value *rnd = builder.getInt32(
586 static_cast<int>(getLLVMRoundingModeForFPArith(rndMode)));
587
588 // For f64 vector fma, and f32 vector fma with saturation, we need to
589 // scalarize the intrinsic call.
590 llvm::Type *scalarTypeLLVM = opTypeLLVM->getScalarType();
591 if (opTypeLLVM->isVectorTy() && (scalarTypeLLVM->isDoubleTy() ||
592 (isSat && scalarTypeLLVM->isFloatTy()))) {
593 mt.mapValue(thisOp.getRes(), createScalarizedIntrinsicCall(
594 builder, id, opTypeLLVM,
595 {argA, argB, argC, rnd}, scalarTypeLLVM));
596 return;
597 }
598
599 mt.mapValue(thisOp.getRes(), createIntrinsicCall(builder, id, opTypeLLVM,
600 {argA, argB, argC, rnd}));
601}
602
603// Returns the LLVM type the sp{de}compress intrinsics use for metadata: i32 for
604// a single register (including vector<1xi32>) and a vector of i32 otherwise.
605static llvm::Type *getSPMetadataType(Type type, llvm::IRBuilderBase &builder) {
606 auto vecTy = dyn_cast<VectorType>(type);
607 if (!vecTy || vecTy.getNumElements() == 1)
608 return builder.getInt32Ty();
609 return llvm::FixedVectorType::get(builder.getInt32Ty(),
610 vecTy.getNumElements());
611}
612
613void NVVM::SparseCompressOp::lowerToLLVMIR(Operation &op,
615 llvm::IRBuilderBase &builder) {
616 auto thisOp = cast<NVVM::SparseCompressOp>(op);
617 Value metadata = thisOp.getMetadata();
618 Value compressedData = thisOp.getCompressedData();
619 llvm::Type *resultTy =
620 llvm::StructType::get(getSPMetadataType(metadata.getType(), builder),
621 mt.convertType(compressedData.getType()));
622
623 llvm::Value *result = createIntrinsicCall(
624 builder, llvm::Intrinsic::nvvm_spcompress, resultTy,
625 {mt.lookupValue(thisOp.getData()), mt.lookupValue(thisOp.getSpDesc()),
626 builder.getInt32(thisOp.getIdxSize()),
627 // spcompress only supports the 2:4 sparsity factor.
628 builder.getInt32(4)});
629
630 mt.mapValue(metadata) =
631 builder.CreateBitCast(builder.CreateExtractValue(result, 0),
632 mt.convertType(metadata.getType()));
633 mt.mapValue(compressedData) = builder.CreateExtractValue(result, 1);
634}
635
636void NVVM::SparseDecompressOp::lowerToLLVMIR(Operation &op,
638 llvm::IRBuilderBase &builder) {
639 auto thisOp = cast<NVVM::SparseDecompressOp>(op);
640 Value metadata = thisOp.getMetadata();
641 mt.mapValue(thisOp.getData()) = createIntrinsicCall(
642 builder, llvm::Intrinsic::nvvm_spdecompress,
643 mt.convertType(thisOp.getData().getType()),
644 {builder.CreateBitCast(mt.lookupValue(metadata),
645 getSPMetadataType(metadata.getType(), builder)),
646 mt.lookupValue(thisOp.getCompressedData()),
647 builder.getInt32(thisOp.getIdxSize()),
648 builder.getInt32(thisOp.getNumTgt())});
649}
650
651namespace {
652/// Implementation of the dialect interface that converts operations belonging
653/// to the NVVM dialect to LLVM IR.
654class NVVMDialectLLVMIRTranslationInterface
655 : public LLVMTranslationDialectInterface {
656public:
657 using LLVMTranslationDialectInterface::LLVMTranslationDialectInterface;
658
659 /// Translates the given operation to LLVM IR using the provided IR builder
660 /// and saving the state in `moduleTranslation`.
661 LogicalResult
662 convertOperation(Operation *op, llvm::IRBuilderBase &builder,
663 LLVM::ModuleTranslation &moduleTranslation) const final {
664 // All NVVM ops are instruction-level and require an active insertion point.
665 // A null insert block means the op is misplaced (e.g., at module scope),
666 // which would otherwise cause a null dereference in createIntrinsicCall.
667 if (!builder.GetInsertBlock())
668 return op->emitOpError(
669 "cannot be translated to LLVM IR without an active insertion "
670 "point; make sure the op is inside a function");
671 Operation &opInst = *op;
672#include "mlir/Dialect/LLVMIR/NVVMConversions.inc"
673
674 return failure();
675 }
676
677 /// Attaches module-level metadata for functions marked as kernels
678 /// and managed annotations for global variables.
679 LogicalResult
680 amendOperation(Operation *op, ArrayRef<llvm::Instruction *> instructions,
681 NamedAttribute attribute,
682 LLVM::ModuleTranslation &moduleTranslation) const final {
683 if (auto globalOp = dyn_cast<LLVM::GlobalOp>(op)) {
684 if (attribute.getName() == NVVM::NVVMDialect::getManagedAttrName()) {
685 auto *gv = cast<llvm::GlobalVariable>(
686 moduleTranslation.lookupGlobal(globalOp));
687 llvm::Module *m = gv->getParent();
688 llvm::LLVMContext &ctx = m->getContext();
689 llvm::NamedMDNode *md = m->getOrInsertNamedMetadata("nvvm.annotations");
690 md->addOperand(llvm::MDNode::get(
691 ctx, {llvm::ConstantAsMetadata::get(gv),
692 llvm::MDString::get(ctx, "managed"),
693 llvm::ConstantAsMetadata::get(llvm::ConstantInt::get(
694 llvm::Type::getInt32Ty(ctx), 1))}));
695 }
696 return success();
697 }
698
699 auto func = dyn_cast<LLVM::LLVMFuncOp>(op);
700 if (!func)
701 return failure();
702 llvm::Function *llvmFunc = moduleTranslation.lookupFunction(func.getName());
703
704 if (attribute.getName() == NVVM::NVVMDialect::getMaxntidAttrName()) {
705 if (!isa<DenseI32ArrayAttr>(attribute.getValue()))
706 return failure();
707 auto values = cast<DenseI32ArrayAttr>(attribute.getValue());
708 const std::string attr = llvm::formatv(
709 "{0:$[,]}", llvm::make_range(values.asArrayRef().begin(),
710 values.asArrayRef().end()));
711 llvmFunc->addFnAttr(llvm::NVVMAttr::MaxNTID, attr);
712 } else if (attribute.getName() == NVVM::NVVMDialect::getReqntidAttrName()) {
713 if (!isa<DenseI32ArrayAttr>(attribute.getValue()))
714 return failure();
715 auto values = cast<DenseI32ArrayAttr>(attribute.getValue());
716 const std::string attr = llvm::formatv(
717 "{0:$[,]}", llvm::make_range(values.asArrayRef().begin(),
718 values.asArrayRef().end()));
719 llvmFunc->addFnAttr(llvm::NVVMAttr::ReqNTID, attr);
720 } else if (attribute.getName() ==
721 NVVM::NVVMDialect::getClusterDimAttrName()) {
722 if (!isa<DenseI32ArrayAttr>(attribute.getValue()))
723 return failure();
724 auto values = cast<DenseI32ArrayAttr>(attribute.getValue());
725 const std::string attr = llvm::formatv(
726 "{0:$[,]}", llvm::make_range(values.asArrayRef().begin(),
727 values.asArrayRef().end()));
728 llvmFunc->addFnAttr(llvm::NVVMAttr::ClusterDim, attr);
729 } else if (attribute.getName() ==
730 NVVM::NVVMDialect::getClusterMaxBlocksAttrName()) {
731 auto value = dyn_cast<IntegerAttr>(attribute.getValue());
732 llvmFunc->addFnAttr(llvm::NVVMAttr::MaxClusterRank,
733 llvm::utostr(value.getInt()));
734 } else if (attribute.getName() ==
735 NVVM::NVVMDialect::getMinctasmAttrName()) {
736 auto value = dyn_cast<IntegerAttr>(attribute.getValue());
737 llvmFunc->addFnAttr(llvm::NVVMAttr::MinCTASm,
738 llvm::utostr(value.getInt()));
739 } else if (attribute.getName() == NVVM::NVVMDialect::getMaxnregAttrName()) {
740 auto value = dyn_cast<IntegerAttr>(attribute.getValue());
741 llvmFunc->addFnAttr(llvm::NVVMAttr::MaxNReg,
742 llvm::utostr(value.getInt()));
743 } else if (attribute.getName() ==
744 NVVM::NVVMDialect::getKernelFuncAttrName()) {
745 llvmFunc->setCallingConv(llvm::CallingConv::PTX_Kernel);
746 } else if (attribute.getName() ==
747 NVVM::NVVMDialect::getBlocksAreClustersAttrName()) {
748 llvmFunc->addFnAttr(llvm::NVVMAttr::BlocksAreClusters);
749 }
750
751 return success();
752 }
753
754 LogicalResult
755 convertParameterAttr(LLVMFuncOp funcOp, int argIdx, NamedAttribute attribute,
756 LLVM::ModuleTranslation &moduleTranslation) const final {
757
758 llvm::LLVMContext &llvmContext = moduleTranslation.getLLVMContext();
759 llvm::Function *llvmFunc =
760 moduleTranslation.lookupFunction(funcOp.getName());
761
762 if (attribute.getName() == NVVM::NVVMDialect::getGridConstantAttrName()) {
763 llvmFunc->addParamAttr(
764 argIdx,
765 llvm::Attribute::get(llvmContext, llvm::NVVMAttr::GridConstant));
766 }
767 return success();
768 }
769};
770} // namespace
771
773 registry.insert<NVVM::NVVMDialect>();
774 registry.addExtension(+[](MLIRContext *ctx, NVVM::NVVMDialect *dialect) {
775 dialect->addInterfaces<NVVMDialectLLVMIRTranslationInterface>();
776 });
777}
778
780 DialectRegistry registry;
782 context.appendDialectRegistry(registry);
783}
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 void lowerFloatBinaryOpToLLVMIR(const llvm::Intrinsic::ID(&ids)[2][2], llvm::Value *argLHS, llvm::Value *argRHS, Value res, NVVM::FPRoundingMode rndMode, NVVM::SaturationMode satMode, bool isFTZ, LLVM::ModuleTranslation &mt, llvm::IRBuilderBase &builder)
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::Type * getSPMetadataType(Type type, llvm::IRBuilderBase &builder)
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 Value max(ImplicitLocOpBuilder &builder, Value value, Value bound)
static Value min(ImplicitLocOpBuilder &builder, Value value, Value bound)
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:71
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
Type getType() const
Return the type of this value.
Definition Value.h:105
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;.