clang 24.0.0git
LowerToLLVMOffloadPrintfAMDGPU.cpp
Go to the documentation of this file.
1//===- LowerToLLVMOffloadPrintfAMDGPU.cpp - AMDGPU printf lowering --------===//
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 lowers cir.offload.printf to the hostcall and buffered printf
10// runtime interfaces of AMDGPU.
11//
12//===----------------------------------------------------------------------===//
13
14#include "LowerToLLVM.h"
16
17#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
18#include "mlir/IR/BuiltinOps.h"
20#include "llvm/ADT/SparseBitVector.h"
21#include "llvm/Support/AMDGPUPrintf.h"
22#include "llvm/Support/MathExtras.h"
23
24namespace cir {
25namespace direct {
26
27namespace {
28
29std::optional<int64_t> getConstantInt(mlir::Value value) {
30 auto constOp = value.getDefiningOp<cir::ConstantOp>();
31 auto intAttr =
32 constOp ? mlir::dyn_cast<cir::IntAttr>(constOp.getValue()) : nullptr;
33 if (!intAttr)
34 return std::nullopt;
35 const llvm::APInt &v = intAttr.getValue();
36 if (mlir::cast<cir::IntType>(intAttr.getType()).isSigned()
37 ? v.getSignificantBits() > 64
38 : v.getActiveBits() > 63)
39 return std::nullopt;
40 return v.getSExtValue();
41}
42
43bool isByteType(mlir::Type ty) {
44 auto intTy = mlir::dyn_cast<cir::IntType>(ty);
45 return intTy && intTy.getWidth() == 8;
46}
47
48bool getConstantStringInfo(mlir::Value ptr, mlir::ModuleOp mod,
49 mlir::SymbolTableCollection &symbolTables,
50 std::string &str) {
51 int64_t offset = 0;
52 while (mlir::Operation *def = ptr.getDefiningOp()) {
53 if (auto cast = mlir::dyn_cast<cir::CastOp>(def)) {
54 if (cast.getKind() != cir::CastKind::array_to_ptrdecay &&
55 cast.getKind() != cir::CastKind::bitcast &&
56 cast.getKind() != cir::CastKind::address_space)
57 break;
58 ptr = cast.getSrc();
59 } else if (auto elt = mlir::dyn_cast<cir::GetElementOp>(def)) {
60 std::optional<int64_t> index = getConstantInt(elt.getIndex());
61 if (!index || !isByteType(elt.getElementType()) ||
62 llvm::AddOverflow(offset, *index, offset))
63 return false;
64 ptr = elt.getBase();
65 } else if (auto stride = mlir::dyn_cast<cir::PtrStrideOp>(def)) {
66 std::optional<int64_t> index = getConstantInt(stride.getStride());
67 if (!index || !isByteType(stride.getElementType()) ||
68 llvm::AddOverflow(offset, *index, offset))
69 return false;
70 ptr = stride.getBase();
71 } else {
72 break;
73 }
74 }
75
76 auto getGlobal = ptr.getDefiningOp<cir::GetGlobalOp>();
77 if (!getGlobal)
78 return false;
79
80 // The global may or may not have been converted already.
81 std::string data;
82 mlir::Operation *symbol =
83 symbolTables.lookupSymbolIn(mod, getGlobal.getNameAttr());
84 if (auto global = mlir::dyn_cast_if_present<cir::GlobalOp>(symbol)) {
85 std::optional<mlir::Attribute> init = global.getInitialValue();
86 auto arrayTy = mlir::dyn_cast<cir::ArrayType>(global.getSymType());
87 if (!global.getConstant() || !init || !arrayTy ||
88 !isByteType(arrayTy.getElementType()))
89 return false;
90 if (auto constArr = mlir::dyn_cast<cir::ConstArrayAttr>(*init)) {
91 auto strAttr = mlir::dyn_cast<mlir::StringAttr>(constArr.getElts());
92 if (!strAttr)
93 return false;
94 data = strAttr.getValue().str();
95 } else if (!mlir::isa<cir::ZeroAttr>(*init)) {
96 return false;
97 }
98 // Add back the trailing NULs the string attribute leaves out.
99 data.resize(arrayTy.getSize(), '\0');
100 } else if (auto global =
101 mlir::dyn_cast_if_present<mlir::LLVM::GlobalOp>(symbol)) {
102 if (!global.getConstant())
103 return false;
104 mlir::Attribute value = global.getValueOrNull();
105 if (auto strAttr = mlir::dyn_cast_if_present<mlir::StringAttr>(value)) {
106 data = strAttr.getValue().str();
107 } else if (auto dense =
108 mlir::dyn_cast_if_present<mlir::DenseIntElementsAttr>(value);
109 dense && dense.getElementType().isInteger(8)) {
110 for (const llvm::APInt &c : dense.getValues<llvm::APInt>())
111 data.push_back(static_cast<char>(c.getZExtValue()));
112 } else {
113 return false;
114 }
115 } else {
116 return false;
117 }
118
119 if (offset < 0 || static_cast<uint64_t>(offset) > data.size())
120 return false;
121 llvm::StringRef rest = llvm::StringRef(data).drop_front(offset);
122 str = rest.substr(0, rest.find('\0')).str();
123 return true;
124}
125
126// helper struct to package the string related data
127struct StringData {
128 std::string str;
129 mlir::Value realSize;
130 mlir::Value alignedSize;
131 bool isConst = true;
132};
133
134class AMDGPUPrintfLowering {
135public:
136 AMDGPUPrintfLowering(cir::OffloadPrintfOp op,
137 mlir::ConversionPatternRewriter &rewriter,
138 const mlir::DataLayout &dataLayout,
139 mlir::SymbolTableCollection &symbolTables,
140 unsigned genericAddrSpace)
141 : op(op), rewriter(rewriter), dataLayout(dataLayout),
142 symbolTables(symbolTables), mod(op->getParentOfType<mlir::ModuleOp>()),
143 genericAddrSpace(genericAddrSpace) {}
144
145 mlir::LogicalResult lower(mlir::ValueRange args, bool isBuffered);
146
147private:
148 mlir::Value getI32(uint32_t value) {
149 return mlir::LLVM::ConstantOp::create(
150 rewriter, op.getLoc(), rewriter.getI32Type(), llvm::APInt(32, value));
151 }
152 mlir::Value getI64(uint64_t value) {
153 return mlir::LLVM::ConstantOp::create(
154 rewriter, op.getLoc(), rewriter.getI64Type(), llvm::APInt(64, value));
155 }
156 mlir::Value createCall(llvm::StringRef name, mlir::Type resTy,
157 mlir::ValueRange args);
158 mlir::Value gepInBounds(mlir::Value ptr, mlir::LLVM::GEPArg offset);
159 void addPrintfFormatMetadata(llvm::StringRef entry, bool onlyIfEmpty);
160
161 mlir::Value fitArgInto64Bits(mlir::Value arg);
162 mlir::Value callPrintfBegin(mlir::Value version);
163 mlir::Value callAppendArgs(mlir::Value desc, int numArgs, mlir::Value arg0,
164 mlir::Value arg1, mlir::Value arg2,
165 mlir::Value arg3, mlir::Value arg4,
166 mlir::Value arg5, mlir::Value arg6, bool isLast);
167 mlir::Value appendArg(mlir::Value desc, mlir::Value arg, bool isLast);
168 mlir::Value getStrlenWithNull(mlir::Value str);
169 mlir::Value callAppendStringN(mlir::Value desc, mlir::Value str,
170 mlir::Value length, bool isLast);
171 mlir::Value appendString(mlir::Value desc, mlir::Value arg, bool isLast);
172 mlir::Value processArg(mlir::Value desc, mlir::Value arg, bool specIsCString,
173 bool isLast);
174 bool isCStringArg(mlir::ValueRange args, size_t i) const;
175 mlir::Value callBufferedPrintfStart(
176 mlir::ValueRange args, mlir::Value fmt, bool isConstFmtStr,
177 llvm::SmallVectorImpl<StringData> &stringContents, mlir::Value &argSize);
178 void
179 processConstantStringArg(const StringData &sd,
180 llvm::SmallVectorImpl<mlir::Value> &whatToStore);
181 mlir::Value processNonStringArg(mlir::Value arg);
182 void callBufferedPrintfArgPush(mlir::ValueRange args, mlir::Value ptrToStore,
183 llvm::ArrayRef<StringData> stringContents,
184 bool isConstFmtStr);
185 mlir::Value emitAMDGPUPrintfCall(mlir::ValueRange args, bool isBuffered);
186
187 cir::OffloadPrintfOp op;
188 mlir::ConversionPatternRewriter &rewriter;
189 const mlir::DataLayout &dataLayout;
190 mlir::SymbolTableCollection &symbolTables;
191 mlir::ModuleOp mod;
192 unsigned genericAddrSpace;
193
194 /// The rest of the op's block, which the expansion branches to.
195 mlir::Block *cont = nullptr;
196 std::string fmtStr;
197 llvm::SparseBitVector<8> specIsCString;
198};
199
200} // namespace
201
202mlir::Value AMDGPUPrintfLowering::createCall(llvm::StringRef name,
203 mlir::Type resTy,
204 mlir::ValueRange args) {
205 auto fnTy = mlir::LLVM::LLVMFunctionType::get(
206 resTy, llvm::to_vector(args.getTypes()), /*isVarArg=*/false);
207 createLLVMFuncOpIfNotExist(rewriter, symbolTables, op, name, fnTy);
208 return mlir::LLVM::CallOp::create(
209 rewriter, op.getLoc(), fnTy,
210 mlir::FlatSymbolRefAttr::get(rewriter.getContext(), name), args)
211 .getResult();
212}
213
214mlir::Value AMDGPUPrintfLowering::gepInBounds(mlir::Value ptr,
215 mlir::LLVM::GEPArg offset) {
216 return mlir::LLVM::GEPOp::create(rewriter, op.getLoc(), ptr.getType(),
217 rewriter.getI8Type(), ptr,
218 llvm::ArrayRef<mlir::LLVM::GEPArg>{offset},
219 mlir::LLVM::GEPNoWrapFlags::inbounds);
220}
221
222void AMDGPUPrintfLowering::addPrintfFormatMetadata(llvm::StringRef entry,
223 bool onlyIfEmpty) {
224 constexpr llvm::StringLiteral name = "llvm.printf.fmts";
225 mlir::Attribute node = mlir::LLVM::MDNodeAttr::get(
226 rewriter.getContext(),
227 {mlir::LLVM::MDStringAttr::get(
228 rewriter.getContext(),
229 mlir::StringAttr::get(rewriter.getContext(), entry))});
230
231 mlir::LLVM::NamedMetadataOp metaD;
232 for (auto md : mod.getOps<mlir::LLVM::NamedMetadataOp>()) {
233 if (md.getMetadataName() == name) {
234 metaD = md;
235 break;
236 }
237 }
238
239 if (!metaD) {
240 mlir::OpBuilder::InsertionGuard guard(rewriter);
241 rewriter.setInsertionPointToEnd(mod.getBody());
242 mlir::LLVM::NamedMetadataOp::create(rewriter, mod.getLoc(), name,
243 rewriter.getArrayAttr(node));
244 return;
245 }
246
247 if (onlyIfEmpty && !metaD.getNodes().empty())
248 return;
249 llvm::SmallVector<mlir::Attribute> nodes(metaD.getNodes().getValue());
250 nodes.push_back(node);
251 rewriter.modifyOpInPlace(
252 metaD, [&] { metaD.setNodesAttr(rewriter.getArrayAttr(nodes)); });
253}
254
255mlir::Value AMDGPUPrintfLowering::fitArgInto64Bits(mlir::Value arg) {
256 mlir::Type int64Ty = rewriter.getI64Type();
257 mlir::Type ty = arg.getType();
258
259 if (auto intTy = mlir::dyn_cast<mlir::IntegerType>(ty)) {
260 switch (intTy.getWidth()) {
261 case 32:
262 return mlir::LLVM::ZExtOp::create(rewriter, op.getLoc(), int64Ty, arg);
263 case 64:
264 return arg;
265 }
266 }
267
268 if (ty.isF64()) {
269 return mlir::LLVM::BitcastOp::create(rewriter, op.getLoc(), int64Ty, arg);
270 }
271
272 if (mlir::isa<mlir::LLVM::LLVMPointerType>(ty)) {
273 return mlir::LLVM::PtrToIntOp::create(rewriter, op.getLoc(), int64Ty, arg);
274 }
275
276 llvm_unreachable("unexpected type");
277}
278
279mlir::Value AMDGPUPrintfLowering::callPrintfBegin(mlir::Value version) {
280 return createCall("__ockl_printf_begin", rewriter.getI64Type(), version);
281}
282
283mlir::Value AMDGPUPrintfLowering::callAppendArgs(
284 mlir::Value desc, int numArgs, mlir::Value arg0, mlir::Value arg1,
285 mlir::Value arg2, mlir::Value arg3, mlir::Value arg4, mlir::Value arg5,
286 mlir::Value arg6, bool isLast) {
287 mlir::Value isLastValue = getI32(isLast);
288 mlir::Value numArgsValue = getI32(numArgs);
289 return createCall("__ockl_printf_append_args", rewriter.getI64Type(),
290 {desc, numArgsValue, arg0, arg1, arg2, arg3, arg4, arg5,
291 arg6, isLastValue});
292}
293
294mlir::Value AMDGPUPrintfLowering::appendArg(mlir::Value desc, mlir::Value arg,
295 bool isLast) {
296 mlir::Value arg0 = fitArgInto64Bits(arg);
297 mlir::Value zero = getI64(0);
298 return callAppendArgs(desc, 1, arg0, zero, zero, zero, zero, zero, zero,
299 isLast);
300}
301
302// The device library does not provide strlen, so we build our own loop
303// here. While we are at it, we also include the terminating null in the length.
304mlir::Value AMDGPUPrintfLowering::getStrlenWithNull(mlir::Value str) {
305 mlir::Block *prev = rewriter.getInsertionBlock();
306 mlir::Region *region = cont->getParent();
307
308 mlir::Value charZero = mlir::LLVM::ConstantOp::create(
309 rewriter, op.getLoc(), rewriter.getI8Type(), llvm::APInt(8, 0));
310 mlir::Value one = getI64(1);
311 mlir::Value zero = getI64(0);
312 mlir::Type int64Ty = rewriter.getI64Type();
313
314 // The length is either zero for a null pointer, or the computed value for an
315 // actual string. We need a join block for a phi that represents the final
316 // value.
317 //
318 // Strictly speaking, the zero does not matter since
319 // __ockl_printf_append_string_n ignores the length if the pointer is null.
320 mlir::Block *join = rewriter.createBlock(region, cont->getIterator(),
321 {int64Ty}, {op.getLoc()});
322 mlir::Block *whileBlock = rewriter.createBlock(
323 region, join->getIterator(), {str.getType()}, {op.getLoc()});
324 mlir::Block *whileDone = rewriter.createBlock(region, join->getIterator());
325
326 // Emit an early return for when the pointer is null.
327 rewriter.setInsertionPointToEnd(prev);
328 mlir::Value cmpNull = mlir::LLVM::ICmpOp::create(
329 rewriter, op.getLoc(), mlir::LLVM::ICmpPredicate::eq, str,
330 mlir::LLVM::ZeroOp::create(rewriter, op.getLoc(), str.getType()));
331 mlir::LLVM::CondBrOp::create(rewriter, op.getLoc(), cmpNull, join, zero,
332 whileBlock, str);
333
334 // Entry to the while loop.
335 rewriter.setInsertionPointToEnd(whileBlock);
336
337 mlir::Value ptrPhi = whileBlock->getArgument(0);
338 mlir::Value ptrNext = mlir::LLVM::GEPOp::create(
339 rewriter, op.getLoc(), str.getType(), rewriter.getI8Type(), ptrPhi,
340 llvm::ArrayRef<mlir::LLVM::GEPArg>{one});
341
342 // Condition for the while loop.
343 mlir::Value data = mlir::LLVM::LoadOp::create(rewriter, op.getLoc(),
344 rewriter.getI8Type(), ptrPhi);
345 mlir::Value cmp = mlir::LLVM::ICmpOp::create(
346 rewriter, op.getLoc(), mlir::LLVM::ICmpPredicate::eq, data, charZero);
347 mlir::LLVM::CondBrOp::create(rewriter, op.getLoc(), cmp, whileDone,
348 mlir::ValueRange(), whileBlock, ptrNext);
349
350 // Add one to the computed length.
351 rewriter.setInsertionPointToEnd(whileDone);
352 auto addrTy = mlir::IntegerType::get(
353 rewriter.getContext(), *dataLayout.getTypeIndexBitwidth(str.getType()));
354 mlir::Value endAddr =
355 mlir::LLVM::PtrToAddrOp::create(rewriter, op.getLoc(), addrTy, ptrPhi);
356 mlir::Value beginAddr =
357 mlir::LLVM::PtrToAddrOp::create(rewriter, op.getLoc(), addrTy, str);
358 mlir::Value len =
359 mlir::LLVM::SubOp::create(rewriter, op.getLoc(), endAddr, beginAddr);
360 if (addrTy != int64Ty)
361 len = mlir::LLVM::ZExtOp::create(rewriter, op.getLoc(), int64Ty, len);
362 len = mlir::LLVM::AddOp::create(rewriter, op.getLoc(), len, one);
363
364 // Final join.
365 mlir::LLVM::BrOp::create(rewriter, op.getLoc(), len, join);
366 rewriter.setInsertionPointToEnd(join);
367 return join->getArgument(0);
368}
369
370mlir::Value AMDGPUPrintfLowering::callAppendStringN(mlir::Value desc,
371 mlir::Value str,
372 mlir::Value length,
373 bool isLast) {
374 mlir::Value isLastInt32 = getI32(isLast);
375 // __ockl_printf_append_string_n takes a generic pointer, which can address a
376 // string in any address space.
377 auto ptrTy =
378 mlir::LLVM::LLVMPointerType::get(rewriter.getContext(), genericAddrSpace);
379 if (str.getType() != ptrTy)
380 str =
381 mlir::LLVM::AddrSpaceCastOp::create(rewriter, op.getLoc(), ptrTy, str);
382 return createCall("__ockl_printf_append_string_n", rewriter.getI64Type(),
383 {desc, str, length, isLastInt32});
384}
385
386mlir::Value AMDGPUPrintfLowering::appendString(mlir::Value desc,
387 mlir::Value arg, bool isLast) {
388 mlir::Value length = getStrlenWithNull(arg);
389 return callAppendStringN(desc, arg, length, isLast);
390}
391
392mlir::Value AMDGPUPrintfLowering::processArg(mlir::Value desc, mlir::Value arg,
393 bool specIsCString, bool isLast) {
394 if (specIsCString && mlir::isa<mlir::LLVM::LLVMPointerType>(arg.getType())) {
395 return appendString(desc, arg, isLast);
396 }
397 // If the format specifies a string but the argument is not, the frontend will
398 // have printed a warning. We just rely on undefined behaviour and send the
399 // argument anyway.
400 return appendArg(desc, arg, isLast);
401}
402
403// As in processArg, a non-pointer argument for a string is sent as a scalar.
404bool AMDGPUPrintfLowering::isCStringArg(mlir::ValueRange args, size_t i) const {
405 return specIsCString.test(i) &&
406 mlir::isa<mlir::LLVM::LLVMPointerType>(args[i].getType());
407}
408
409// Calculates frame size required for current printf expansion and allocates
410// space on printf buffer. Printf frame includes following contents
411// [ ControlDWord , format string/Hash , Arguments (each aligned to 8 byte) ]
412mlir::Value AMDGPUPrintfLowering::callBufferedPrintfStart(
413 mlir::ValueRange args, mlir::Value fmt, bool isConstFmtStr,
414 llvm::SmallVectorImpl<StringData> &stringContents, mlir::Value &argSize) {
415 mlir::Value nonConstStrLen;
416 mlir::Value lenWithNull;
417 mlir::Value lenWithNullAligned;
418 mlir::Value tempAdd;
419
420 // First 4 bytes to be reserved for control dword
421 size_t bufSize = 4;
422 if (isConstFmtStr)
423 // First 8 bytes of MD5 hash
424 bufSize += 8;
425 else {
426 lenWithNull = getStrlenWithNull(fmt);
427
428 // Align the computed length to next 8 byte boundary
429 tempAdd = mlir::LLVM::AddOp::create(rewriter, op.getLoc(), lenWithNull,
430 getI64(7U));
431 nonConstStrLen =
432 mlir::LLVM::AndOp::create(rewriter, op.getLoc(), tempAdd, getI64(~7U));
433
434 stringContents.push_back({"", lenWithNull, nonConstStrLen, false});
435 }
436
437 mlir::OperandRange cirArgs = op->getOperands();
438 for (size_t i = 1; i < args.size(); i++) {
439 if (isCStringArg(args, i)) {
440 std::string argStr;
441 if (getConstantStringInfo(cirArgs[i], mod, symbolTables, argStr)) {
442 auto alignedLen = llvm::alignTo(argStr.size() + 1, 8);
443 stringContents.push_back({argStr, /*realSize=*/{}, /*alignedSize=*/{},
444 /*isConst=*/true});
445 bufSize += alignedLen;
446 } else {
447 lenWithNull = getStrlenWithNull(args[i]);
448
449 // Align the computed length to next 8 byte boundary
450 tempAdd = mlir::LLVM::AddOp::create(rewriter, op.getLoc(), lenWithNull,
451 getI64(7U));
452 lenWithNullAligned = mlir::LLVM::AndOp::create(rewriter, op.getLoc(),
453 tempAdd, getI64(~7U));
454
455 if (nonConstStrLen) {
456 mlir::Value val = mlir::LLVM::AddOp::create(
457 rewriter, op.getLoc(), lenWithNullAligned, nonConstStrLen);
458 nonConstStrLen = val;
459 } else
460 nonConstStrLen = lenWithNullAligned;
461
462 stringContents.push_back({"", lenWithNull, lenWithNullAligned, false});
463 }
464 } else {
465 uint64_t allocSize =
466 dataLayout.getTypeAllocSize(args[i].getType()).getFixedValue();
467 // We end up expanding non string arguments to 8 bytes
468 // (args smaller than 8 bytes)
469 bufSize += llvm::alignTo(allocSize, 8);
470 }
471 }
472
473 // calculate final size value to be passed to printf_alloc
474 mlir::Value sizeToReserve = getI64(bufSize);
475 if (nonConstStrLen)
476 sizeToReserve = mlir::LLVM::AddOp::create(rewriter, op.getLoc(),
477 nonConstStrLen, sizeToReserve);
478
479 argSize = mlir::LLVM::TruncOp::create(rewriter, op.getLoc(),
480 rewriter.getI32Type(), sizeToReserve);
481
482 // call the printf_alloc function
483 unsigned globalAS = 0;
484 if (auto as = mlir::dyn_cast_if_present<mlir::IntegerAttr>(
485 dataLayout.getGlobalMemorySpace()))
486 globalAS = as.getValue().getZExtValue();
487 auto ptrTy =
488 mlir::LLVM::LLVMPointerType::get(rewriter.getContext(), globalAS);
489 auto allocName =
490 mlir::StringAttr::get(rewriter.getContext(), "__printf_alloc");
491 bool declared = symbolTables.lookupSymbolIn(mod, allocName);
492 mlir::Value ptr = createCall(allocName, ptrTy, argSize);
493 if (!declared)
494 mlir::cast<mlir::LLVM::LLVMFuncOp>(
495 symbolTables.lookupSymbolIn(mod, allocName))
496 .setNoUnwind(true);
497 return ptr;
498}
499
500// Prepare constant string argument to push onto the buffer
501void AMDGPUPrintfLowering::processConstantStringArg(
502 const StringData &sd, llvm::SmallVectorImpl<mlir::Value> &whatToStore) {
503 llvm::SmallVector<uint32_t, 16> words;
504 llvm::packAMDGPUPrintfConstantString(sd.str, words);
505 for (uint32_t word : words)
506 whatToStore.push_back(getI32(word));
507}
508
509mlir::Value AMDGPUPrintfLowering::processNonStringArg(mlir::Value arg) {
510 mlir::Type ty = arg.getType();
511
512 if (auto intTy = mlir::dyn_cast<mlir::IntegerType>(ty)) {
513 if (intTy.getWidth() < 64) {
514 return mlir::LLVM::ZExtOp::create(rewriter, op.getLoc(),
515 rewriter.getI64Type(), arg);
516 }
517 }
518
519 if (mlir::isa<mlir::FloatType>(ty)) {
520 if (dataLayout.getTypeAllocSize(ty).getFixedValue() < 8) {
521 return mlir::LLVM::FPExtOp::create(rewriter, op.getLoc(),
522 rewriter.getF64Type(), arg);
523 }
524 }
525
526 return arg;
527}
528
529void AMDGPUPrintfLowering::callBufferedPrintfArgPush(
530 mlir::ValueRange args, mlir::Value ptrToStore,
531 llvm::ArrayRef<StringData> stringContents, bool isConstFmtStr) {
532 const StringData *strIt = stringContents.begin();
533 size_t i = isConstFmtStr ? 1 : 0;
534 for (; i < args.size(); i++) {
535 if (i != 0 && !isCStringArg(args, i)) {
536 // The runtime reads each non-string argument from a slot of at least 8
537 // bytes, also when the stored value is smaller.
538 mlir::Value toStore = processNonStringArg(args[i]);
539 mlir::LLVM::StoreOp::create(rewriter, op.getLoc(), toStore, ptrToStore);
540 uint64_t allocSize =
541 dataLayout.getTypeAllocSize(toStore.getType()).getFixedValue();
542 ptrToStore = gepInBounds(ptrToStore, llvm::alignTo(allocSize, 8));
543 continue;
544 }
545
546 if (!strIt->isConst) {
547 // This copies the contents of the string, however the next offset
548 // is at aligned length, the extra space that might be created due
549 // to alignment padding is not populated with any specific value
550 // here. This would be safe as long as runtime is sync with
551 // the offsets.
552 auto memcpy = mlir::LLVM::MemcpyOp::create(
553 rewriter, op.getLoc(), ptrToStore, args[i], strIt->realSize,
554 /*isVolatile=*/false);
555 mlir::NamedAttribute align1(mlir::LLVM::LLVMDialect::getAlignAttrName(),
556 rewriter.getI64IntegerAttr(1));
557 memcpy.setArgAttrsAttr(
558 rewriter.getArrayAttr({rewriter.getDictionaryAttr(align1),
559 rewriter.getDictionaryAttr(align1),
560 rewriter.getDictionaryAttr({})}));
561
562 ptrToStore = gepInBounds(ptrToStore, strIt->alignedSize);
563
564 // done with current argument, move to next
565 strIt++;
566 continue;
567 }
568
569 llvm::SmallVector<mlir::Value, 32> whatToStore;
570 processConstantStringArg(*strIt, whatToStore);
571 strIt++;
572
573 for (mlir::Value toStore : whatToStore) {
574 mlir::LLVM::StoreOp::create(rewriter, op.getLoc(), toStore, ptrToStore);
575 ptrToStore = gepInBounds(
576 ptrToStore,
577 dataLayout.getTypeAllocSize(toStore.getType()).getFixedValue());
578 }
579 }
580}
581
582mlir::Value AMDGPUPrintfLowering::emitAMDGPUPrintfCall(mlir::ValueRange args,
583 bool isBuffered) {
584 size_t numOps = args.size();
585 assert(numOps >= 1);
586
587 mlir::Value fmt = args[0];
588
589 if (getConstantStringInfo(op.getFormat(), mod, symbolTables, fmtStr))
590 llvm::locateAMDGPUPrintfCStrings(specIsCString, fmtStr);
591
592 if (isBuffered) {
593 llvm::SmallVector<StringData, 8> stringContents;
594 bool isConstFmtStr = !fmtStr.empty();
595
596 mlir::Value argSize;
597 mlir::Value ptr = callBufferedPrintfStart(args, fmt, isConstFmtStr,
598 stringContents, argSize);
599
600 // The buffered version still follows OpenCL printf standards for
601 // printf return value, i.e 0 on success, -1 on failure.
602 mlir::Value cmp = mlir::LLVM::ICmpOp::create(
603 rewriter, op.getLoc(), mlir::LLVM::ICmpPredicate::ne, ptr,
604 mlir::LLVM::ZeroOp::create(rewriter, op.getLoc(), ptr.getType()));
605
606 // The rest of the op's block is the end block.
607 mlir::Block *end = cont;
608 mlir::Block *argPush =
609 rewriter.createBlock(cont->getParent(), cont->getIterator());
610
611 rewriter.setInsertionPointToEnd(cmp.getParentBlock());
612 mlir::LLVM::CondBrOp::create(rewriter, op.getLoc(), cmp, argPush, end);
613 rewriter.setInsertionPointToEnd(argPush);
614
615 // Create controlDWord and store as the first entry, format as follows
616 // Bit 0 (LSB) -> stream (1 if stderr, 0 if stdout, printf always outputs to
617 // stdout) Bit 1 -> constant format string (1 if constant) Bits 2-31 -> size
618 // of printf data frame
619 mlir::Value constantTwo = getI32(2);
620 mlir::Value controlDWord =
621 mlir::LLVM::ShlOp::create(rewriter, op.getLoc(), argSize, constantTwo);
622 if (isConstFmtStr)
623 controlDWord = mlir::LLVM::OrOp::create(rewriter, op.getLoc(),
624 controlDWord, constantTwo);
625
626 mlir::LLVM::StoreOp::create(rewriter, op.getLoc(), controlDWord, ptr);
627
628 ptr = gepInBounds(ptr, 4);
629
630 // Create MD5 hash for constant format string, push low 64 bits of the
631 // same onto buffer and metadata.
632 if (isConstFmtStr) {
633 addPrintfFormatMetadata(llvm::getAMDGPUPrintfFormatMetadata(fmtStr),
634 /*onlyIfEmpty=*/false);
635
636 mlir::LLVM::StoreOp::create(
637 rewriter, op.getLoc(),
638 getI64(llvm::getAMDGPUPrintfFormatHash(fmtStr)), ptr);
639 ptr = gepInBounds(ptr, 8);
640 } else {
641 // Include a dummy metadata instance in case of only non constant
642 // format string usage, This might be an absurd usecase but needs to
643 // be done for completeness
644 addPrintfFormatMetadata(llvm::AMDGPUPrintfNonConstFormatMetadata,
645 /*onlyIfEmpty=*/true);
646 }
647
648 // Push The printf arguments onto buffer
649 callBufferedPrintfArgPush(args, ptr, stringContents, isConstFmtStr);
650
651 // End block, returns -1 on failure
652 mlir::LLVM::BrOp::create(rewriter, op.getLoc(), end);
653 rewriter.setInsertionPointToStart(end);
654 mlir::Value notCmp = mlir::LLVM::XOrOp::create(
655 rewriter, op.getLoc(), cmp,
656 mlir::LLVM::ConstantOp::create(
657 rewriter, op.getLoc(), rewriter.getI1Type(), llvm::APInt(1, 1)));
658 return mlir::LLVM::SExtOp::create(rewriter, op.getLoc(),
659 rewriter.getI32Type(), notCmp);
660 }
661
662 mlir::Value desc = callPrintfBegin(getI64(0));
663 desc = appendString(desc, fmt, numOps == 1);
664
665 // FIXME: This invokes hostcall once for each argument. We can pack up to
666 // seven scalar printf arguments in a single hostcall. See the signature of
667 // callAppendArgs().
668 for (unsigned int i = 1; i != numOps; ++i) {
669 bool isLast = i == numOps - 1;
670 bool isCString = specIsCString.test(i);
671 desc = processArg(desc, args[i], isCString, isLast);
672 }
673
674 return mlir::LLVM::TruncOp::create(rewriter, op.getLoc(),
675 rewriter.getI32Type(), desc);
676}
677
678mlir::LogicalResult AMDGPUPrintfLowering::lower(mlir::ValueRange args,
679 bool isBuffered) {
680 // Unlike fitArgInto64Bits's unreachable, report a type hostcall cannot send.
681 if (!isBuffered) {
682 for (auto [cirArg, arg] : llvm::zip(op.getArgs(), args.drop_front())) {
683 mlir::Type ty = arg.getType();
684 if (ty.isInteger(32) || ty.isInteger(64) || ty.isF64() ||
685 mlir::isa<mlir::LLVM::LLVMPointerType>(ty))
686 continue;
687 return op.emitError() << "unsupported argument type " << cirArg.getType()
688 << " for AMDGPU printf";
689 }
690 }
691
692 mlir::Block *block = op->getBlock();
693 cont = rewriter.splitBlock(block, mlir::Block::iterator(op));
694 rewriter.setInsertionPointToEnd(block);
695
696 mlir::Value result = emitAMDGPUPrintfCall(args, isBuffered);
697 if (rewriter.getInsertionBlock() != cont)
698 mlir::LLVM::BrOp::create(rewriter, op.getLoc(), cont);
699
700 rewriter.replaceOp(op, result);
701 return mlir::success();
702}
703
704mlir::LogicalResult lowerAMDGPUPrintf(cir::OffloadPrintfOp op,
705 mlir::ValueRange operands,
706 mlir::ConversionPatternRewriter &rewriter,
707 const mlir::DataLayout &dataLayout,
708 mlir::SymbolTableCollection &symbolTables,
709 unsigned genericAddrSpace,
710 bool isBuffered) {
711 return AMDGPUPrintfLowering(op, rewriter, dataLayout, symbolTables,
712 genericAddrSpace)
713 .lower(operands, isBuffered);
714}
715
716} // namespace direct
717} // namespace cir
TokenType getType() const
Returns the token's type, e.g.
__DEVICE__ void * memcpy(void *__a, const void *__b, size_t __c)
mlir::LogicalResult lowerAMDGPUPrintf(cir::OffloadPrintfOp op, mlir::ValueRange operands, mlir::ConversionPatternRewriter &rewriter, const mlir::DataLayout &dataLayout, mlir::SymbolTableCollection &symbolTables, unsigned genericAddrSpace, bool isBuffered)
Lowers op, whose lowered operands are operands, to the buffered printf runtime interface if isBuffere...
void createLLVMFuncOpIfNotExist(mlir::ConversionPatternRewriter &rewriter, mlir::SymbolTableCollection &symbolTables, mlir::Operation *srcOp, llvm::StringRef fnName, mlir::Type fnTy, mlir::ArrayAttr argAttrs, mlir::ArrayAttr resAttrs)
Declare fnName with type fnTy before the function enclosing srcOp, unless the module already has a sy...
SetTy< T > join(SetTy< T > A, SetTy< T > B, typename SetTy< T >::Factory &F)
Computes the union of two ImmutableSets.
Definition Utils.h:49
RangeSelector node(std::string ID)
Selects a node, including trailing semicolon, if any (for declarations and non-expression statements)...
RangeSelector name(std::string ID)
Given a node with a "name", (like NamedDecl, DeclRefExpr, CxxCtorInitializer, and TypeLoc) selects th...
nullptr
This class represents a compute construct, representing a 'Kind' of ‘parallel’, 'serial',...
U cast(CodeGen::Address addr)
Definition Address.h:327
unsigned long uint64_t
__DEVICE__ _Tp arg(const std::complex< _Tp > &__c)
float __ovld __cnfn length(float)
Return the length of vector p, i.e., sqrt(p.x2 + p.y 2 + ...)
__builtin_bitreverse32 uint32_t __builtin_riscv_aaddu_u32 __builtin_riscv_asubu_u32 __builtin_elementwise_add_sat __builtin_elementwise_sub_sat int32_t uint32_t uint32_t __builtin_riscv_mhacc_i32 __builtin_riscv_mhaccu_u32 __builtin_riscv_mhaccsu_i32 __builtin_riscv_mulq_i32 int64_t