MLIR 24.0.0git
ACCAtomicPatterns.cpp
Go to the documentation of this file.
1//===- ACCAtomicPatterns.cpp - ACC atomic to LLVM patterns ------*- C++ -*-===//
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// Lowers OpenACC atomic operations (read, write, update, capture) to LLVM
10// dialect atomicrmw / cmpxchg sequences.
11//
12//===----------------------------------------------------------------------===//
13
15
24#include "mlir/IR/BuiltinOps.h"
25#include "llvm/ADT/TypeSwitch.h"
26#include "llvm/Support/Debug.h"
27
28#include <cstdint>
29#include <optional>
30#include <set>
31#include <utility>
32
33#define DEBUG_TYPE "acc-atomic-patterns"
34
35using namespace mlir;
36using namespace mlir::acc;
37
38namespace {
39
40constexpr uint64_t kBitsInByte = 8;
41
42template <typename AtomicOpTy>
43class ACCAtomicOpConversion : public ConvertOpToLLVMPattern<AtomicOpTy> {
45
46public:
47 ACCAtomicOpConversion(const LLVMTypeConverter &typeConverter,
48 OpenACCSupport &accSupport,
49 const ACCAtomicLoadAddressCallback &getLoadAddress)
51 accSupport(accSupport), getLoadAddress(getLoadAddress) {}
52
53 LogicalResult
54 matchAndRewrite(AtomicOpTy op, OpAdaptor adaptor,
55 ConversionPatternRewriter &rewriter) const override;
56
57private:
58 OpenACCSupport &accSupport;
59 ACCAtomicLoadAddressCallback getLoadAddress;
60
61 size_t getComplexStructElementSizeInBits(Type ty) const {
62 auto structType = dyn_cast<LLVM::LLVMStructType>(ty);
63 if (!structType || structType.getBody().size() != 2 ||
64 structType.getBody()[0] != structType.getBody()[1] ||
65 !structType.getBody()[0].isIntOrFloat())
66 return 0;
67 size_t elementSizeInBits = structType.getBody()[0].getIntOrFloatBitWidth();
68 if (elementSizeInBits == 0 || elementSizeInBits > 64)
69 llvm_unreachable("unexpected complex type");
70 return elementSizeInBits;
71 }
72
73 /// Serialize a complex value into an integer.
74 Value serializeExpr(Value expr, ConversionPatternRewriter &rewriter) const {
75 size_t elementSizeInBits =
76 getComplexStructElementSizeInBits(expr.getType());
77 if (!elementSizeInBits)
78 return expr;
79
80 Location loc = expr.getLoc();
81 Value firstValue = LLVM::ExtractValueOp::create(rewriter, loc, expr, 0);
82 Value secondValue = LLVM::ExtractValueOp::create(rewriter, loc, expr, 1);
83 MLIRContext *context = rewriter.getContext();
84 Type intCastTy = IntegerType::get(context, elementSizeInBits);
85 Type intTy = IntegerType::get(context, elementSizeInBits * 2);
86 firstValue = LLVM::BitcastOp::create(rewriter, loc, intCastTy, firstValue);
87 secondValue =
88 LLVM::BitcastOp::create(rewriter, loc, intCastTy, secondValue);
89 firstValue = LLVM::ZExtOp::create(rewriter, loc, intTy, firstValue);
90 secondValue = LLVM::ZExtOp::create(rewriter, loc, intTy, secondValue);
91 Value shlVal =
92 LLVM::ConstantOp::create(rewriter, loc, intTy, elementSizeInBits);
93 Value result = LLVM::ShlOp::create(rewriter, loc, secondValue, shlVal);
94 return LLVM::OrOp::create(rewriter, loc, result, firstValue);
95 }
96
97 /// Deserialize an integer into a complex value.
98 Value deserializeExpr(Value expr, Type origTy,
99 ConversionPatternRewriter &rewriter) const {
100 size_t elementSizeInBits = getComplexStructElementSizeInBits(origTy);
101 if (!elementSizeInBits)
102 return expr;
103
104 auto intTy = dyn_cast<IntegerType>(expr.getType());
105 if (!intTy || intTy.getWidth() != elementSizeInBits * 2)
106 return expr;
107
108 Location loc = expr.getLoc();
109 MLIRContext *context = rewriter.getContext();
110 Type elemTy = IntegerType::get(context, elementSizeInBits);
111 Value low = LLVM::TruncOp::create(rewriter, loc, elemTy, expr);
112 Value shiftAmount =
113 LLVM::ConstantOp::create(rewriter, loc, intTy, elementSizeInBits);
114 Value highFull = LLVM::LShrOp::create(rewriter, loc, expr, shiftAmount);
115 Value high = LLVM::TruncOp::create(rewriter, loc, elemTy, highFull);
116 Type origElemTy = cast<LLVM::LLVMStructType>(origTy).getBody()[0];
117 if (origElemTy != elemTy) {
118 low = LLVM::BitcastOp::create(rewriter, loc, origElemTy, low);
119 high = LLVM::BitcastOp::create(rewriter, loc, origElemTy, high);
120 }
121 Value undefStruct = LLVM::UndefOp::create(rewriter, loc, origTy);
122 Value structWithLow = LLVM::InsertValueOp::create(
123 rewriter, loc, origTy, undefStruct, low, ArrayRef<int64_t>{0});
124 return LLVM::InsertValueOp::create(rewriter, loc, origTy, structWithLow,
125 high, ArrayRef<int64_t>{1});
126 }
127
128 uint64_t getAtomicSizeInBytes(Type originalTy, Type convertedTy,
129 ModuleOp module) const {
130 std::optional<TypeSizeAndAlignment> sizeAndAlignment =
131 acc::getTypeSizeAndAlignment(originalTy, module, &accSupport);
132 if (!sizeAndAlignment)
133 sizeAndAlignment =
134 acc::getTypeSizeAndAlignment(convertedTy, module, &accSupport);
135 assert(sizeAndAlignment && "atomic type size is not computable");
136 return sizeAndAlignment->first.getFixedValue();
137 }
138
139 Type getAtomicType(Type originalTy, Type convertedTy, ModuleOp module) const {
140 if (convertedTy.isIntOrFloat())
141 return convertedTy;
142 return IntegerType::get(
143 convertedTy.getContext(),
144 getAtomicSizeInBytes(originalTy, convertedTy, module) * kBitsInByte);
145 }
146
147 Type getReferencedElementType(Value ref, ModuleOp module = nullptr) const {
148 auto ptr = dyn_cast<PointerLikeType>(ref.getType());
149 if (!ptr)
150 llvm_unreachable("unexpected type");
151 Type elementTy = ptr.getElementType();
152 Type convertedTy = this->getTypeConverter()->convertType(elementTy);
153 if (module)
154 return getAtomicType(elementTy, convertedTy, module);
155 return convertedTy;
156 }
157
158 /// Memrefs are converted to descriptors rather than bare pointers, so the
159 /// element pointer must be recomputed from the descriptor.
160 Value getAtomicPointer(Value originalRef, Value convertedPtr, Location loc,
161 ConversionPatternRewriter &rewriter) const {
162 auto memrefTy = dyn_cast<MemRefType>(originalRef.getType());
163 if (!memrefTy)
164 return convertedPtr;
165
166 // Extract aligned base pointer (index 1) and offset (index 2).
167 Value alignedPtr =
168 LLVM::ExtractValueOp::create(rewriter, loc, convertedPtr, 1);
169 Value offset = LLVM::ExtractValueOp::create(rewriter, loc, convertedPtr, 2);
170 Type elemPtrType = LLVM::LLVMPointerType::get(rewriter.getContext());
171 return LLVM::GEPOp::create(
172 rewriter, loc, elemPtrType,
173 this->getTypeConverter()->convertType(memrefTy.getElementType()),
174 alignedPtr, offset);
175 }
176
177 Block *constructCmpxchgLoop(Value ptr, Type type, Value expr,
178 ConversionPatternRewriter &rewriter) const;
179
180 Value genUpdateCmpxchgLoop(AtomicUpdateOp update,
181 ConversionPatternRewriter &rewriter) const;
182};
183
184template <>
185LogicalResult ACCAtomicOpConversion<AtomicReadOp>::matchAndRewrite(
186 AtomicReadOp read, OpAdaptor adaptor,
187 ConversionPatternRewriter &rewriter) const {
188 Location loc = read.getLoc();
189 Value xRef = read.getX();
190 Value xPtr = getAtomicPointer(xRef, adaptor.getX(), loc, rewriter);
191 ModuleOp mod = read->getParentOfType<ModuleOp>();
192 Type xType = getReferencedElementType(xRef, mod);
193
194 auto ordering = LLVM::AtomicOrdering::monotonic;
195 Value storeVal;
196 if (xType.isSignlessInteger()) {
197 Value zero = LLVM::ConstantOp::create(rewriter, loc, xType, 0);
198 storeVal = LLVM::AtomicRMWOp::create(rewriter, loc, LLVM::AtomicBinOp::_or,
199 xPtr, zero, ordering);
200 } else {
201 unsigned bitWidth = xType.getIntOrFloatBitWidth();
202 Type intType = IntegerType::get(rewriter.getContext(), bitWidth);
203 Value zero = LLVM::ConstantOp::create(rewriter, loc, intType, 0);
204 Value intVal = LLVM::AtomicRMWOp::create(
205 rewriter, loc, LLVM::AtomicBinOp::_or, xPtr, zero, ordering);
206 storeVal = LLVM::BitcastOp::create(rewriter, loc, xType, intVal);
207 }
208
209 Value vRef = read.getV();
210 Value vPtr = getAtomicPointer(vRef, adaptor.getV(), loc, rewriter);
211 Type vType = getReferencedElementType(vRef, mod);
212
213 if (xType != vType) {
214 // Convert `x` if the types do not match.
215 auto vPtrType = cast<PointerLikeType>(vRef.getType());
216 storeVal = vPtrType.genCast(rewriter, loc, storeVal, vType);
217 if (!storeVal) {
218 return rewriter.notifyMatchFailure(
219 read, "failed to convert the loaded value to the destination type");
220 }
221 }
222 auto storeOp = LLVM::StoreOp::create(rewriter, loc, storeVal, vPtr);
223 rewriter.replaceOp(read, storeOp);
224 return success();
225}
226
227template <>
228LogicalResult ACCAtomicOpConversion<AtomicWriteOp>::matchAndRewrite(
229 AtomicWriteOp write, OpAdaptor adaptor,
230 ConversionPatternRewriter &rewriter) const {
231 Location loc = write.getLoc();
232 Value expr = serializeExpr(adaptor.getExpr(), rewriter);
233 Value xRef = write.getX();
234 Value xPtr = getAtomicPointer(xRef, adaptor.getX(), loc, rewriter);
235 ModuleOp mod = write->getParentOfType<ModuleOp>();
236 Type xType = getReferencedElementType(xRef, mod);
237
238 auto ordering = LLVM::AtomicOrdering::monotonic;
239 if (!xType.isSignlessInteger()) {
240 unsigned bitWidth = xType.getIntOrFloatBitWidth();
241 Type intType = IntegerType::get(rewriter.getContext(), bitWidth);
242 expr = LLVM::BitcastOp::create(rewriter, loc, intType, expr);
243 }
244 LLVM::AtomicRMWOp::create(rewriter, loc, LLVM::AtomicBinOp::xchg, xPtr, expr,
245 ordering);
246 rewriter.eraseOp(write);
247 return success();
248}
249
250// Generate a cmpxchg loop based on the GenericAtomicRMWOpLowering algorithm.
251template <typename AtomicOpTy>
252Block *ACCAtomicOpConversion<AtomicOpTy>::constructCmpxchgLoop(
253 Value ptr, Type type, Value expr,
254 ConversionPatternRewriter &rewriter) const {
255 // Split the block into initial, loop, and ending parts.
256 Location loc = rewriter.getInsertionPoint()->getLoc();
257 Block *initBlock = rewriter.getInsertionBlock();
258 Block *loopBlock =
259 rewriter.splitBlock(initBlock, rewriter.getInsertionPoint());
260 loopBlock->addArgument(type, loc);
261 Block *endBlock =
262 rewriter.splitBlock(loopBlock, rewriter.getInsertionPoint());
263
264 // Compute the loaded value and branch to the loop block.
265 rewriter.setInsertionPointToEnd(initBlock);
266 Value init = LLVM::LoadOp::create(rewriter, loc, type, ptr);
267 LLVM::BrOp::create(rewriter, loc, init, loopBlock);
268
269 // Prepare the body of the loop block.
270 rewriter.setInsertionPointToStart(loopBlock);
271
272 Value loopArgument = loopBlock->getArgument(0);
273 Value result = serializeExpr(rewriter.getRemappedValue(expr), rewriter);
274 ModuleOp mod = initBlock->getParent()->getParentOfType<ModuleOp>();
275 Type convertedExprType =
276 this->getTypeConverter()->convertType(expr.getType());
277 Type exprType = getAtomicType(expr.getType(), convertedExprType, mod);
278
279 // Cast to an integer type.
280 if (!exprType.isSignlessInteger()) {
281 Type tmpType = IntegerType::get(
282 rewriter.getContext(),
283 getAtomicSizeInBytes(expr.getType(), convertedExprType, mod) *
285 result = LLVM::BitcastOp::create(rewriter, loc, tmpType, result);
286 loopArgument =
287 LLVM::BitcastOp::create(rewriter, loc, tmpType, loopArgument);
288 }
289
290 // Prepare the epilog of the loop block.
291 // Append the cmpxchg op to the end of the loop block.
292 auto successOrdering = LLVM::AtomicOrdering::acq_rel;
293 auto failureOrdering = LLVM::AtomicOrdering::monotonic;
294 auto cmpxchg =
295 LLVM::AtomicCmpXchgOp::create(rewriter, loc, ptr, loopArgument, result,
296 successOrdering, failureOrdering);
297 // Extract the %new_loaded and %ok values from the pair.
298 Value newLoaded = LLVM::ExtractValueOp::create(rewriter, loc, cmpxchg, 0);
299 Value ok = LLVM::ExtractValueOp::create(rewriter, loc, cmpxchg, 1);
300
301 // Cast back to the original type.
302 if (!exprType.isSignlessInteger())
303 newLoaded = LLVM::BitcastOp::create(rewriter, loc, exprType, newLoaded);
304
305 // Conditionally branch to the end or back to the loop depending on %ok.
306 LLVM::CondBrOp::create(rewriter, loc, ok, endBlock, ArrayRef<Value>(),
307 loopBlock, newLoaded);
308
309 return loopBlock;
310}
311
312static Value skipUnrealizedConversionOp(Value v) {
313 if (auto convOp =
314 dyn_cast_or_null<UnrealizedConversionCastOp>(v.getDefiningOp()))
315 return skipUnrealizedConversionOp(convOp.getOperand(0));
316 return v;
317}
318
319/// Obtain the foremost value of addr to indicate the origin of the storage.
320static Value getBaseStorage(Value addr, ConversionPatternRewriter &rewriter) {
321 Operation *op = skipUnrealizedConversionOp(addr).getDefiningOp();
322 if (auto gepOp = dyn_cast_or_null<LLVM::GEPOp>(op)) {
323 addr = gepOp.getBase();
324 op = addr.getDefiningOp();
325 }
326 if (auto extractOp = dyn_cast_or_null<LLVM::ExtractValueOp>(op)) {
327 // addr is in a struct. Get the inserted value corresponding to the
328 // extraction.
329 ArrayRef extractingPosition = extractOp.getPosition();
330 Value container = skipUnrealizedConversionOp(extractOp.getContainer());
331 Value remappedContainer = rewriter.getRemappedValue(container);
332 op = remappedContainer ? remappedContainer.getDefiningOp() : nullptr;
333 bool inserted = false;
334 while (auto insertValueOp = dyn_cast_or_null<LLVM::InsertValueOp>(op)) {
335 ArrayRef insertingPosition = insertValueOp.getPosition();
336 if (insertingPosition == extractingPosition) {
337 addr = insertValueOp.getValue();
338 inserted = true;
339 break;
340 }
341 op = insertValueOp.getContainer().getDefiningOp();
342 }
343 // The aggregate holding addr is not built by insertions, so the value it
344 // is converted from identifies the storage.
345 if (!inserted)
346 addr = container;
347 }
348 // op might be alloca already, or addr might be an argument.
349 return skipUnrealizedConversionOp(addr);
350}
351
352/// Include flow dependency (v -> expr) in the generated loop of
353/// `{ atomic.read, atomic.write/update }`.
354static void moveDependency(Value vRef, Value vPtr, Value expr,
355 Value loopArgument, Operation &loopHead,
356 ConversionPatternRewriter &rewriter,
357 const ACCAtomicLoadAddressCallback &getLoadAddress) {
358 Value vStorage = getBaseStorage(vPtr, rewriter);
359 // A dependency may be reached before it is itself converted, in which case
360 // its address is still expressed in terms of `vRef`.
361 Value vStorageRef = skipUnrealizedConversionOp(vRef);
362 LLVM_DEBUG({
363 llvm::dbgs() << "[acc-atomic] moveDependency\n";
364 llvm::dbgs() << " vPtr = " << vPtr << "\n";
365 llvm::dbgs() << " vStorage = " << vStorage << "\n";
366 llvm::dbgs() << " vStorageRef = " << vStorageRef << "\n";
367 llvm::dbgs() << " expr = " << expr << "\n";
368 });
369 llvm::DenseMap<Value, Value> remappedToOriginal;
370 std::set<std::pair<Operation *, std::set<Operation *>>> worklist;
371 std::set<Operation *> included;
372 Value mappedExpr = rewriter.getRemappedValue(expr);
373 remappedToOriginal[mappedExpr] = expr;
374 if (Operation *exprDef = mappedExpr.getDefiningOp())
375 worklist.insert(std::pair{exprDef, std::set<Operation *>{}});
376
377 while (!worklist.empty()) {
378 auto [dep, post] = worklist.extract(worklist.begin()).value();
379 if (!dep)
380 continue;
381 LLVM_DEBUG(llvm::dbgs() << " visit dep = " << *dep << "\n");
382 if (vPtr.getDefiningOp() &&
383 !vPtr.getDefiningOp()->getParentOp()->isAncestor(dep)) {
384 // Outside the parental region. No load found in this flow.
385 LLVM_DEBUG(llvm::dbgs() << " -> skipped: not in parental region\n");
386 continue;
387 }
388
389 Value addr;
390 if (auto load = dyn_cast<LLVM::LoadOp>(dep)) {
391 addr = load.getAddr();
392 } else if (auto load = dyn_cast<memref::LoadOp>(dep)) {
393 addr = load.getMemref();
394 } else if (getLoadAddress) {
395 addr = getLoadAddress(dep);
396 }
397 if (addr) {
398 Value baseStorage = getBaseStorage(addr, rewriter);
399 LLVM_DEBUG({
400 llvm::dbgs() << " addr = " << addr << "\n";
401 llvm::dbgs() << " baseStorage = " << baseStorage << "\n";
402 llvm::dbgs() << " remapped = "
403 << rewriter.getRemappedValue(baseStorage) << "\n";
404 llvm::dbgs() << " matchRef=" << (baseStorage == vStorageRef)
405 << " matchConv="
406 << (rewriter.getRemappedValue(baseStorage) == vStorage)
407 << "\n";
408 });
409 if (baseStorage == vStorageRef ||
410 rewriter.getRemappedValue(baseStorage) == vStorage) {
411 // Found the load of `v`. Include this flow in dependency.
412 Value load = dep->getResult(0);
413 auto replaceUses = [&](Operation *op) {
414 rewriter.modifyOpInPlace(op, [&] {
415 op->replaceUsesOfWith(remappedToOriginal[load], loopArgument);
416 });
417 };
418 for (Operation *p : post)
419 replaceUses(p);
420 replaceUses(&loopHead);
421 included.insert(post.begin(), post.end());
422 }
423 continue;
424 }
425 post.insert(dep);
426 for (Value operand : dep->getOperands()) {
427 Value mappedOperand = rewriter.getRemappedValue(operand);
428 if (auto *d = mappedOperand.getDefiningOp()) {
429 if (dep == d) {
430 if (auto *op = operand.getDefiningOp()) {
431 remappedToOriginal[op->getResult(0)] = operand;
432 worklist.insert(std::pair{op, post});
433 }
434 } else {
435 remappedToOriginal[mappedOperand] = operand;
436 worklist.insert(std::pair{d, post});
437 }
438 }
439 }
440 }
441
442 // Include dependency.
443 SmallVector<Operation *> includedInOrder;
444 vPtr.getParentRegion()->walk([&](Operation *op) {
445 if (included.find(op) != included.end())
446 includedInOrder.push_back(op);
447 });
448 for (Operation *d : includedInOrder)
449 d->moveBefore(&loopHead);
450}
451
452/// Generate a cmpxchg loop for update and return a stored value.
453template <typename AtomicOpTy>
454Value ACCAtomicOpConversion<AtomicOpTy>::genUpdateCmpxchgLoop(
455 AtomicUpdateOp update, ConversionPatternRewriter &rewriter) const {
456 Location loc = update.getLoc();
457 Value xRef = update.getX();
458 Value xPtr =
459 getAtomicPointer(xRef, rewriter.getRemappedValue(xRef), loc, rewriter);
460 ModuleOp mod = update->getParentOfType<ModuleOp>();
461 Type xType = getReferencedElementType(xRef, mod);
462 Type xTypeOrig = getReferencedElementType(xRef);
463
464 Block &updateBlock = update.getRegion().front();
465 Value updateArgument = updateBlock.getArgument(0);
466 Operation *terminator = updateBlock.getTerminator();
467 Value expr = terminator->getOperand(0);
468
469 Block *loopBlock = constructCmpxchgLoop(xPtr, xType, expr, rewriter);
470 Value loopArgument = loopBlock->getArgument(0);
471 Operation &loopHead = loopBlock->front();
472
473 rewriter.setInsertionPointToStart(loopBlock);
474 loopArgument = deserializeExpr(loopArgument, xTypeOrig, rewriter);
475
476 // Move in and out flow dependency (x -> expr). Some computation might be
477 // outside atomic regions.
478 moveDependency(xRef, xPtr, expr, loopArgument, loopHead, rewriter,
479 getLoadAddress);
480 // Move out the residue.
481 rewriter.replaceAllUsesWith(cast<BlockArgument>(updateArgument),
482 {loopArgument});
483
484 updateBlock.walk([&](Operation *op) {
486 rewriter.moveOpBefore(op, &loopHead);
487 });
488
489 if (auto cmpxchg = dyn_cast<LLVM::AtomicCmpXchgOp>(loopHead))
490 return cmpxchg.getVal();
491 if (auto bitcast = dyn_cast<LLVM::BitcastOp>(loopHead))
492 // Handling a non-integer type.
493 return bitcast.getArg();
494 if (auto extract = dyn_cast<LLVM::ExtractValueOp>(loopHead))
495 // Handling a complex type.
496 return extract.getContainer();
497 llvm_unreachable("invalid cmpxchg loop");
498}
499
500/// Generate llvm.atomicrmw or an llvm.cmpxchg loop.
501template <>
502LogicalResult ACCAtomicOpConversion<AtomicUpdateOp>::matchAndRewrite(
503 AtomicUpdateOp update, OpAdaptor adaptor,
504 ConversionPatternRewriter &rewriter) const {
505 Block &updateBlock = update.getRegion().front();
506 Value updateArgument = updateBlock.getArgument(0);
507
508 // Collect operations that depend on the update argument.
509 std::set<Operation *> dependents;
510 SmallVector<Value> worklist;
511 worklist.push_back(updateArgument);
512 while (!worklist.empty()) {
513 Value value = worklist.back();
514 worklist.pop_back();
515 for (OpOperand &use : value.getUses()) {
516 Operation *useOp = use.getOwner();
517 dependents.insert(useOp);
518 if (useOp->getNumResults() == 1)
519 worklist.push_back(useOp->getResult(0));
520 }
521 }
522
523 // Move independent operations out of the update block.
524 SmallVector<Operation *> independent;
525 for (Operation &op : updateBlock.getOperations()) {
526 if (dependents.find(&op) == dependents.end()) {
527 if (op.hasTrait<OpTrait::IsTerminator>())
528 llvm_unreachable("invalid update operation");
529 independent.push_back(&op);
530 }
531 }
532 for (Operation *op : independent)
533 rewriter.moveOpBefore(op, update);
534
535 // Map arith op to atomicrmw kind.
536 //
537 // In the current version of LLVM, these are the equivalences for float
538 // min/max across the different instructions intrinsics and operations -
539 // confusingly they have slightly different and not very descriptive names.
540 //
541 // | MLIR | atomicrmw inst | llvm intrinsic |
542 // |------------+----------------+-------------------|
543 // | - | fmax | llvm.maxnum.* |
544 // | MaximumFOp | fmaximum | llvm.maximum.* |
545 // | MaxNumFOp | fmaximumnum | llvm.maximumnum.* |
546 //
547 // Sources:
548 // https://llvm.org/docs/LangRef.html#id236
549 // https://llvm.org/docs/LangRef.html#floating-point-min-max-intrinsics-comparison
550 // https://mlir.llvm.org/docs/Dialects/ArithOps/#arithmaximumf-arithmaximumfop
551 // https://mlir.llvm.org/docs/Dialects/ArithOps/#arithmaxnumf-arithmaxnumfop
552 auto getAtomicBinOp =
553 [](Operation *op, bool updateIsLhs) -> std::optional<LLVM::AtomicBinOp> {
555 .Case<arith::AddFOp>([](auto) { return LLVM::AtomicBinOp::fadd; })
556 .Case<arith::AddIOp>([](auto) { return LLVM::AtomicBinOp::add; })
557 .Case<arith::SubFOp>(
558 [updateIsLhs](auto) -> std::optional<LLVM::AtomicBinOp> {
559 // atomicrmw fsub is always `*ptr = *ptr - val`.
560 if (!updateIsLhs)
561 return std::nullopt;
562 return LLVM::AtomicBinOp::fsub;
563 })
564 .Case<arith::SubIOp>(
565 [updateIsLhs](auto) -> std::optional<LLVM::AtomicBinOp> {
566 // atomicrmw sub is always `*ptr = *ptr - val`.
567 if (!updateIsLhs)
568 return std::nullopt;
569 return LLVM::AtomicBinOp::sub;
570 })
571 .Case<arith::AndIOp>([](auto) { return LLVM::AtomicBinOp::_and; })
572 .Case<arith::OrIOp>([](auto) { return LLVM::AtomicBinOp::_or; })
573 .Case<arith::XOrIOp>([](auto) { return LLVM::AtomicBinOp::_xor; })
574 .Case<arith::MaxSIOp>([](auto) { return LLVM::AtomicBinOp::max; })
575 .Case<arith::MinSIOp>([](auto) { return LLVM::AtomicBinOp::min; })
576 .Case<arith::MaxUIOp>([](auto) { return LLVM::AtomicBinOp::umax; })
577 .Case<arith::MinUIOp>([](auto) { return LLVM::AtomicBinOp::umin; })
578 .Case<arith::MaximumFOp>(
579 [](auto) { return LLVM::AtomicBinOp::fmaximum; })
580 .Case<arith::MinimumFOp>(
581 [](auto) { return LLVM::AtomicBinOp::fminimum; })
582 .Case<arith::MaxNumFOp>(
583 [](auto) { return LLVM::AtomicBinOp::fmaximumnum; })
584 .Case<arith::MinNumFOp>(
585 [](auto) { return LLVM::AtomicBinOp::fminimumnum; })
586 .Default([](Operation *) { return std::nullopt; });
587 };
588
589 // Select the kind and the val of atomicrmw.
590 std::optional<Value> val = std::nullopt;
591 std::optional<LLVM::AtomicBinOp> kind = std::nullopt;
592
593 auto &ops = updateBlock.getOperations();
594 Operation &firstOp = ops.front();
595 Operation &yield = ops.back();
596
597 if (dependents.size() == 2 && firstOp.getResult(0) == yield.getOperand(0)) {
598 bool updateIsLhs = firstOp.getOperand(0) == updateArgument;
599 kind = getAtomicBinOp(&firstOp, updateIsLhs);
600 if (kind)
601 val = firstOp.getOperand(updateIsLhs ? 1 : 0);
602 }
603
604 // Per-component atomicrmw info for complex type decomposition.
605 // Decomposed complex ops (complex.re/im + arith binop + complex.create)
606 // produce per-component binary ops that each need a separate atomicrmw.
607 struct ComponentAtomic {
608 LLVM::AtomicBinOp kind;
609 Value val;
610 int32_t fieldIdx;
611 };
612 SmallVector<ComponentAtomic, 2> componentAtomics;
613
614 if (!val || !kind) {
615 Type convertedArgTy =
616 this->getTypeConverter()->convertType(updateArgument.getType());
617 if (auto structTy = dyn_cast<LLVM::LLVMStructType>(convertedArgTy)) {
618 if (structTy.getBody().size() == 2 &&
619 structTy.getBody()[0] == structTy.getBody()[1] &&
620 structTy.getBody()[0].isIntOrFloat() &&
621 structTy.getBody()[0].getIntOrFloatBitWidth() > 32) {
622 for (Operation &op : updateBlock.getOperations()) {
623 if (op.hasTrait<OpTrait::IsTerminator>() || op.getNumOperands() < 2)
624 continue;
625 int32_t fieldIdx = -1;
626 Value externalVal = nullptr;
627 bool updateIsLhs = false;
628 for (unsigned i = 0; i < 2; ++i) {
629 Value operand = op.getOperand(i);
630 if (auto reOp = operand.getDefiningOp<complex::ReOp>()) {
631 if (reOp.getOperand() == updateArgument) {
632 fieldIdx = 0;
633 externalVal = op.getOperand(1 - i);
634 updateIsLhs = i == 0;
635 }
636 } else if (auto imOp = operand.getDefiningOp<complex::ImOp>()) {
637 if (imOp.getOperand() == updateArgument) {
638 fieldIdx = 1;
639 externalVal = op.getOperand(1 - i);
640 updateIsLhs = i == 0;
641 }
642 }
643 }
644 if (fieldIdx < 0)
645 continue;
646 auto componentKind = getAtomicBinOp(&op, updateIsLhs);
647 if (!componentKind)
648 continue;
649 componentAtomics.push_back({*componentKind, externalVal, fieldIdx});
650 }
651 }
652 }
653 }
654
655 Location loc = update.getLoc();
656 Value xPtr = getAtomicPointer(update.getX(), adaptor.getX(), loc, rewriter);
657
658 // Require distinct real/imag lanes; duplicate fieldIdx values must fall back
659 // to cmpxchg rather than emitting two atomicrmw ops on the same component.
660 bool hasDistinctComplexLanes = false;
661 if (componentAtomics.size() == 2) {
662 unsigned lanes = 0;
663 for (const ComponentAtomic &ca : componentAtomics) {
664 if (ca.fieldIdx == 0 || ca.fieldIdx == 1)
665 lanes |= 1u << ca.fieldIdx;
666 }
667 hasDistinctComplexLanes = lanes == 0b11;
668 }
669
670 if (val && kind) {
671 auto ordering = LLVM::AtomicOrdering::monotonic;
672 LLVM::AtomicRMWOp::create(rewriter, loc, *kind, xPtr,
673 rewriter.getRemappedValue(*val), ordering);
674 } else if (hasDistinctComplexLanes) {
675 auto structTy = cast<LLVM::LLVMStructType>(
676 this->getTypeConverter()->convertType(updateArgument.getType()));
677 Type ptrType = LLVM::LLVMPointerType::get(rewriter.getContext());
678 auto ordering = LLVM::AtomicOrdering::monotonic;
679 for (ComponentAtomic &ca : componentAtomics) {
680 Value elemPtr =
681 LLVM::GEPOp::create(rewriter, loc, ptrType, structTy, xPtr,
682 ArrayRef<LLVM::GEPArg>{0, ca.fieldIdx});
683 LLVM::AtomicRMWOp::create(rewriter, loc, ca.kind, elemPtr,
684 rewriter.getRemappedValue(ca.val), ordering);
685 }
686 } else {
687 // Fallback to the llvm.cmpxchg loop generation.
688 genUpdateCmpxchgLoop(update, rewriter);
689 }
690 rewriter.eraseOp(update);
691 return success();
692}
693
694/// Generate an llvm.cmpxchg loop.
695template <>
696LogicalResult ACCAtomicOpConversion<AtomicCaptureOp>::matchAndRewrite(
697 AtomicCaptureOp capture, OpAdaptor /*adaptor*/,
698 ConversionPatternRewriter &rewriter) const {
699 Operation *firstOp = capture.getFirstOp();
700 Operation *secondOp = capture.getSecondOp();
701 Value vPtr = nullptr;
702 Value storeVal = nullptr;
703 if (auto firstReadStmt = dyn_cast<AtomicReadOp>(firstOp)) {
704 Location loc = capture.getLoc();
705 Value xRef = firstReadStmt.getX();
706 Value xPtr =
707 getAtomicPointer(xRef, rewriter.getRemappedValue(xRef), loc, rewriter);
708 ModuleOp mod = capture->getParentOfType<ModuleOp>();
709 Type xType = getReferencedElementType(xRef, mod);
710 Type xTypeOrig = getReferencedElementType(xRef);
711 Value vRef = firstReadStmt.getV();
712 vPtr =
713 getAtomicPointer(vRef, rewriter.getRemappedValue(vRef), loc, rewriter);
714
715 Value expr = nullptr;
716 if (auto secondWriteStmt = dyn_cast<AtomicWriteOp>(secondOp)) {
717 // 1. `{ atomic.read, atomic.write }` pattern
718 expr = secondWriteStmt.getExpr();
719 } else if (auto secondUpdateStmt = dyn_cast<AtomicUpdateOp>(secondOp)) {
720 // 2. `{ atomic.read, atomic.update }` pattern
721 Block &updateBlock = secondUpdateStmt.getRegion().front();
722 Operation *terminator = updateBlock.getTerminator();
723 expr = terminator->getOperand(0);
724 }
725
726 Block *loopBlock = constructCmpxchgLoop(xPtr, xType, expr, rewriter);
727 Value loopArgument = loopBlock->getArgument(0);
728 Operation &loopHead = loopBlock->front();
729 auto condBr = cast<LLVM::CondBrOp>(loopBlock->back());
730 storeVal = condBr.getFalseDestOperands()[0];
731
732 rewriter.setInsertionPointToStart(loopBlock);
733 loopArgument = deserializeExpr(loopArgument, xTypeOrig, rewriter);
734
735 // Include flow dependency (v -> expr).
736 if (auto secondUpdateStmt = dyn_cast<AtomicUpdateOp>(secondOp)) {
737 Block &updateBlock = secondUpdateStmt.getRegion().front();
738 Value updateArgument = updateBlock.getArgument(0);
739 updateArgument.replaceAllUsesWith(loopArgument);
740 updateBlock.walk([&](Operation *op) {
742 rewriter.moveOpBefore(op, &loopHead);
743 });
744 }
745 moveDependency(vRef, vPtr, expr, loopArgument, loopHead, rewriter,
746 getLoadAddress);
747 } else if (auto firstUpdateStmt = dyn_cast<AtomicUpdateOp>(firstOp)) {
748 if (auto secondReadStmt = dyn_cast<AtomicReadOp>(secondOp)) {
749 // 3. `{ atomic.update, atomic.read }` pattern
750 storeVal = genUpdateCmpxchgLoop(firstUpdateStmt, rewriter);
751
752 Value vRef = secondReadStmt.getV();
753 vPtr = getAtomicPointer(vRef, rewriter.getRemappedValue(vRef),
754 capture.getLoc(), rewriter);
755 }
756 }
757 // Generate `v = x`.
758 rewriter.setInsertionPoint(capture);
759 rewriter.replaceOpWithNewOp<LLVM::StoreOp>(capture, storeVal, vPtr);
760 return success();
761}
762
763} // namespace
764
765namespace mlir {
766
768 target.addIllegalOp<AtomicReadOp, AtomicWriteOp, AtomicUpdateOp,
769 AtomicCaptureOp>();
770}
771
773 RewritePatternSet &patterns,
774 acc::OpenACCSupport &accSupport,
775 ACCAtomicLoadAddressCallback getLoadAddress) {
776 patterns.add<ACCAtomicOpConversion<AtomicReadOp>,
777 ACCAtomicOpConversion<AtomicWriteOp>,
778 ACCAtomicOpConversion<AtomicUpdateOp>,
779 ACCAtomicOpConversion<AtomicCaptureOp>>(converter, accSupport,
780 getLoadAddress);
781}
782
783} // namespace mlir
return success()
constexpr static const uint64_t kBitsInByte
Definition LLVMTypes.cpp:30
auto load
*if copies could not be generated due to yet unimplemented cases *copyInPlacementStart and copyOutPlacementStart in copyPlacementBlock *specify the insertion points where the incoming copies and outgoing should be inserted(the insertion happens right before the *insertion point). Since `begin` can itself be invalidated due to the memref *rewriting done from this method
Block represents an ordered list of Operations.
Definition Block.h:33
BlockArgument getArgument(unsigned i)
Definition Block.h:153
Region * getParent() const
Provide a 'getParent' method for ilist_node_with_parent methods.
Definition Block.cpp:27
OpListType & getOperations()
Definition Block.h:161
Operation & front()
Definition Block.h:177
Operation & back()
Definition Block.h:176
RetT walk(FnT &&callback)
Walk all nested operations, blocks (including this block) or regions, depending on the type of callba...
Definition Block.h:332
Operation * getTerminator()
Get the terminator operation of this block.
Definition Block.cpp:249
BlockArgument addArgument(Type type, Location loc)
Add one value to the argument list.
Definition Block.cpp:158
Utility class for operation conversions targeting the LLVM dialect that match exactly one source oper...
Definition Pattern.h:227
typename SourceOp::Adaptor OpAdaptor
Definition Pattern.h:229
Conversion from types to the LLVM IR dialect.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
This class represents an operand of an operation.
Definition Value.h:254
This class provides the API for ops that are known to be terminators.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Value getOperand(unsigned idx)
Definition Operation.h:375
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
Definition Operation.h:794
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:251
unsigned getNumOperands()
Definition Operation.h:371
bool isAncestor(Operation *other)
Return true if this operation is an ancestor of the other operation.
Definition Operation.h:288
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:429
ParentT getParentOfType()
Find the first parent operation of the given type, or nullptr if there is no ancestor operation.
Definition Region.h:221
RetT walk(FnT &&callback)
Walk all nested operations, blocks or regions (including this region), depending on the type of callb...
Definition Region.h:312
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
MLIRContext * getContext() const
Return the MLIRContext in which this type was uniqued.
Definition Types.cpp:35
bool isSignlessInteger() const
Return true if this is a signless integer type (with the specified width).
Definition Types.cpp:66
bool isIntOrFloat() const
Return true if this is an integer (of any signedness) or a float type.
Definition Types.cpp:118
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
use_range getUses() const
Returns a range of all uses, which is useful for iterating over all uses.
Definition Value.h:188
void replaceAllUsesWith(Value newValue)
Replace all uses of 'this' value with the new value, updating anything in the IR that uses 'this' to ...
Definition Value.h:149
Location getLoc() const
Return the location of this value.
Definition Value.cpp:24
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
Region * getParentRegion()
Return the Region in which this Value is defined.
Definition Value.cpp:39
std::optional< TypeSizeAndAlignment > getTypeSizeAndAlignment(Type ty, ModuleOp module, const DataLayout &dl, OpenACCSupport *support=nullptr)
Returns the size and ABI alignment in bytes.
Include the generated interface declarations.
void populateACCAtomicPatterns(const LLVMTypeConverter &converter, RewritePatternSet &patterns, acc::OpenACCSupport &accSupport, ACCAtomicLoadAddressCallback getLoadAddress={})
Populate patterns that lower OpenACC atomic operations to LLVM dialect.
llvm::TypeSwitch< T, ResultT > TypeSwitch
Definition LLVM.h:139
std::function< Value(Operation *)> ACCAtomicLoadAddressCallback
Returns the address operand of a dialect-specific load operation.
Definition ACCToLLVM.h:47
void configureACCAtomicConversionLegality(ConversionTarget &target)
Configure conversion legality for OpenACC atomic operations.