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
451// Calls an LLVM intrinsic on the given operands. For f32/f64 vector types,
452// the intrinsic is called per-element and the results are packed back into a
453// vector. If retType is non-null, it is forwarded as the return-type
454// overload to `createIntrinsicCall`.
455static llvm::Value *
456createScalarizedIntrinsicCall(llvm::IRBuilderBase &builder,
457 llvm::Intrinsic::ID IID, llvm::Type *opTypeLLVM,
459 llvm::Type *retType) {
460 if (opTypeLLVM->isVectorTy() && (opTypeLLVM->getScalarType()->isFloatTy() ||
461 opTypeLLVM->getScalarType()->isDoubleTy())) {
462 llvm::Value *result = llvm::PoisonValue::get(
463 llvm::FixedVectorType::get(opTypeLLVM->getScalarType(), 2));
464 for (int64_t i = 0; i < 2; ++i) {
466 for (llvm::Value *op : operands)
467 scalarArgs.push_back(
468 builder.CreateExtractElement(op, builder.getInt32(i)));
469 llvm::Value *res = createIntrinsicCall(builder, IID, retType, scalarArgs);
470 result = builder.CreateInsertElement(result, res, builder.getInt32(i));
471 }
472 return result;
473 }
474
475 return createIntrinsicCall(builder, IID, retType, operands);
476}
477
478void NVVM::AddFOp::lowerAddFToLLVMIR(llvm::Value *argLHS, llvm::Value *argRHS,
479 Value res, NVVM::FPRoundingMode rndMode,
480 NVVM::SaturationMode satMode, bool isFTZ,
482 llvm::IRBuilderBase &builder) {
483 llvm::Type *opTypeLLVM = argLHS->getType();
484 bool isVectorOp = opTypeLLVM->isVectorTy();
485 bool isSat = satMode != NVVM::SaturationMode::NONE;
486
487 // FIXME: Add intrinsics for add.rn.ftz.f16x2 and add.rn.ftz.f16 here when
488 // they are available.
489 static constexpr llvm::Intrinsic::ID f16IDs[] = {
490 llvm::Intrinsic::nvvm_add_rn_sat_f16,
491 llvm::Intrinsic::nvvm_add_rn_ftz_sat_f16,
492 llvm::Intrinsic::nvvm_add_rn_sat_v2f16,
493 llvm::Intrinsic::nvvm_add_rn_ftz_sat_v2f16,
494 };
495
496 static constexpr llvm::Intrinsic::ID f32IDs[] = {
497 llvm::Intrinsic::nvvm_add_rn_f, // default rounding mode RN
498 llvm::Intrinsic::nvvm_add_rn_f,
499 llvm::Intrinsic::nvvm_add_rm_f,
500 llvm::Intrinsic::nvvm_add_rp_f,
501 llvm::Intrinsic::nvvm_add_rz_f,
502 llvm::Intrinsic::nvvm_add_rn_sat_f, // default rounding mode RN
503 llvm::Intrinsic::nvvm_add_rn_sat_f,
504 llvm::Intrinsic::nvvm_add_rm_sat_f,
505 llvm::Intrinsic::nvvm_add_rp_sat_f,
506 llvm::Intrinsic::nvvm_add_rz_sat_f,
507 llvm::Intrinsic::nvvm_add_rn_ftz_f, // default rounding mode RN
508 llvm::Intrinsic::nvvm_add_rn_ftz_f,
509 llvm::Intrinsic::nvvm_add_rm_ftz_f,
510 llvm::Intrinsic::nvvm_add_rp_ftz_f,
511 llvm::Intrinsic::nvvm_add_rz_ftz_f,
512 llvm::Intrinsic::nvvm_add_rn_ftz_sat_f, // default rounding mode RN
513 llvm::Intrinsic::nvvm_add_rn_ftz_sat_f,
514 llvm::Intrinsic::nvvm_add_rm_ftz_sat_f,
515 llvm::Intrinsic::nvvm_add_rp_ftz_sat_f,
516 llvm::Intrinsic::nvvm_add_rz_ftz_sat_f,
517 };
518
519 static constexpr llvm::Intrinsic::ID f64IDs[] = {
520 llvm::Intrinsic::nvvm_add_rn_d, // default rounding mode RN
521 llvm::Intrinsic::nvvm_add_rn_d, llvm::Intrinsic::nvvm_add_rm_d,
522 llvm::Intrinsic::nvvm_add_rp_d, llvm::Intrinsic::nvvm_add_rz_d};
523
524 auto addIntrinsic = [&](llvm::Intrinsic::ID IID) -> llvm::Value * {
525 return createScalarizedIntrinsicCall(builder, IID, opTypeLLVM,
526 {argLHS, argRHS}, opTypeLLVM);
527 };
528
529 // f16 + f16 -> f16 / vector<2xf16> + vector<2xf16> -> vector<2xf16>
530 // FIXME: Allow lowering to add.rn.ftz.f16x2 and add.rn.ftz.f16 here when the
531 // intrinsics are available.
532 if (opTypeLLVM->getScalarType()->isHalfTy()) {
533 llvm::Value *result;
534 if (isSat) {
535 unsigned index = (isVectorOp << 1) | isFTZ;
536 result = addIntrinsic(f16IDs[index]);
537 } else {
538 result = builder.CreateFAdd(argLHS, argRHS);
539 }
540 mt.mapValue(res, result);
541 return;
542 }
543
544 // bf16 + bf16 -> bf16 / vector<2xbf16> + vector<2xbf16> -> vector<2xbf16>
545 if (opTypeLLVM->getScalarType()->isBFloatTy()) {
546 mt.mapValue(res, builder.CreateFAdd(argLHS, argRHS));
547 return;
548 }
549
550 // f64 + f64 -> f64 / vector<2xf64> + vector<2xf64> -> vector<2xf64>
551 if (opTypeLLVM->getScalarType()->isDoubleTy()) {
552 unsigned index = static_cast<unsigned>(rndMode);
553 mt.mapValue(res, addIntrinsic(f64IDs[index]));
554 return;
555 }
556
557 // f32 + f32 -> f32 / vector<2xf32> + vector<2xf32> -> vector<2xf32>
558 const unsigned numRndModes = 5; // NONE, RM, RN, RP, RZ
559 if (opTypeLLVM->getScalarType()->isFloatTy()) {
560 unsigned index =
561 ((isFTZ << 1) | isSat) * numRndModes + static_cast<unsigned>(rndMode);
562 mt.mapValue(res, addIntrinsic(f32IDs[index]));
563 return;
564 }
565}
566
567void NVVM::FmaOp::lowerFmaToLLVMIR(Operation &op, LLVM::ModuleTranslation &mt,
568 llvm::IRBuilderBase &builder) {
569 auto thisOp = cast<NVVM::FmaOp>(op);
570 mlir::NVVM::FPRoundingMode rndMode = thisOp.getRnd();
571 unsigned rndIndex = static_cast<unsigned>(rndMode) - 1; // 1-4 mapped to 0-3
572 mlir::NVVM::SaturationMode satMode = thisOp.getSat();
573 bool isFTZ = thisOp.getFtz();
574 bool isRelu = thisOp.getRelu();
575 bool isSat = satMode == NVVM::SaturationMode::SAT;
576 bool isOOB = thisOp.getOob();
577
578 mlir::Type opType = thisOp.getRes().getType();
579 llvm::Type *opTypeLLVM = mt.convertType(opType);
580 bool isVectorFma = opTypeLLVM->isVectorTy();
581
582 llvm::Value *argA = mt.lookupValue(thisOp.getA());
583 llvm::Value *argB = mt.lookupValue(thisOp.getB());
584 llvm::Value *argC = mt.lookupValue(thisOp.getC());
585
586 static constexpr llvm::Intrinsic::ID f16IDs[] = {
587 llvm::Intrinsic::nvvm_fma_rn_f16,
588 llvm::Intrinsic::nvvm_fma_rn_f16x2,
589 llvm::Intrinsic::nvvm_fma_rn_ftz_f16,
590 llvm::Intrinsic::nvvm_fma_rn_ftz_f16x2,
591 llvm::Intrinsic::nvvm_fma_rn_sat_f16,
592 llvm::Intrinsic::nvvm_fma_rn_sat_f16x2,
593 llvm::Intrinsic::nvvm_fma_rn_ftz_sat_f16,
594 llvm::Intrinsic::nvvm_fma_rn_ftz_sat_f16x2,
595 llvm::Intrinsic::nvvm_fma_rn_relu_f16,
596 llvm::Intrinsic::nvvm_fma_rn_relu_f16x2,
597 llvm::Intrinsic::nvvm_fma_rn_ftz_relu_f16,
598 llvm::Intrinsic::nvvm_fma_rn_ftz_relu_f16x2};
599
600 static constexpr llvm::Intrinsic::ID bf16IDs[] = {
601 llvm::Intrinsic::nvvm_fma_rn_bf16, llvm::Intrinsic::nvvm_fma_rn_bf16x2,
602 llvm::Intrinsic::nvvm_fma_rn_relu_bf16,
603 llvm::Intrinsic::nvvm_fma_rn_relu_bf16x2};
604
605 static constexpr llvm::Intrinsic::ID f32IDs[] = {
606 llvm::Intrinsic::nvvm_fma_rn_f,
607 llvm::Intrinsic::nvvm_fma_rm_f,
608 llvm::Intrinsic::nvvm_fma_rp_f,
609 llvm::Intrinsic::nvvm_fma_rz_f,
610 llvm::Intrinsic::nvvm_fma_rn_sat_f,
611 llvm::Intrinsic::nvvm_fma_rm_sat_f,
612 llvm::Intrinsic::nvvm_fma_rp_sat_f,
613 llvm::Intrinsic::nvvm_fma_rz_sat_f,
614 llvm::Intrinsic::nvvm_fma_rn_ftz_f,
615 llvm::Intrinsic::nvvm_fma_rm_ftz_f,
616 llvm::Intrinsic::nvvm_fma_rp_ftz_f,
617 llvm::Intrinsic::nvvm_fma_rz_ftz_f,
618 llvm::Intrinsic::nvvm_fma_rn_ftz_sat_f,
619 llvm::Intrinsic::nvvm_fma_rm_ftz_sat_f,
620 llvm::Intrinsic::nvvm_fma_rp_ftz_sat_f,
621 llvm::Intrinsic::nvvm_fma_rz_ftz_sat_f,
622 };
623
624 static constexpr llvm::Intrinsic::ID f64IDs[] = {
625 llvm::Intrinsic::nvvm_fma_rn_d, llvm::Intrinsic::nvvm_fma_rm_d,
626 llvm::Intrinsic::nvvm_fma_rp_d, llvm::Intrinsic::nvvm_fma_rz_d};
627
628 auto fmaIntrinsic = [&](llvm::Intrinsic::ID IID,
629 llvm::Type *retType) -> llvm::Value * {
631 builder, IID, opTypeLLVM, {argA, argB, argC}, /*retType=*/retType);
632 };
633
634 // f16 + f16 -> f16 / vector<2xf16> + vector<2xf16> -> vector<2xf16>
635 if (opTypeLLVM->getScalarType()->isHalfTy()) {
636 llvm::Value *result;
637 if (isOOB) {
638 result = fmaIntrinsic(isRelu ? llvm::Intrinsic::nvvm_fma_rn_oob_relu
639 : llvm::Intrinsic::nvvm_fma_rn_oob,
640 opTypeLLVM);
641 } else {
642 unsigned index =
643 (isRelu << 3) | (isSat << 2) | (isFTZ << 1) |
644 isVectorFma; // Op verifier ensures that this index is valid
645 result = fmaIntrinsic(f16IDs[index], opTypeLLVM);
646 }
647 mt.mapValue(thisOp.getRes(), result);
648 return;
649 }
650
651 // bf16 + bf16 -> bf16 / vector<2xbf16> + vector<2xbf16> -> vector<2xbf16>
652 if (opTypeLLVM->getScalarType()->isBFloatTy()) {
653 llvm::Value *result;
654 if (isOOB) {
655 result = fmaIntrinsic(isRelu ? llvm::Intrinsic::nvvm_fma_rn_oob_relu
656 : llvm::Intrinsic::nvvm_fma_rn_oob,
657 opTypeLLVM);
658 } else {
659 unsigned index = (isRelu << 1) | isVectorFma;
660 result = fmaIntrinsic(bf16IDs[index], opTypeLLVM);
661 }
662 mt.mapValue(thisOp.getRes(), result);
663 return;
664 }
665
666 // f64 + f64 -> f64 / vector<2xf64> + vector<2xf64> -> vector<2xf64>
667 if (opTypeLLVM->getScalarType()->isDoubleTy()) {
668 mt.mapValue(thisOp.getRes(),
669 fmaIntrinsic(f64IDs[rndIndex], opTypeLLVM->getScalarType()));
670 return;
671 }
672
673 // f32 + f32 -> f32 / vector<2xf32> + vector<2xf32> -> vector<2xf32>
674 const unsigned numRndModes = 4; // RN, RM, RP, RZ
675 if (opTypeLLVM->getScalarType()->isFloatTy()) {
676 unsigned index = ((isFTZ << 1) | isSat) * numRndModes + rndIndex;
677 mt.mapValue(thisOp.getRes(),
678 fmaIntrinsic(f32IDs[index], opTypeLLVM->getScalarType()));
679 return;
680 }
681}
682
683namespace {
684/// Implementation of the dialect interface that converts operations belonging
685/// to the NVVM dialect to LLVM IR.
686class NVVMDialectLLVMIRTranslationInterface
687 : public LLVMTranslationDialectInterface {
688public:
689 using LLVMTranslationDialectInterface::LLVMTranslationDialectInterface;
690
691 /// Translates the given operation to LLVM IR using the provided IR builder
692 /// and saving the state in `moduleTranslation`.
693 LogicalResult
694 convertOperation(Operation *op, llvm::IRBuilderBase &builder,
695 LLVM::ModuleTranslation &moduleTranslation) const final {
696 // All NVVM ops are instruction-level and require an active insertion point.
697 // A null insert block means the op is misplaced (e.g., at module scope),
698 // which would otherwise cause a null dereference in createIntrinsicCall.
699 if (!builder.GetInsertBlock())
700 return op->emitOpError(
701 "cannot be translated to LLVM IR without an active insertion "
702 "point; make sure the op is inside a function");
703 Operation &opInst = *op;
704#include "mlir/Dialect/LLVMIR/NVVMConversions.inc"
705
706 return failure();
707 }
708
709 /// Attaches module-level metadata for functions marked as kernels
710 /// and managed annotations for global variables.
711 LogicalResult
712 amendOperation(Operation *op, ArrayRef<llvm::Instruction *> instructions,
713 NamedAttribute attribute,
714 LLVM::ModuleTranslation &moduleTranslation) const final {
715 if (auto globalOp = dyn_cast<LLVM::GlobalOp>(op)) {
716 if (attribute.getName() == NVVM::NVVMDialect::getManagedAttrName()) {
717 auto *gv = cast<llvm::GlobalVariable>(
718 moduleTranslation.lookupGlobal(globalOp));
719 llvm::Module *m = gv->getParent();
720 llvm::LLVMContext &ctx = m->getContext();
721 llvm::NamedMDNode *md = m->getOrInsertNamedMetadata("nvvm.annotations");
722 md->addOperand(llvm::MDNode::get(
723 ctx, {llvm::ConstantAsMetadata::get(gv),
724 llvm::MDString::get(ctx, "managed"),
725 llvm::ConstantAsMetadata::get(llvm::ConstantInt::get(
726 llvm::Type::getInt32Ty(ctx), 1))}));
727 }
728 return success();
729 }
730
731 auto func = dyn_cast<LLVM::LLVMFuncOp>(op);
732 if (!func)
733 return failure();
734 llvm::Function *llvmFunc = moduleTranslation.lookupFunction(func.getName());
735
736 if (attribute.getName() == NVVM::NVVMDialect::getMaxntidAttrName()) {
737 if (!isa<DenseI32ArrayAttr>(attribute.getValue()))
738 return failure();
739 auto values = cast<DenseI32ArrayAttr>(attribute.getValue());
740 const std::string attr = llvm::formatv(
741 "{0:$[,]}", llvm::make_range(values.asArrayRef().begin(),
742 values.asArrayRef().end()));
743 llvmFunc->addFnAttr(llvm::NVVMAttr::MaxNTID, attr);
744 } else if (attribute.getName() == NVVM::NVVMDialect::getReqntidAttrName()) {
745 if (!isa<DenseI32ArrayAttr>(attribute.getValue()))
746 return failure();
747 auto values = cast<DenseI32ArrayAttr>(attribute.getValue());
748 const std::string attr = llvm::formatv(
749 "{0:$[,]}", llvm::make_range(values.asArrayRef().begin(),
750 values.asArrayRef().end()));
751 llvmFunc->addFnAttr(llvm::NVVMAttr::ReqNTID, attr);
752 } else if (attribute.getName() ==
753 NVVM::NVVMDialect::getClusterDimAttrName()) {
754 if (!isa<DenseI32ArrayAttr>(attribute.getValue()))
755 return failure();
756 auto values = cast<DenseI32ArrayAttr>(attribute.getValue());
757 const std::string attr = llvm::formatv(
758 "{0:$[,]}", llvm::make_range(values.asArrayRef().begin(),
759 values.asArrayRef().end()));
760 llvmFunc->addFnAttr(llvm::NVVMAttr::ClusterDim, attr);
761 } else if (attribute.getName() ==
762 NVVM::NVVMDialect::getClusterMaxBlocksAttrName()) {
763 auto value = dyn_cast<IntegerAttr>(attribute.getValue());
764 llvmFunc->addFnAttr(llvm::NVVMAttr::MaxClusterRank,
765 llvm::utostr(value.getInt()));
766 } else if (attribute.getName() ==
767 NVVM::NVVMDialect::getMinctasmAttrName()) {
768 auto value = dyn_cast<IntegerAttr>(attribute.getValue());
769 llvmFunc->addFnAttr(llvm::NVVMAttr::MinCTASm,
770 llvm::utostr(value.getInt()));
771 } else if (attribute.getName() == NVVM::NVVMDialect::getMaxnregAttrName()) {
772 auto value = dyn_cast<IntegerAttr>(attribute.getValue());
773 llvmFunc->addFnAttr(llvm::NVVMAttr::MaxNReg,
774 llvm::utostr(value.getInt()));
775 } else if (attribute.getName() ==
776 NVVM::NVVMDialect::getKernelFuncAttrName()) {
777 llvmFunc->setCallingConv(llvm::CallingConv::PTX_Kernel);
778 } else if (attribute.getName() ==
779 NVVM::NVVMDialect::getBlocksAreClustersAttrName()) {
780 llvmFunc->addFnAttr(llvm::NVVMAttr::BlocksAreClusters);
781 }
782
783 return success();
784 }
785
786 LogicalResult
787 convertParameterAttr(LLVMFuncOp funcOp, int argIdx, NamedAttribute attribute,
788 LLVM::ModuleTranslation &moduleTranslation) const final {
789
790 llvm::LLVMContext &llvmContext = moduleTranslation.getLLVMContext();
791 llvm::Function *llvmFunc =
792 moduleTranslation.lookupFunction(funcOp.getName());
793
794 if (attribute.getName() == NVVM::NVVMDialect::getGridConstantAttrName()) {
795 llvmFunc->addParamAttr(
796 argIdx,
797 llvm::Attribute::get(llvmContext, llvm::NVVMAttr::GridConstant));
798 }
799 return success();
800 }
801};
802} // namespace
803
805 registry.insert<NVVM::NVVMDialect>();
806 registry.addExtension(+[](MLIRContext *ctx, NVVM::NVVMDialect *dialect) {
807 dialect->addInterfaces<NVVMDialectLLVMIRTranslationInterface>();
808 });
809}
810
812 DialectRegistry registry;
814 context.appendDialectRegistry(registry);
815}
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::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;.