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 Value operand = rewriter.getRemappedValue(*val);
701 // There is no atomic on i1, but it occupies a byte, and and/or/xor keep a
702 // 0/1 byte 0/1, so apply them to the byte.
703 if (operand.getType().isInteger(1) &&
704 llvm::is_contained({LLVM::AtomicBinOp::_and, LLVM::AtomicBinOp::_or,
705 LLVM::AtomicBinOp::_xor},
706 *kind))
707 operand =
708 LLVM::ZExtOp::create(rewriter, loc, rewriter.getI8Type(), operand);
709 LLVM::AtomicRMWOp::create(rewriter, loc, *kind, xPtr, operand, ordering);
710 } else if (hasDistinctComplexLanes) {
711 auto structTy = cast<LLVM::LLVMStructType>(
712 this->getTypeConverter()->convertType(updateArgument.getType()));
713 Type ptrType = LLVM::LLVMPointerType::get(rewriter.getContext());
714 auto ordering = LLVM::AtomicOrdering::monotonic;
715 for (ComponentAtomic &ca : componentAtomics) {
716 Value elemPtr =
717 LLVM::GEPOp::create(rewriter, loc, ptrType, structTy, xPtr,
718 ArrayRef<LLVM::GEPArg>{0, ca.fieldIdx});
719 LLVM::AtomicRMWOp::create(rewriter, loc, ca.kind, elemPtr,
720 rewriter.getRemappedValue(ca.val), ordering);
721 }
722 } else {
723 // Fallback to the llvm.cmpxchg loop generation.
724 genUpdateCmpxchgLoop(update, rewriter);
725 }
726 rewriter.eraseOp(update);
727 return success();
728}
729
730/// The cmpxchg loop re-points reads of `v`/`x` inside `expr` at the atomically
731/// loaded value (see moveDependency). An atomicrmw cannot do that, so any
732/// `expr` that loads from memory keeps the loop: the load may be the captured
733/// value itself, and the address is not reliably comparable here.
734static bool exprReadsMemory(Value expr) {
735 SmallVector<Value> worklist{expr};
737 while (!worklist.empty()) {
738 Operation *def = worklist.pop_back_val().getDefiningOp();
739 if (!def || !seen.insert(def).second)
740 continue;
741 if (!isMemoryEffectFree(def))
742 return true;
743 worklist.append(def->getOperands().begin(), def->getOperands().end());
744 }
745 return false;
746}
747
748/// Emit a single atomicrmw for a `x = x <binop> expr` capture.
749template <typename AtomicOpTy>
750LogicalResult ACCAtomicOpConversion<AtomicOpTy>::tryEmitCaptureAtomicRMW(
751 AtomicCaptureOp capture, AtomicUpdateOp update, AtomicReadOp read,
752 ConversionPatternRewriter &rewriter) const {
753 if (read.getX() != update.getX())
754 return failure();
755 auto matched = matchAtomicBinOpUpdate(update);
756 if (!matched)
757 return failure();
758 auto [kind, binOp] = *matched;
759
760 Value arg = update.getRegion().front().getArgument(0);
761 bool updateIsLhs = binOp->getOperand(0) == arg;
762 Value expr = binOp->getOperand(updateIsLhs ? 1 : 0);
763
764 // Keep serialized and aggregate types on the cmpxchg path.
765 Type argTy = arg.getType();
766 if (!argTy.isIntOrFloat() ||
767 this->getTypeConverter()->convertType(argTy) != argTy)
768 return failure();
769 // The operand must already be available, and must not be the captured value.
770 Operation *exprDef = expr.getDefiningOp();
771 if (exprDef && exprDef->getBlock() == &update.getRegion().front())
772 return failure();
773 if (exprReadsMemory(expr))
774 return failure();
775
776 Location loc = capture.getLoc();
777 Value xRef = update.getX();
778 Value vRef = read.getV();
779 Value xPtr =
780 getAtomicPointer(xRef, rewriter.getRemappedValue(xRef), loc, rewriter);
781 Value vPtr =
782 getAtomicPointer(vRef, rewriter.getRemappedValue(vRef), loc, rewriter);
783
784 rewriter.setInsertionPoint(capture);
785 auto rmw = LLVM::AtomicRMWOp::create(rewriter, loc, kind, xPtr,
786 rewriter.getRemappedValue(expr),
787 LLVM::AtomicOrdering::monotonic);
788 // atomicrmw yields the old value; `{update, read}` captures the new one.
789 Value captured = rmw.getRes();
790 if (capture.getFirstOp() == update.getOperation()) {
791 rewriter.moveOpAfter(binOp, rmw);
792 binOp->replaceUsesOfWith(arg, captured);
793 captured = binOp->getResult(0);
794 }
795 rewriter.replaceOpWithNewOp<LLVM::StoreOp>(capture, captured, vPtr);
796 return success();
797}
798
799/// Generate an llvm.cmpxchg loop.
800template <>
801LogicalResult ACCAtomicOpConversion<AtomicCaptureOp>::matchAndRewrite(
802 AtomicCaptureOp capture, OpAdaptor /*adaptor*/,
803 ConversionPatternRewriter &rewriter) const {
804 Operation *firstOp = capture.getFirstOp();
805 Operation *secondOp = capture.getSecondOp();
806 Value vPtr = nullptr;
807 Value storeVal = nullptr;
808
809 // A single `x = x <binop> expr` capture becomes one atomicrmw. The cmpxchg
810 // loop below serializes retries and collapses under contention.
811 if (AtomicUpdateOp update = capture.getAtomicUpdateOp())
812 if (AtomicReadOp read = capture.getAtomicReadOp())
813 if (succeeded(tryEmitCaptureAtomicRMW(capture, update, read, rewriter)))
814 return success();
815
816 if (auto firstReadStmt = dyn_cast<AtomicReadOp>(firstOp)) {
817 Location loc = capture.getLoc();
818 Value xRef = firstReadStmt.getX();
819 Value xPtr =
820 getAtomicPointer(xRef, rewriter.getRemappedValue(xRef), loc, rewriter);
821 ModuleOp mod = capture->getParentOfType<ModuleOp>();
822 Type xType = getReferencedElementType(xRef, mod);
823 Type xTypeOrig = getReferencedElementType(xRef);
824 Value vRef = firstReadStmt.getV();
825 vPtr =
826 getAtomicPointer(vRef, rewriter.getRemappedValue(vRef), loc, rewriter);
827
828 Value expr = nullptr;
829 if (auto secondWriteStmt = dyn_cast<AtomicWriteOp>(secondOp)) {
830 // 1. `{ atomic.read, atomic.write }` pattern
831 expr = secondWriteStmt.getExpr();
832 } else if (auto secondUpdateStmt = dyn_cast<AtomicUpdateOp>(secondOp)) {
833 // 2. `{ atomic.read, atomic.update }` pattern
834 Block &updateBlock = secondUpdateStmt.getRegion().front();
835 Operation *terminator = updateBlock.getTerminator();
836 expr = terminator->getOperand(0);
837 }
838
839 Block *loopBlock = constructCmpxchgLoop(xPtr, xType, expr, rewriter);
840 Value loopArgument = loopBlock->getArgument(0);
841 Operation &loopHead = loopBlock->front();
842 auto condBr = cast<LLVM::CondBrOp>(loopBlock->back());
843 storeVal = condBr.getFalseDestOperands()[0];
844
845 rewriter.setInsertionPointToStart(loopBlock);
846 loopArgument = deserializeExpr(loopArgument, xTypeOrig, rewriter);
847
848 // Include flow dependency (v -> expr).
849 if (auto secondUpdateStmt = dyn_cast<AtomicUpdateOp>(secondOp)) {
850 Block &updateBlock = secondUpdateStmt.getRegion().front();
851 Value updateArgument = updateBlock.getArgument(0);
852 updateArgument.replaceAllUsesWith(loopArgument);
853 updateBlock.walk([&](Operation *op) {
855 rewriter.moveOpBefore(op, &loopHead);
856 });
857 }
858 moveDependency(vRef, vPtr, expr, loopArgument, loopHead, rewriter,
859 getLoadAddress);
860 } else if (auto firstUpdateStmt = dyn_cast<AtomicUpdateOp>(firstOp)) {
861 if (auto secondReadStmt = dyn_cast<AtomicReadOp>(secondOp)) {
862 // 3. `{ atomic.update, atomic.read }` pattern
863 storeVal = genUpdateCmpxchgLoop(firstUpdateStmt, rewriter);
864
865 Value vRef = secondReadStmt.getV();
866 vPtr = getAtomicPointer(vRef, rewriter.getRemappedValue(vRef),
867 capture.getLoc(), rewriter);
868 }
869 }
870 // Generate `v = x`.
871 rewriter.setInsertionPoint(capture);
872 rewriter.replaceOpWithNewOp<LLVM::StoreOp>(capture, storeVal, vPtr);
873 return success();
874}
875
876} // namespace
877
878namespace mlir {
879
881 target.addIllegalOp<AtomicReadOp, AtomicWriteOp, AtomicUpdateOp,
882 AtomicCaptureOp>();
883}
884
886 RewritePatternSet &patterns,
887 acc::OpenACCSupport &accSupport,
888 ACCAtomicLoadAddressCallback getLoadAddress) {
889 patterns.add<ACCAtomicOpConversion<AtomicReadOp>,
890 ACCAtomicOpConversion<AtomicWriteOp>,
891 ACCAtomicOpConversion<AtomicUpdateOp>,
892 ACCAtomicOpConversion<AtomicCaptureOp>>(converter, accSupport,
893 getLoadAddress);
894}
895
896} // 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:34
BlockArgument getArgument(unsigned i)
Definition Block.h:154
Region * getParent() const
Provide a 'getParent' method for ilist_node_with_parent methods.
Definition Block.cpp:27
OpListType & getOperations()
Definition Block.h:162
Operation & front()
Definition Block.h:178
Operation & back()
Definition Block.h:177
RetT walk(FnT &&callback)
Walk all nested operations, blocks (including this block) or regions, depending on the type of callba...
Definition Block.h:318
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:206
RetT walk(FnT &&callback)
Walk all nested operations, blocks or regions (including this region), depending on the type of callb...
Definition Region.h:297
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 isInteger() const
Return true if this is an integer type (with the specified width).
Definition Types.cpp:58
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:53
void configureACCAtomicConversionLegality(ConversionTarget &target)
Configure conversion legality for OpenACC atomic operations.