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"
26#include "llvm/ADT/TypeSwitch.h"
27#include "llvm/Support/Debug.h"
28
29#include <cstdint>
30#include <optional>
31#include <set>
32#include <utility>
33
34#define DEBUG_TYPE "acc-atomic-patterns"
35
36using namespace mlir;
37using namespace mlir::acc;
38
39namespace {
40
41constexpr uint64_t kBitsInByte = 8;
42
43template <typename AtomicOpTy>
44class ACCAtomicOpConversion : public ConvertOpToLLVMPattern<AtomicOpTy> {
46
47public:
48 ACCAtomicOpConversion(const LLVMTypeConverter &typeConverter,
49 OpenACCSupport &accSupport,
50 const ACCAtomicLoadAddressCallback &getLoadAddress)
52 accSupport(accSupport), getLoadAddress(getLoadAddress) {}
53
54 LogicalResult
55 matchAndRewrite(AtomicOpTy op, OpAdaptor adaptor,
56 ConversionPatternRewriter &rewriter) const override;
57
58private:
59 OpenACCSupport &accSupport;
60 ACCAtomicLoadAddressCallback getLoadAddress;
61
62 size_t getComplexStructElementSizeInBits(Type ty) const {
63 auto structType = dyn_cast<LLVM::LLVMStructType>(ty);
64 if (!structType || structType.getBody().size() != 2 ||
65 structType.getBody()[0] != structType.getBody()[1] ||
66 !structType.getBody()[0].isIntOrFloat())
67 return 0;
68 size_t elementSizeInBits = structType.getBody()[0].getIntOrFloatBitWidth();
69 if (elementSizeInBits == 0 || elementSizeInBits > 64)
70 llvm_unreachable("unexpected complex type");
71 return elementSizeInBits;
72 }
73
74 /// Serialize a complex value into an integer.
75 Value serializeExpr(Value expr, ConversionPatternRewriter &rewriter) const {
76 size_t elementSizeInBits =
77 getComplexStructElementSizeInBits(expr.getType());
78 if (!elementSizeInBits)
79 return expr;
80
81 Location loc = expr.getLoc();
82 Value firstValue = LLVM::ExtractValueOp::create(rewriter, loc, expr, 0);
83 Value secondValue = LLVM::ExtractValueOp::create(rewriter, loc, expr, 1);
84 MLIRContext *context = rewriter.getContext();
85 Type intCastTy = IntegerType::get(context, elementSizeInBits);
86 Type intTy = IntegerType::get(context, elementSizeInBits * 2);
87 firstValue = LLVM::BitcastOp::create(rewriter, loc, intCastTy, firstValue);
88 secondValue =
89 LLVM::BitcastOp::create(rewriter, loc, intCastTy, secondValue);
90 firstValue = LLVM::ZExtOp::create(rewriter, loc, intTy, firstValue);
91 secondValue = LLVM::ZExtOp::create(rewriter, loc, intTy, secondValue);
92 Value shlVal =
93 LLVM::ConstantOp::create(rewriter, loc, intTy, elementSizeInBits);
94 Value result = LLVM::ShlOp::create(rewriter, loc, secondValue, shlVal);
95 return LLVM::OrOp::create(rewriter, loc, result, firstValue);
96 }
97
98 /// Deserialize an integer into a complex value.
99 Value deserializeExpr(Value expr, Type origTy,
100 ConversionPatternRewriter &rewriter) const {
101 size_t elementSizeInBits = getComplexStructElementSizeInBits(origTy);
102 if (!elementSizeInBits)
103 return expr;
104
105 auto intTy = dyn_cast<IntegerType>(expr.getType());
106 if (!intTy || intTy.getWidth() != elementSizeInBits * 2)
107 return expr;
108
109 Location loc = expr.getLoc();
110 MLIRContext *context = rewriter.getContext();
111 Type elemTy = IntegerType::get(context, elementSizeInBits);
112 Value low = LLVM::TruncOp::create(rewriter, loc, elemTy, expr);
113 Value shiftAmount =
114 LLVM::ConstantOp::create(rewriter, loc, intTy, elementSizeInBits);
115 Value highFull = LLVM::LShrOp::create(rewriter, loc, expr, shiftAmount);
116 Value high = LLVM::TruncOp::create(rewriter, loc, elemTy, highFull);
117 Type origElemTy = cast<LLVM::LLVMStructType>(origTy).getBody()[0];
118 if (origElemTy != elemTy) {
119 low = LLVM::BitcastOp::create(rewriter, loc, origElemTy, low);
120 high = LLVM::BitcastOp::create(rewriter, loc, origElemTy, high);
121 }
122 Value undefStruct = LLVM::UndefOp::create(rewriter, loc, origTy);
123 Value structWithLow = LLVM::InsertValueOp::create(
124 rewriter, loc, origTy, undefStruct, low, ArrayRef<int64_t>{0});
125 return LLVM::InsertValueOp::create(rewriter, loc, origTy, structWithLow,
126 high, ArrayRef<int64_t>{1});
127 }
128
129 uint64_t getAtomicSizeInBytes(Type originalTy, Type convertedTy,
130 ModuleOp module) const {
131 std::optional<TypeSizeAndAlignment> sizeAndAlignment =
132 acc::getTypeSizeAndAlignment(originalTy, module, &accSupport);
133 if (!sizeAndAlignment)
134 sizeAndAlignment =
135 acc::getTypeSizeAndAlignment(convertedTy, module, &accSupport);
136 assert(sizeAndAlignment && "atomic type size is not computable");
137 return sizeAndAlignment->first.getFixedValue();
138 }
139
140 Type getAtomicType(Type originalTy, Type convertedTy, ModuleOp module) const {
141 if (convertedTy.isIntOrFloat())
142 return convertedTy;
143 return IntegerType::get(
144 convertedTy.getContext(),
145 getAtomicSizeInBytes(originalTy, convertedTy, module) * kBitsInByte);
146 }
147
148 Type getReferencedElementType(Value ref, ModuleOp module = nullptr) const {
149 auto ptr = dyn_cast<PointerLikeType>(ref.getType());
150 if (!ptr)
151 llvm_unreachable("unexpected type");
152 Type elementTy = ptr.getElementType();
153 Type convertedTy = this->getTypeConverter()->convertType(elementTy);
154 if (module)
155 return getAtomicType(elementTy, convertedTy, module);
156 return convertedTy;
157 }
158
159 /// Memrefs are converted to descriptors rather than bare pointers, so the
160 /// element pointer must be recomputed from the descriptor.
161 Value getAtomicPointer(Value originalRef, Value convertedPtr, Location loc,
162 ConversionPatternRewriter &rewriter) const {
163 auto memrefTy = dyn_cast<MemRefType>(originalRef.getType());
164 if (!memrefTy)
165 return convertedPtr;
166
167 // Extract aligned base pointer (index 1) and offset (index 2).
168 Value alignedPtr =
169 LLVM::ExtractValueOp::create(rewriter, loc, convertedPtr, 1);
170 Value offset = LLVM::ExtractValueOp::create(rewriter, loc, convertedPtr, 2);
171 Type elemPtrType = LLVM::LLVMPointerType::get(rewriter.getContext());
172 return LLVM::GEPOp::create(
173 rewriter, loc, elemPtrType,
174 this->getTypeConverter()->convertType(memrefTy.getElementType()),
175 alignedPtr, offset);
176 }
177
178 Block *constructCmpxchgLoop(Value ptr, Type type, Value expr,
179 ConversionPatternRewriter &rewriter) const;
180
181 /// Emit a single atomicrmw for a `x = x <binop> expr` capture, or fail so
182 /// the caller falls back to the cmpxchg loop.
183 LogicalResult
184 tryEmitCaptureAtomicRMW(AtomicCaptureOp capture, AtomicUpdateOp update,
185 AtomicReadOp read,
186 ConversionPatternRewriter &rewriter) const;
187
188 Value genUpdateCmpxchgLoop(AtomicUpdateOp update,
189 ConversionPatternRewriter &rewriter) const;
190};
191
192template <>
193LogicalResult ACCAtomicOpConversion<AtomicReadOp>::matchAndRewrite(
194 AtomicReadOp read, OpAdaptor adaptor,
195 ConversionPatternRewriter &rewriter) const {
196 Location loc = read.getLoc();
197 Value xRef = read.getX();
198 Value xPtr = getAtomicPointer(xRef, adaptor.getX(), loc, rewriter);
199 ModuleOp mod = read->getParentOfType<ModuleOp>();
200 Type xType = getReferencedElementType(xRef, mod);
201
202 auto ordering = LLVM::AtomicOrdering::monotonic;
203 Value storeVal;
204 if (xType.isSignlessInteger()) {
205 Value zero = LLVM::ConstantOp::create(rewriter, loc, xType, 0);
206 storeVal = LLVM::AtomicRMWOp::create(rewriter, loc, LLVM::AtomicBinOp::_or,
207 xPtr, zero, ordering);
208 } else {
209 unsigned bitWidth = xType.getIntOrFloatBitWidth();
210 Type intType = IntegerType::get(rewriter.getContext(), bitWidth);
211 Value zero = LLVM::ConstantOp::create(rewriter, loc, intType, 0);
212 Value intVal = LLVM::AtomicRMWOp::create(
213 rewriter, loc, LLVM::AtomicBinOp::_or, xPtr, zero, ordering);
214 storeVal = LLVM::BitcastOp::create(rewriter, loc, xType, intVal);
215 }
216
217 Value vRef = read.getV();
218 Value vPtr = getAtomicPointer(vRef, adaptor.getV(), loc, rewriter);
219 Type vType = getReferencedElementType(vRef, mod);
220
221 if (xType != vType) {
222 // Convert `x` if the types do not match.
223 auto vPtrType = cast<PointerLikeType>(vRef.getType());
224 storeVal = vPtrType.genCast(rewriter, loc, storeVal, vType);
225 if (!storeVal) {
226 return rewriter.notifyMatchFailure(
227 read, "failed to convert the loaded value to the destination type");
228 }
229 }
230 auto storeOp = LLVM::StoreOp::create(rewriter, loc, storeVal, vPtr);
231 rewriter.replaceOp(read, storeOp);
232 return success();
233}
234
235template <>
236LogicalResult ACCAtomicOpConversion<AtomicWriteOp>::matchAndRewrite(
237 AtomicWriteOp write, OpAdaptor adaptor,
238 ConversionPatternRewriter &rewriter) const {
239 Location loc = write.getLoc();
240 Value expr = serializeExpr(adaptor.getExpr(), rewriter);
241 Value xRef = write.getX();
242 Value xPtr = getAtomicPointer(xRef, adaptor.getX(), loc, rewriter);
243 ModuleOp mod = write->getParentOfType<ModuleOp>();
244 Type xType = getReferencedElementType(xRef, mod);
245
246 auto ordering = LLVM::AtomicOrdering::monotonic;
247 if (!xType.isSignlessInteger()) {
248 unsigned bitWidth = xType.getIntOrFloatBitWidth();
249 Type intType = IntegerType::get(rewriter.getContext(), bitWidth);
250 expr = LLVM::BitcastOp::create(rewriter, loc, intType, expr);
251 }
252 LLVM::AtomicRMWOp::create(rewriter, loc, LLVM::AtomicBinOp::xchg, xPtr, expr,
253 ordering);
254 rewriter.eraseOp(write);
255 return success();
256}
257
258// Generate a cmpxchg loop based on the GenericAtomicRMWOpLowering algorithm.
259template <typename AtomicOpTy>
260Block *ACCAtomicOpConversion<AtomicOpTy>::constructCmpxchgLoop(
261 Value ptr, Type type, Value expr,
262 ConversionPatternRewriter &rewriter) const {
263 // Split the block into initial, loop, and ending parts.
264 Location loc = rewriter.getInsertionPoint()->getLoc();
265 Block *initBlock = rewriter.getInsertionBlock();
266 Block *loopBlock =
267 rewriter.splitBlock(initBlock, rewriter.getInsertionPoint());
268 loopBlock->addArgument(type, loc);
269 Block *endBlock =
270 rewriter.splitBlock(loopBlock, rewriter.getInsertionPoint());
271
272 // Compute the loaded value and branch to the loop block.
273 rewriter.setInsertionPointToEnd(initBlock);
274 Value init = LLVM::LoadOp::create(rewriter, loc, type, ptr);
275 LLVM::BrOp::create(rewriter, loc, init, loopBlock);
276
277 // Prepare the body of the loop block.
278 rewriter.setInsertionPointToStart(loopBlock);
279
280 Value loopArgument = loopBlock->getArgument(0);
281 Value result = serializeExpr(rewriter.getRemappedValue(expr), rewriter);
282 ModuleOp mod = initBlock->getParent()->getParentOfType<ModuleOp>();
283 Type convertedExprType =
284 this->getTypeConverter()->convertType(expr.getType());
285 Type exprType = getAtomicType(expr.getType(), convertedExprType, mod);
286
287 // Cast to an integer type.
288 if (!exprType.isSignlessInteger()) {
289 Type tmpType = IntegerType::get(
290 rewriter.getContext(),
291 getAtomicSizeInBytes(expr.getType(), convertedExprType, mod) *
293 result = LLVM::BitcastOp::create(rewriter, loc, tmpType, result);
294 loopArgument =
295 LLVM::BitcastOp::create(rewriter, loc, tmpType, loopArgument);
296 }
297
298 // Prepare the epilog of the loop block.
299 // Append the cmpxchg op to the end of the loop block.
300 auto successOrdering = LLVM::AtomicOrdering::acq_rel;
301 auto failureOrdering = LLVM::AtomicOrdering::monotonic;
302 auto cmpxchg =
303 LLVM::AtomicCmpXchgOp::create(rewriter, loc, ptr, loopArgument, result,
304 successOrdering, failureOrdering);
305 // Extract the %new_loaded and %ok values from the pair.
306 Value newLoaded = LLVM::ExtractValueOp::create(rewriter, loc, cmpxchg, 0);
307 Value ok = LLVM::ExtractValueOp::create(rewriter, loc, cmpxchg, 1);
308
309 // Cast back to the original type.
310 if (!exprType.isSignlessInteger())
311 newLoaded = LLVM::BitcastOp::create(rewriter, loc, exprType, newLoaded);
312
313 // Conditionally branch to the end or back to the loop depending on %ok.
314 LLVM::CondBrOp::create(rewriter, loc, ok, endBlock, ArrayRef<Value>(),
315 loopBlock, newLoaded);
316
317 return loopBlock;
318}
319
320static Value skipUnrealizedConversionOp(Value v) {
321 if (auto convOp =
322 dyn_cast_or_null<UnrealizedConversionCastOp>(v.getDefiningOp()))
323 return skipUnrealizedConversionOp(convOp.getOperand(0));
324 return v;
325}
326
327/// Obtain the foremost value of addr to indicate the origin of the storage.
328static Value getBaseStorage(Value addr, ConversionPatternRewriter &rewriter) {
329 Operation *op = skipUnrealizedConversionOp(addr).getDefiningOp();
330 if (auto gepOp = dyn_cast_or_null<LLVM::GEPOp>(op)) {
331 addr = gepOp.getBase();
332 op = addr.getDefiningOp();
333 }
334 if (auto extractOp = dyn_cast_or_null<LLVM::ExtractValueOp>(op)) {
335 // addr is in a struct. Get the inserted value corresponding to the
336 // extraction.
337 ArrayRef extractingPosition = extractOp.getPosition();
338 Value container = skipUnrealizedConversionOp(extractOp.getContainer());
339 Value remappedContainer = rewriter.getRemappedValue(container);
340 op = remappedContainer ? remappedContainer.getDefiningOp() : nullptr;
341 bool inserted = false;
342 while (auto insertValueOp = dyn_cast_or_null<LLVM::InsertValueOp>(op)) {
343 ArrayRef insertingPosition = insertValueOp.getPosition();
344 if (insertingPosition == extractingPosition) {
345 addr = insertValueOp.getValue();
346 inserted = true;
347 break;
348 }
349 op = insertValueOp.getContainer().getDefiningOp();
350 }
351 // The aggregate holding addr is not built by insertions, so the value it
352 // is converted from identifies the storage.
353 if (!inserted)
354 addr = container;
355 }
356 // op might be alloca already, or addr might be an argument.
357 return skipUnrealizedConversionOp(addr);
358}
359
360/// Include flow dependency (v -> expr) in the generated loop of
361/// `{ atomic.read, atomic.write/update }`.
362static void moveDependency(Value vRef, Value vPtr, Value expr,
363 Value loopArgument, Operation &loopHead,
364 ConversionPatternRewriter &rewriter,
365 const ACCAtomicLoadAddressCallback &getLoadAddress) {
366 Value vStorage = getBaseStorage(vPtr, rewriter);
367 // A dependency may be reached before it is itself converted, in which case
368 // its address is still expressed in terms of `vRef`.
369 Value vStorageRef = skipUnrealizedConversionOp(vRef);
370 LLVM_DEBUG({
371 llvm::dbgs() << "[acc-atomic] moveDependency\n";
372 llvm::dbgs() << " vPtr = " << vPtr << "\n";
373 llvm::dbgs() << " vStorage = " << vStorage << "\n";
374 llvm::dbgs() << " vStorageRef = " << vStorageRef << "\n";
375 llvm::dbgs() << " expr = " << expr << "\n";
376 });
377 llvm::DenseMap<Value, Value> remappedToOriginal;
378 std::set<std::pair<Operation *, std::set<Operation *>>> worklist;
379 std::set<Operation *> included;
380 Value mappedExpr = rewriter.getRemappedValue(expr);
381 remappedToOriginal[mappedExpr] = expr;
382 if (Operation *exprDef = mappedExpr.getDefiningOp())
383 worklist.insert(std::pair{exprDef, std::set<Operation *>{}});
384
385 while (!worklist.empty()) {
386 auto [dep, post] = worklist.extract(worklist.begin()).value();
387 if (!dep)
388 continue;
389 LLVM_DEBUG(llvm::dbgs() << " visit dep = " << *dep << "\n");
390 if (vPtr.getDefiningOp() &&
391 !vPtr.getDefiningOp()->getParentOp()->isAncestor(dep)) {
392 // Outside the parental region. No load found in this flow.
393 LLVM_DEBUG(llvm::dbgs() << " -> skipped: not in parental region\n");
394 continue;
395 }
396
397 Value addr;
398 if (auto load = dyn_cast<LLVM::LoadOp>(dep)) {
399 addr = load.getAddr();
400 } else if (auto load = dyn_cast<memref::LoadOp>(dep)) {
401 addr = load.getMemref();
402 } else if (getLoadAddress) {
403 addr = getLoadAddress(dep);
404 }
405 if (addr) {
406 Value baseStorage = getBaseStorage(addr, rewriter);
407 LLVM_DEBUG({
408 llvm::dbgs() << " addr = " << addr << "\n";
409 llvm::dbgs() << " baseStorage = " << baseStorage << "\n";
410 llvm::dbgs() << " remapped = "
411 << rewriter.getRemappedValue(baseStorage) << "\n";
412 llvm::dbgs() << " matchRef=" << (baseStorage == vStorageRef)
413 << " matchConv="
414 << (rewriter.getRemappedValue(baseStorage) == vStorage)
415 << "\n";
416 });
417 if (baseStorage == vStorageRef ||
418 rewriter.getRemappedValue(baseStorage) == vStorage) {
419 // Found the load of `v`. Include this flow in dependency.
420 Value load = dep->getResult(0);
421 auto replaceUses = [&](Operation *op) {
422 rewriter.modifyOpInPlace(op, [&] {
423 op->replaceUsesOfWith(remappedToOriginal[load], loopArgument);
424 });
425 };
426 for (Operation *p : post)
427 replaceUses(p);
428 replaceUses(&loopHead);
429 included.insert(post.begin(), post.end());
430 }
431 continue;
432 }
433 post.insert(dep);
434 for (Value operand : dep->getOperands()) {
435 Value mappedOperand = rewriter.getRemappedValue(operand);
436 if (auto *d = mappedOperand.getDefiningOp()) {
437 if (dep == d) {
438 if (auto *op = operand.getDefiningOp()) {
439 remappedToOriginal[op->getResult(0)] = operand;
440 worklist.insert(std::pair{op, post});
441 }
442 } else {
443 remappedToOriginal[mappedOperand] = operand;
444 worklist.insert(std::pair{d, post});
445 }
446 }
447 }
448 }
449
450 // Include dependency.
451 SmallVector<Operation *> includedInOrder;
452 vPtr.getParentRegion()->walk([&](Operation *op) {
453 if (included.find(op) != included.end())
454 includedInOrder.push_back(op);
455 });
456 for (Operation *d : includedInOrder)
457 d->moveBefore(&loopHead);
458}
459
460/// Generate a cmpxchg loop for update and return a stored value.
461template <typename AtomicOpTy>
462Value ACCAtomicOpConversion<AtomicOpTy>::genUpdateCmpxchgLoop(
463 AtomicUpdateOp update, ConversionPatternRewriter &rewriter) const {
464 Location loc = update.getLoc();
465 Value xRef = update.getX();
466 Value xPtr =
467 getAtomicPointer(xRef, rewriter.getRemappedValue(xRef), loc, rewriter);
468 ModuleOp mod = update->getParentOfType<ModuleOp>();
469 Type xType = getReferencedElementType(xRef, mod);
470 Type xTypeOrig = getReferencedElementType(xRef);
471
472 Block &updateBlock = update.getRegion().front();
473 Value updateArgument = updateBlock.getArgument(0);
474 Operation *terminator = updateBlock.getTerminator();
475 Value expr = terminator->getOperand(0);
476
477 Block *loopBlock = constructCmpxchgLoop(xPtr, xType, expr, rewriter);
478 Value loopArgument = loopBlock->getArgument(0);
479 Operation &loopHead = loopBlock->front();
480
481 rewriter.setInsertionPointToStart(loopBlock);
482 loopArgument = deserializeExpr(loopArgument, xTypeOrig, rewriter);
483
484 // Move in and out flow dependency (x -> expr). Some computation might be
485 // outside atomic regions.
486 moveDependency(xRef, xPtr, expr, loopArgument, loopHead, rewriter,
487 getLoadAddress);
488 // Move out the residue.
489 rewriter.replaceAllUsesWith(cast<BlockArgument>(updateArgument),
490 {loopArgument});
491
492 updateBlock.walk([&](Operation *op) {
494 rewriter.moveOpBefore(op, &loopHead);
495 });
496
497 if (auto cmpxchg = dyn_cast<LLVM::AtomicCmpXchgOp>(loopHead))
498 return cmpxchg.getVal();
499 if (auto bitcast = dyn_cast<LLVM::BitcastOp>(loopHead))
500 // Handling a non-integer type.
501 return bitcast.getArg();
502 if (auto extract = dyn_cast<LLVM::ExtractValueOp>(loopHead))
503 // Handling a complex type.
504 return extract.getContainer();
505 llvm_unreachable("invalid cmpxchg loop");
506}
507
508static std::optional<LLVM::AtomicBinOp> getAtomicBinOp(Operation *op,
509 bool updateIsLhs) {
511 .Case<arith::AddFOp>([](auto) { return LLVM::AtomicBinOp::fadd; })
512 .Case<arith::AddIOp>([](auto) { return LLVM::AtomicBinOp::add; })
513 .Case<arith::SubFOp>(
514 [updateIsLhs](auto) -> std::optional<LLVM::AtomicBinOp> {
515 // atomicrmw fsub is always `*ptr = *ptr - val`.
516 if (!updateIsLhs)
517 return std::nullopt;
518 return LLVM::AtomicBinOp::fsub;
519 })
520 .Case<arith::SubIOp>(
521 [updateIsLhs](auto) -> std::optional<LLVM::AtomicBinOp> {
522 // atomicrmw sub is always `*ptr = *ptr - val`.
523 if (!updateIsLhs)
524 return std::nullopt;
525 return LLVM::AtomicBinOp::sub;
526 })
527 .Case<arith::AndIOp>([](auto) { return LLVM::AtomicBinOp::_and; })
528 .Case<arith::OrIOp>([](auto) { return LLVM::AtomicBinOp::_or; })
529 .Case<arith::XOrIOp>([](auto) { return LLVM::AtomicBinOp::_xor; })
530 .Case<arith::MaxSIOp>([](auto) { return LLVM::AtomicBinOp::max; })
531 .Case<arith::MinSIOp>([](auto) { return LLVM::AtomicBinOp::min; })
532 .Case<arith::MaxUIOp>([](auto) { return LLVM::AtomicBinOp::umax; })
533 .Case<arith::MinUIOp>([](auto) { return LLVM::AtomicBinOp::umin; })
534 .Case<arith::MaximumFOp>([](auto) { return LLVM::AtomicBinOp::fmaximum; })
535 .Case<arith::MinimumFOp>([](auto) { return LLVM::AtomicBinOp::fminimum; })
536 .Case<arith::MaxNumFOp>(
537 [](auto) { return LLVM::AtomicBinOp::fmaximumnum; })
538 .Case<arith::MinNumFOp>(
539 [](auto) { return LLVM::AtomicBinOp::fminimumnum; })
540 .Default([](Operation *) { return std::nullopt; });
541}
542
543/// Match an update region computing `x = x <binop> expr`, and return the
544/// atomicrmw kind with the binary operation. Shared by the update and capture
545/// conversions so the same region cannot take atomicrmw in one and the cmpxchg
546/// loop in the other.
547static std::optional<std::pair<LLVM::AtomicBinOp, Operation *>>
548matchAtomicBinOpUpdate(AtomicUpdateOp update) {
549 Block &block = update.getRegion().front();
550 Value arg = block.getArgument(0);
551 Operation *yield = block.getTerminator();
552 if (!yield || yield->getNumOperands() != 1)
553 return std::nullopt;
554 Operation *binOp = yield->getOperand(0).getDefiningOp();
555 if (!binOp || binOp->getBlock() != &block || binOp->getNumOperands() != 2 ||
556 binOp->getNumResults() != 1)
557 return std::nullopt;
558 // The updated value has to feed the binop and nothing else.
559 if (!arg.hasOneUse() || arg.use_begin()->getOwner() != binOp)
560 return std::nullopt;
561 bool updateIsLhs = binOp->getOperand(0) == arg;
562 std::optional<LLVM::AtomicBinOp> kind = getAtomicBinOp(binOp, updateIsLhs);
563 if (!kind)
564 return std::nullopt;
565 return std::make_pair(*kind, binOp);
566}
567
568/// Generate llvm.atomicrmw or an llvm.cmpxchg loop.
569template <>
570LogicalResult ACCAtomicOpConversion<AtomicUpdateOp>::matchAndRewrite(
571 AtomicUpdateOp update, OpAdaptor adaptor,
572 ConversionPatternRewriter &rewriter) const {
573 Block &updateBlock = update.getRegion().front();
574 Value updateArgument = updateBlock.getArgument(0);
575
576 // Collect operations that depend on the update argument.
577 std::set<Operation *> dependents;
578 SmallVector<Value> worklist;
579 worklist.push_back(updateArgument);
580 while (!worklist.empty()) {
581 Value value = worklist.back();
582 worklist.pop_back();
583 for (OpOperand &use : value.getUses()) {
584 Operation *useOp = use.getOwner();
585 dependents.insert(useOp);
586 if (useOp->getNumResults() == 1)
587 worklist.push_back(useOp->getResult(0));
588 }
589 }
590
591 // Move independent operations out of the update block.
592 SmallVector<Operation *> independent;
593 for (Operation &op : updateBlock.getOperations()) {
594 if (dependents.find(&op) == dependents.end()) {
596 llvm_unreachable("invalid update operation");
597 independent.push_back(&op);
598 }
599 }
600 for (Operation *op : independent)
601 rewriter.moveOpBefore(op, update);
602
603 // Map arith op to atomicrmw kind.
604 //
605 // In the current version of LLVM, these are the equivalences for float
606 // min/max across the different instructions intrinsics and operations -
607 // confusingly they have slightly different and not very descriptive names.
608 //
609 // | MLIR | atomicrmw inst | llvm intrinsic |
610 // |------------+----------------+-------------------|
611 // | - | fmax | llvm.maxnum.* |
612 // | MaximumFOp | fmaximum | llvm.maximum.* |
613 // | MaxNumFOp | fmaximumnum | llvm.maximumnum.* |
614 //
615 // Sources:
616 // https://llvm.org/docs/LangRef.html#id236
617 // https://llvm.org/docs/LangRef.html#floating-point-min-max-intrinsics-comparison
618 // https://mlir.llvm.org/docs/Dialects/ArithOps/#arithmaximumf-arithmaximumfop
619 // https://mlir.llvm.org/docs/Dialects/ArithOps/#arithmaxnumf-arithmaxnumfop
620
621 // Select the kind and the val of atomicrmw.
622 std::optional<Value> val = std::nullopt;
623 std::optional<LLVM::AtomicBinOp> kind = std::nullopt;
624
625 if (auto matched = matchAtomicBinOpUpdate(update)) {
626 Operation *binOp = matched->second;
627 bool updateIsLhs = binOp->getOperand(0) == updateArgument;
628 kind = matched->first;
629 val = binOp->getOperand(updateIsLhs ? 1 : 0);
630 }
631
632 // Per-component atomicrmw info for complex type decomposition.
633 // Decomposed complex ops (complex.re/im + arith binop + complex.create)
634 // produce per-component binary ops that each need a separate atomicrmw.
635 struct ComponentAtomic {
636 LLVM::AtomicBinOp kind;
637 Value val;
638 int32_t fieldIdx;
639 };
640 SmallVector<ComponentAtomic, 2> componentAtomics;
641
642 if (!val || !kind) {
643 Type convertedArgTy =
644 this->getTypeConverter()->convertType(updateArgument.getType());
645 if (auto structTy = dyn_cast<LLVM::LLVMStructType>(convertedArgTy)) {
646 if (structTy.getBody().size() == 2 &&
647 structTy.getBody()[0] == structTy.getBody()[1] &&
648 structTy.getBody()[0].isIntOrFloat() &&
649 structTy.getBody()[0].getIntOrFloatBitWidth() > 32) {
650 for (Operation &op : updateBlock.getOperations()) {
651 if (op.hasTrait<OpTrait::IsTerminator>() || op.getNumOperands() < 2)
652 continue;
653 int32_t fieldIdx = -1;
654 Value externalVal = nullptr;
655 bool updateIsLhs = false;
656 for (unsigned i = 0; i < 2; ++i) {
657 Value operand = op.getOperand(i);
658 if (auto reOp = operand.getDefiningOp<complex::ReOp>()) {
659 if (reOp.getOperand() == updateArgument) {
660 fieldIdx = 0;
661 externalVal = op.getOperand(1 - i);
662 updateIsLhs = i == 0;
663 }
664 } else if (auto imOp = operand.getDefiningOp<complex::ImOp>()) {
665 if (imOp.getOperand() == updateArgument) {
666 fieldIdx = 1;
667 externalVal = op.getOperand(1 - i);
668 updateIsLhs = i == 0;
669 }
670 }
671 }
672 if (fieldIdx < 0)
673 continue;
674 auto componentKind = getAtomicBinOp(&op, updateIsLhs);
675 if (!componentKind)
676 continue;
677 componentAtomics.push_back({*componentKind, externalVal, fieldIdx});
678 }
679 }
680 }
681 }
682
683 Location loc = update.getLoc();
684 Value xPtr = getAtomicPointer(update.getX(), adaptor.getX(), loc, rewriter);
685
686 // Require distinct real/imag lanes; duplicate fieldIdx values must fall back
687 // to cmpxchg rather than emitting two atomicrmw ops on the same component.
688 bool hasDistinctComplexLanes = false;
689 if (componentAtomics.size() == 2) {
690 unsigned lanes = 0;
691 for (const ComponentAtomic &ca : componentAtomics) {
692 if (ca.fieldIdx == 0 || ca.fieldIdx == 1)
693 lanes |= 1u << ca.fieldIdx;
694 }
695 hasDistinctComplexLanes = lanes == 0b11;
696 }
697
698 if (val && kind) {
699 auto ordering = LLVM::AtomicOrdering::monotonic;
700 LLVM::AtomicRMWOp::create(rewriter, loc, *kind, xPtr,
701 rewriter.getRemappedValue(*val), ordering);
702 } else if (hasDistinctComplexLanes) {
703 auto structTy = cast<LLVM::LLVMStructType>(
704 this->getTypeConverter()->convertType(updateArgument.getType()));
705 Type ptrType = LLVM::LLVMPointerType::get(rewriter.getContext());
706 auto ordering = LLVM::AtomicOrdering::monotonic;
707 for (ComponentAtomic &ca : componentAtomics) {
708 Value elemPtr =
709 LLVM::GEPOp::create(rewriter, loc, ptrType, structTy, xPtr,
710 ArrayRef<LLVM::GEPArg>{0, ca.fieldIdx});
711 LLVM::AtomicRMWOp::create(rewriter, loc, ca.kind, elemPtr,
712 rewriter.getRemappedValue(ca.val), ordering);
713 }
714 } else {
715 // Fallback to the llvm.cmpxchg loop generation.
716 genUpdateCmpxchgLoop(update, rewriter);
717 }
718 rewriter.eraseOp(update);
719 return success();
720}
721
722/// The cmpxchg loop re-points reads of `v`/`x` inside `expr` at the atomically
723/// loaded value (see moveDependency). An atomicrmw cannot do that, so any
724/// `expr` that loads from memory keeps the loop: the load may be the captured
725/// value itself, and the address is not reliably comparable here.
726static bool exprReadsMemory(Value expr) {
727 SmallVector<Value> worklist{expr};
729 while (!worklist.empty()) {
730 Operation *def = worklist.pop_back_val().getDefiningOp();
731 if (!def || !seen.insert(def).second)
732 continue;
733 if (!isMemoryEffectFree(def))
734 return true;
735 worklist.append(def->getOperands().begin(), def->getOperands().end());
736 }
737 return false;
738}
739
740/// Emit a single atomicrmw for a `x = x <binop> expr` capture.
741template <typename AtomicOpTy>
742LogicalResult ACCAtomicOpConversion<AtomicOpTy>::tryEmitCaptureAtomicRMW(
743 AtomicCaptureOp capture, AtomicUpdateOp update, AtomicReadOp read,
744 ConversionPatternRewriter &rewriter) const {
745 if (read.getX() != update.getX())
746 return failure();
747 auto matched = matchAtomicBinOpUpdate(update);
748 if (!matched)
749 return failure();
750 auto [kind, binOp] = *matched;
751
752 Value arg = update.getRegion().front().getArgument(0);
753 bool updateIsLhs = binOp->getOperand(0) == arg;
754 Value expr = binOp->getOperand(updateIsLhs ? 1 : 0);
755
756 // Keep serialized and aggregate types on the cmpxchg path.
757 Type argTy = arg.getType();
758 if (!argTy.isIntOrFloat() ||
759 this->getTypeConverter()->convertType(argTy) != argTy)
760 return failure();
761 // The operand must already be available, and must not be the captured value.
762 Operation *exprDef = expr.getDefiningOp();
763 if (exprDef && exprDef->getBlock() == &update.getRegion().front())
764 return failure();
765 if (exprReadsMemory(expr))
766 return failure();
767
768 Location loc = capture.getLoc();
769 Value xRef = update.getX();
770 Value vRef = read.getV();
771 Value xPtr =
772 getAtomicPointer(xRef, rewriter.getRemappedValue(xRef), loc, rewriter);
773 Value vPtr =
774 getAtomicPointer(vRef, rewriter.getRemappedValue(vRef), loc, rewriter);
775
776 rewriter.setInsertionPoint(capture);
777 auto rmw = LLVM::AtomicRMWOp::create(rewriter, loc, kind, xPtr,
778 rewriter.getRemappedValue(expr),
779 LLVM::AtomicOrdering::monotonic);
780 // atomicrmw yields the old value; `{update, read}` captures the new one.
781 Value captured = rmw.getRes();
782 if (capture.getFirstOp() == update.getOperation()) {
783 rewriter.moveOpAfter(binOp, rmw);
784 binOp->replaceUsesOfWith(arg, captured);
785 captured = binOp->getResult(0);
786 }
787 rewriter.replaceOpWithNewOp<LLVM::StoreOp>(capture, captured, vPtr);
788 return success();
789}
790
791/// Generate an llvm.cmpxchg loop.
792template <>
793LogicalResult ACCAtomicOpConversion<AtomicCaptureOp>::matchAndRewrite(
794 AtomicCaptureOp capture, OpAdaptor /*adaptor*/,
795 ConversionPatternRewriter &rewriter) const {
796 Operation *firstOp = capture.getFirstOp();
797 Operation *secondOp = capture.getSecondOp();
798 Value vPtr = nullptr;
799 Value storeVal = nullptr;
800
801 // A single `x = x <binop> expr` capture becomes one atomicrmw. The cmpxchg
802 // loop below serializes retries and collapses under contention.
803 if (AtomicUpdateOp update = capture.getAtomicUpdateOp())
804 if (AtomicReadOp read = capture.getAtomicReadOp())
805 if (succeeded(tryEmitCaptureAtomicRMW(capture, update, read, rewriter)))
806 return success();
807
808 if (auto firstReadStmt = dyn_cast<AtomicReadOp>(firstOp)) {
809 Location loc = capture.getLoc();
810 Value xRef = firstReadStmt.getX();
811 Value xPtr =
812 getAtomicPointer(xRef, rewriter.getRemappedValue(xRef), loc, rewriter);
813 ModuleOp mod = capture->getParentOfType<ModuleOp>();
814 Type xType = getReferencedElementType(xRef, mod);
815 Type xTypeOrig = getReferencedElementType(xRef);
816 Value vRef = firstReadStmt.getV();
817 vPtr =
818 getAtomicPointer(vRef, rewriter.getRemappedValue(vRef), loc, rewriter);
819
820 Value expr = nullptr;
821 if (auto secondWriteStmt = dyn_cast<AtomicWriteOp>(secondOp)) {
822 // 1. `{ atomic.read, atomic.write }` pattern
823 expr = secondWriteStmt.getExpr();
824 } else if (auto secondUpdateStmt = dyn_cast<AtomicUpdateOp>(secondOp)) {
825 // 2. `{ atomic.read, atomic.update }` pattern
826 Block &updateBlock = secondUpdateStmt.getRegion().front();
827 Operation *terminator = updateBlock.getTerminator();
828 expr = terminator->getOperand(0);
829 }
830
831 Block *loopBlock = constructCmpxchgLoop(xPtr, xType, expr, rewriter);
832 Value loopArgument = loopBlock->getArgument(0);
833 Operation &loopHead = loopBlock->front();
834 auto condBr = cast<LLVM::CondBrOp>(loopBlock->back());
835 storeVal = condBr.getFalseDestOperands()[0];
836
837 rewriter.setInsertionPointToStart(loopBlock);
838 loopArgument = deserializeExpr(loopArgument, xTypeOrig, rewriter);
839
840 // Include flow dependency (v -> expr).
841 if (auto secondUpdateStmt = dyn_cast<AtomicUpdateOp>(secondOp)) {
842 Block &updateBlock = secondUpdateStmt.getRegion().front();
843 Value updateArgument = updateBlock.getArgument(0);
844 updateArgument.replaceAllUsesWith(loopArgument);
845 updateBlock.walk([&](Operation *op) {
847 rewriter.moveOpBefore(op, &loopHead);
848 });
849 }
850 moveDependency(vRef, vPtr, expr, loopArgument, loopHead, rewriter,
851 getLoadAddress);
852 } else if (auto firstUpdateStmt = dyn_cast<AtomicUpdateOp>(firstOp)) {
853 if (auto secondReadStmt = dyn_cast<AtomicReadOp>(secondOp)) {
854 // 3. `{ atomic.update, atomic.read }` pattern
855 storeVal = genUpdateCmpxchgLoop(firstUpdateStmt, rewriter);
856
857 Value vRef = secondReadStmt.getV();
858 vPtr = getAtomicPointer(vRef, rewriter.getRemappedValue(vRef),
859 capture.getLoc(), rewriter);
860 }
861 }
862 // Generate `v = x`.
863 rewriter.setInsertionPoint(capture);
864 rewriter.replaceOpWithNewOp<LLVM::StoreOp>(capture, storeVal, vPtr);
865 return success();
866}
867
868} // namespace
869
870namespace mlir {
871
873 target.addIllegalOp<AtomicReadOp, AtomicWriteOp, AtomicUpdateOp,
874 AtomicCaptureOp>();
875}
876
878 RewritePatternSet &patterns,
879 acc::OpenACCSupport &accSupport,
880 ACCAtomicLoadAddressCallback getLoadAddress) {
881 patterns.add<ACCAtomicOpConversion<AtomicReadOp>,
882 ACCAtomicOpConversion<AtomicWriteOp>,
883 ACCAtomicOpConversion<AtomicUpdateOp>,
884 ACCAtomicOpConversion<AtomicCaptureOp>>(converter, accSupport,
885 getLoadAddress);
886}
887
888} // 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:233
typename SourceOp::Adaptor OpAdaptor
Definition Pattern.h:235
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:801
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:230
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
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
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
bool hasOneUse() const
Returns true if this value has exactly one use.
Definition Value.h:197
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
use_iterator use_begin() const
Definition Value.h:184
std::optional< TypeSizeAndAlignment > getTypeSizeAndAlignment(Type ty, ModuleOp module, const DataLayout &dl, OpenACCSupport *support=nullptr, Value var={})
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.
bool isMemoryEffectFree(Operation *op)
Returns true if the given operation is free of memory effects.
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.