MLIR 24.0.0git
OpenMPToLLVMIRTranslation.cpp
Go to the documentation of this file.
1//===- OpenMPToLLVMIRTranslation.cpp - Translate OpenMP dialect to LLVM IR-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements a translation between the MLIR OpenMP dialect and LLVM
10// IR.
11//
12//===----------------------------------------------------------------------===//
21#include "mlir/IR/Operation.h"
23#include "mlir/Support/LLVM.h"
26
27#include "llvm/ADT/ArrayRef.h"
28#include "llvm/ADT/SmallVector.h"
29#include "llvm/ADT/TypeSwitch.h"
30#include "llvm/Frontend/OpenMP/OMPConstants.h"
31#include "llvm/Frontend/OpenMP/OMPIRBuilder.h"
32#include "llvm/IR/Constants.h"
33#include "llvm/IR/DebugInfoMetadata.h"
34#include "llvm/IR/DerivedTypes.h"
35#include "llvm/IR/IRBuilder.h"
36#include "llvm/IR/MDBuilder.h"
37#include "llvm/IR/ReplaceConstant.h"
38#include "llvm/Support/AMDGPUAddrSpace.h"
39#include "llvm/Support/FileSystem.h"
40#include "llvm/Support/MathExtras.h"
41#include "llvm/Support/NVPTXAddrSpace.h"
42#include "llvm/Support/VirtualFileSystem.h"
43#include "llvm/TargetParser/Triple.h"
44#include "llvm/Transforms/Utils/ModuleUtils.h"
45
46#include <cstdint>
47#include <iterator>
48#include <numeric>
49#include <optional>
50#include <utility>
51
52using namespace mlir;
53
55getPrivateDataAddress(llvm::IRBuilderBase &builder,
56 LLVM::ModuleTranslation &moduleTranslation,
57 omp::PrivateClauseOp privatizer, llvm::Value *var);
58
59namespace {
60static llvm::omp::ScheduleKind
61convertToScheduleKind(std::optional<omp::ClauseScheduleKind> schedKind) {
62 if (!schedKind.has_value())
63 return llvm::omp::OMP_SCHEDULE_Default;
64 switch (schedKind.value()) {
65 case omp::ClauseScheduleKind::Static:
66 return llvm::omp::OMP_SCHEDULE_Static;
67 case omp::ClauseScheduleKind::Dynamic:
68 return llvm::omp::OMP_SCHEDULE_Dynamic;
69 case omp::ClauseScheduleKind::Guided:
70 return llvm::omp::OMP_SCHEDULE_Guided;
71 case omp::ClauseScheduleKind::Auto:
72 return llvm::omp::OMP_SCHEDULE_Auto;
73 case omp::ClauseScheduleKind::Runtime:
74 return llvm::omp::OMP_SCHEDULE_Runtime;
75 case omp::ClauseScheduleKind::Distribute:
76 return llvm::omp::OMP_SCHEDULE_Distribute;
77 }
78 llvm_unreachable("unhandled schedule clause argument");
79}
80
81/// ModuleTranslation stack frame for OpenMP operations. This keeps track of the
82/// blocks in which allocas are emitted.
83class OpenMPAllocStackFrame
84 : public StateStackFrameBase<OpenMPAllocStackFrame> {
85public:
87
88 explicit OpenMPAllocStackFrame(
89 llvm::BasicBlock *allocBlock,
90 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks)
91 : allocBlock(allocBlock), deallocBlocks(deallocBlocks) {}
92 llvm::BasicBlock *allocBlock;
93 llvm::SmallVector<llvm::BasicBlock *> deallocBlocks;
94};
95
96/// Stack frame to hold a \see llvm::CanonicalLoopInfo representing the
97/// collapsed canonical loop information corresponding to an \c omp.loop_nest
98/// operation.
99class OpenMPLoopInfoStackFrame
100 : public StateStackFrameBase<OpenMPLoopInfoStackFrame> {
101public:
102 MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(OpenMPLoopInfoStackFrame)
103 llvm::CanonicalLoopInfo *loopInfo = nullptr;
104};
105
106/// Custom error class to signal translation errors that don't need reporting,
107/// since encountering them will have already triggered relevant error messages.
108///
109/// Its purpose is to serve as the glue between MLIR failures represented as
110/// \see LogicalResult instances and \see llvm::Error instances used to
111/// propagate errors through the \see llvm::OpenMPIRBuilder. Generally, when an
112/// error of the first type is raised, a message is emitted directly (the \see
113/// LogicalResult itself does not hold any information). If we need to forward
114/// this error condition as an \see llvm::Error while avoiding triggering some
115/// redundant error reporting later on, we need a custom \see llvm::ErrorInfo
116/// class to just signal this situation has happened.
117///
118/// For example, this class should be used to trigger errors from within
119/// callbacks passed to the \see OpenMPIRBuilder when they were triggered by the
120/// translation of their own regions. This unclutters the error log from
121/// redundant messages.
122class PreviouslyReportedError
123 : public llvm::ErrorInfo<PreviouslyReportedError> {
124public:
125 void log(raw_ostream &) const override {
126 // Do not log anything.
127 }
128
129 std::error_code convertToErrorCode() const override {
130 llvm_unreachable(
131 "PreviouslyReportedError doesn't support ECError conversion");
132 }
133
134 // Used by ErrorInfo::classID.
135 static char ID;
136};
137
138char PreviouslyReportedError::ID = 0;
139
140/*
141 * Custom class for processing linear clause for omp.wsloop
142 * and omp.simd. Linear clause translation requires setup,
143 * initialization, update, and finalization at varying
144 * basic blocks in the IR. This class helps maintain
145 * internal state to allow consistent translation in
146 * each of these stages.
147 */
148
149class LinearClauseProcessor {
150
151private:
152 SmallVector<llvm::Type *> linearVarTypes;
153 SmallVector<llvm::Value *> linearSteps;
154 // The `linear` privatizer, original and private copy of each linear var.
155 SmallVector<omp::PrivateClauseOp> linearPrivatizers;
156 SmallVector<llvm::Value *> linearOrigVars;
157 SmallVector<llvm::Value *> linearPrivateVars;
158 // The value of each linear var on entry to the loop.
159 SmallVector<llvm::Value *> linearStartVals;
160 llvm::BasicBlock *linearFinalizationBB;
161 llvm::BasicBlock *linearExitBB;
162 llvm::BasicBlock *linearLastIterExitBB;
163 std::optional<size_t> linearLoopIVIndex;
164
165 // Address of the value of linear var `index` held by `var`.
166 llvm::Expected<llvm::Value *>
167 getAddress(llvm::IRBuilderBase &builder,
168 LLVM::ModuleTranslation &moduleTranslation, size_t index,
169 llvm::Value *var) {
170 return getPrivateDataAddress(builder, moduleTranslation,
171 linearPrivatizers[index], var);
172 }
173
174 // Copy the current value of each linear var back to the original.
175 llvm::Error storeLinearVars(llvm::IRBuilderBase &builder,
176 LLVM::ModuleTranslation &moduleTranslation) {
177 for (size_t index = 0; index < linearOrigVars.size(); index++) {
178 llvm::Expected<llvm::Value *> from = getAddress(
179 builder, moduleTranslation, index, linearPrivateVars[index]);
180 if (!from)
181 return from.takeError();
182 llvm::LoadInst *linearVarTemp =
183 builder.CreateLoad(linearVarTypes[index], *from);
184 llvm::Expected<llvm::Value *> to =
185 getAddress(builder, moduleTranslation, index, linearOrigVars[index]);
186 if (!to)
187 return to.takeError();
188 builder.CreateStore(linearVarTemp, *to);
189 }
190 return llvm::Error::success();
191 }
192
193public:
194 // Register type for the linear variables
195 void registerType(LLVM::ModuleTranslation &moduleTranslation,
196 mlir::Attribute &ty) {
197 linearVarTypes.push_back(moduleTranslation.convertType(
198 mlir::cast<mlir::TypeAttr>(ty).getValue()));
199 }
200
201 // Register a linear var, allocating space for its start value at `allocaIP`
202 void createLinearVar(llvm::IRBuilderBase &builder,
203 const llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
204 omp::PrivateClauseOp privatizer, llvm::Value *origVar,
205 llvm::Value *privateVar, int idx) {
206 llvm::IRBuilderBase::InsertPointGuard guard(builder);
207 builder.SetInsertPoint(allocaIP.getNodeParent()->getTerminator());
208 linearStartVals.push_back(
209 builder.CreateAlloca(linearVarTypes[idx], nullptr, ".linear_var"));
210 linearPrivatizers.push_back(privatizer);
211 linearOrigVars.push_back(origVar);
212 linearPrivateVars.push_back(privateVar);
213 }
214
215 // Initialize linear step
216 inline void initLinearStep(LLVM::ModuleTranslation &moduleTranslation,
217 mlir::Value &linearStep) {
218 linearSteps.push_back(moduleTranslation.lookupValue(linearStep));
219 }
220
221 // Emit IR for initialization of linear variables
222 llvm::Error initLinearVar(llvm::IRBuilderBase &builder,
223 LLVM::ModuleTranslation &moduleTranslation,
224 llvm::BasicBlock *loopPreHeader) {
225 builder.SetInsertPoint(loopPreHeader->getTerminator());
226 for (size_t index = 0; index < linearOrigVars.size(); index++) {
227 llvm::Expected<llvm::Value *> orig =
228 getAddress(builder, moduleTranslation, index, linearOrigVars[index]);
229 if (!orig)
230 return orig.takeError();
231 llvm::LoadInst *linearVarLoad =
232 builder.CreateLoad(linearVarTypes[index], *orig);
233 builder.CreateStore(linearVarLoad, linearStartVals[index]);
234 }
235 return llvm::Error::success();
236 }
237
238 // Find linear iteration variable and save it for later updates
239 LogicalResult initLinearIV(omp::SimdOp simdOp) {
240 auto loopOp = cast<omp::LoopNestOp>(simdOp.getWrappedLoop());
241 // NOTE iteration variables can only be linear in non-nested loops.
242 if (loopOp.getIVs().size() != 1)
243 return success();
244 // Currently, frontends using `omp.simd` always generate a store from the
245 // `omp.loop_nest`'s IV to the corresponding iteration variable, possibly
246 // through the data address of its private copy.
247 //
248 // TODO Add an attribute to `omp.loop_nest` that explicitly lists the
249 // variables that correspond to the loop induction variables.
250 MutableArrayRef<BlockArgument> linearArgs =
251 cast<omp::BlockArgOpenMPOpInterface>(*simdOp).getLinearBlockArgs();
252 SmallVector<Value> ivValues{loopOp.getIVs().front()};
253 for (Operation *user : loopOp.getIVs().front().getUsers())
254 if (isa<LLVM::SExtOp, LLVM::ZExtOp, LLVM::TruncOp>(user))
255 ivValues.push_back(user->getResult(0));
256
257 for (Value ivValue : ivValues) {
258 for (Operation *user : ivValue.getUsers()) {
259 auto storeOp = dyn_cast<LLVM::StoreOp>(user);
260 if (!storeOp || storeOp.getValue() != ivValue)
261 continue;
262 // Look through loads, GEPs and copies of a private copy, e.g. of a
263 // descriptor, to find the linear block argument the IV is stored to.
264 Value addr = storeOp.getAddr();
265 while (true) {
266 Operation *def = addr.getDefiningOp();
267 if (auto load = dyn_cast_if_present<LLVM::LoadOp>(def)) {
268 addr = load.getAddr();
269 } else if (auto gep = dyn_cast_if_present<LLVM::GEPOp>(def)) {
270 addr = gep.getBase();
271 } else {
272 auto copy = llvm::find_if(addr.getUsers(), [&](Operation *user) {
273 auto memcpy = dyn_cast<LLVM::MemcpyOp>(user);
274 return memcpy && memcpy.getDst() == addr &&
275 llvm::is_contained(linearArgs, memcpy.getSrc());
276 });
277 if (copy == addr.getUsers().end())
278 break;
279 addr = cast<LLVM::MemcpyOp>(*copy).getSrc();
280 }
281 }
282 auto *it = llvm::find(linearArgs, addr);
283 if (it == linearArgs.end())
284 continue;
285 size_t index = std::distance(linearArgs.begin(), it);
286 if (linearLoopIVIndex && *linearLoopIVIndex != index)
287 return simdOp.emitError(
288 "Could not determine the linear variable associated with the "
289 "loop nest induction variable");
290 linearLoopIVIndex = index;
291 }
292 }
293 return success();
294 }
295
296 // Emit IR for updating Linear variables
297 llvm::Error updateLinearVar(llvm::IRBuilderBase &builder,
298 LLVM::ModuleTranslation &moduleTranslation,
299 llvm::BasicBlock *loopBody,
300 llvm::Value *loopInductionVar) {
301 builder.SetInsertPoint(loopBody->getTerminator());
302 for (size_t index = 0; index < linearStartVals.size(); index++) {
303 llvm::Type *linearVarType = linearVarTypes[index];
304 llvm::Value *iv = loopInductionVar;
305 llvm::Value *step = linearSteps[index];
306 llvm::Expected<llvm::Value *> result = getAddress(
307 builder, moduleTranslation, index, linearPrivateVars[index]);
308 if (!result)
309 return result.takeError();
310
311 if (!iv->getType()->isIntegerTy())
312 llvm_unreachable("OpenMP loop induction variable must be an integer "
313 "type");
314
315 if (linearVarType->isIntegerTy()) {
316 // Integer path: normalize all arithmetic to linearVarType
317 iv = builder.CreateSExtOrTrunc(iv, linearVarType);
318 step = builder.CreateSExtOrTrunc(step, linearVarType);
319
320 llvm::LoadInst *linearVarStart =
321 builder.CreateLoad(linearVarType, linearStartVals[index]);
322 llvm::Value *mulInst = builder.CreateMul(iv, step);
323 llvm::Value *addInst = builder.CreateAdd(linearVarStart, mulInst);
324 builder.CreateStore(addInst, *result);
325 } else if (linearVarType->isFloatingPointTy()) {
326 // Float path: perform multiply in integer, then convert to float
327 step = builder.CreateSExtOrTrunc(step, iv->getType());
328 llvm::Value *mulInst = builder.CreateMul(iv, step);
329
330 llvm::LoadInst *linearVarStart =
331 builder.CreateLoad(linearVarType, linearStartVals[index]);
332 llvm::Value *mulFp = builder.CreateSIToFP(mulInst, linearVarType);
333 llvm::Value *addInst = builder.CreateFAdd(linearVarStart, mulFp);
334 builder.CreateStore(addInst, *result);
335 } else {
336 llvm_unreachable(
337 "Linear variable must be of integer or floating-point type");
338 }
339 }
340 return llvm::Error::success();
341 }
342
343 // Emit IR for updating linear iteration variables on loop exit
344 llvm::Error updateLinearIV(llvm::IRBuilderBase &builder,
345 LLVM::ModuleTranslation &moduleTranslation) {
346 if (!linearLoopIVIndex)
347 return llvm::Error::success();
348 size_t index = *linearLoopIVIndex;
349
350 // Add one more step to the linear iteration variable
351 llvm::Type *varType = linearVarTypes[index];
352 if (!varType->isIntegerTy())
353 llvm_unreachable("Linear iteration variable must be of integer type");
354 llvm::Expected<llvm::Value *> var =
355 getAddress(builder, moduleTranslation, index, linearPrivateVars[index]);
356 if (!var)
357 return var.takeError();
358 llvm::Value *step = builder.CreateSExtOrTrunc(linearSteps[index], varType);
359 llvm::Value *val = builder.CreateLoad(varType, *var);
360 llvm::Value *addInst = builder.CreateAdd(val, step);
361 builder.CreateStore(addInst, *var);
362 return llvm::Error::success();
363 }
364
365 // Linear variable finalization is conditional on the last logical iteration.
366 // Create BB splits to manage the same.
367 void splitLinearFiniBB(llvm::IRBuilderBase &builder,
368 llvm::BasicBlock *loopExit) {
369 linearFinalizationBB = loopExit->splitBasicBlock(
370 loopExit->getTerminator(), "omp_loop.linear_finalization");
371 linearExitBB = linearFinalizationBB->splitBasicBlock(
372 linearFinalizationBB->getTerminator(), "omp_loop.linear_exit");
373 linearLastIterExitBB = linearFinalizationBB->splitBasicBlock(
374 linearFinalizationBB->getTerminator(), "omp_loop.linear_lastiter_exit");
375 }
376
377 // Finalize the linear vars
378 llvm::OpenMPIRBuilder::InsertPointOrErrorTy
379 finalizeLinearVar(llvm::IRBuilderBase &builder,
380 LLVM::ModuleTranslation &moduleTranslation,
381 llvm::Value *lastIter) {
382 // Emit condition to check whether last logical iteration is being executed
383 builder.SetInsertPoint(linearFinalizationBB->getTerminator());
384 llvm::Value *loopLastIterLoad = builder.CreateLoad(
385 llvm::Type::getInt32Ty(builder.getContext()), lastIter);
386 llvm::Value *isLast =
387 builder.CreateCmp(llvm::CmpInst::ICMP_NE, loopLastIterLoad,
388 llvm::ConstantInt::get(
389 llvm::Type::getInt32Ty(builder.getContext()), 0));
390 // Store the linear variable values to original variables.
391 builder.SetInsertPoint(linearLastIterExitBB->getTerminator());
392 if (llvm::Error err = storeLinearVars(builder, moduleTranslation))
393 return std::move(err);
394
395 // Create conditional branch such that the linear variable
396 // values are stored to original variables only at the
397 // last logical iteration
398 builder.SetInsertPoint(linearFinalizationBB->getTerminator());
399 builder.CreateCondBr(isLast, linearLastIterExitBB, linearExitBB);
400 linearFinalizationBB->getTerminator()->eraseFromParent();
401 // Emit barrier
402 builder.SetInsertPoint(linearExitBB->getTerminator());
403 return moduleTranslation.getOpenMPBuilder()->createBarrier(
404 builder, llvm::omp::OMPD_barrier);
405 }
406
407 // Emit stores for linear variables. Useful in case of SIMD
408 // construct.
409 llvm::Error
410 emitStoresForLinearVar(llvm::IRBuilderBase &builder,
411 LLVM::ModuleTranslation &moduleTranslation) {
412 return storeLinearVars(builder, moduleTranslation);
413 }
414};
415
416} // namespace
417
418/// Looks up from the operation from and returns the PrivateClauseOp with
419/// name symbolName
420static omp::PrivateClauseOp findPrivatizer(Operation *from,
421 SymbolRefAttr symbolName) {
422 omp::PrivateClauseOp privatizer =
424 symbolName);
425 assert(privatizer && "privatizer not found in the symbol table");
426 return privatizer;
427}
428
429/// Check whether translation to LLVM IR for the given operation is currently
430/// supported. If not, descriptive diagnostics will be emitted to let users know
431/// this is a not-yet-implemented feature.
432///
433/// \returns success if no unimplemented features are needed to translate the
434/// given operation.
435static LogicalResult checkImplementationStatus(Operation &op) {
436 auto todo = [&op](StringRef clauseName) {
437 return op.emitError() << "not yet implemented: Unhandled clause "
438 << clauseName << " in " << op.getName()
439 << " operation";
440 };
441
442 auto checkAllocate = [&todo](auto op, LogicalResult &result) {
443 if (!op.getAllocateVars().empty() || !op.getAllocatorVars().empty())
444 result = todo("allocate");
445 };
446 auto checkBare = [&todo](auto op, LogicalResult &result) {
447 if (op.getKernelType() == omp::TargetExecMode::bare)
448 result = todo("ompx_bare");
449 };
450 auto checkDepend = [&todo](auto op, LogicalResult &result) {
451 if (!op.getDependVars().empty() || op.getDependKinds() ||
452 !op.getDependIterated().empty() || op.getDependIteratedKinds())
453 result = todo("depend");
454 };
455 auto checkHint = [](auto op, LogicalResult &) {
456 if (op.getHint())
457 op.emitWarning("hint clause discarded");
458 };
459 auto checkInReduction = [&todo](auto op, LogicalResult &result) {
460 if (isa<omp::TargetOp, omp::TaskOp, omp::TaskloopContextOp>(
461 op.getOperation())) {
462 if (auto byrefAttr = op.getInReductionByref()) {
463 for (bool isByRef : *byrefAttr) {
464 if (isByRef) {
465 result = todo("in_reduction with byref modifier");
466 return;
467 }
468 }
469 }
470 if (isa<omp::TargetOp>(op.getOperation())) {
471 if (auto inReductionSyms = op.getInReductionSyms()) {
472 for (auto sym :
473 (*inReductionSyms).template getAsRange<SymbolRefAttr>()) {
474 auto decl =
476 op, sym);
477 assert(decl &&
478 "symbol resolution should be guaranteed by the op verifier");
479 if (decl.getInitializerRegion().front().getNumArguments() != 1) {
480 result = todo("in_reduction with two-argument initializer");
481 return;
482 }
483 if (!decl.getCleanupRegion().empty()) {
484 result = todo("in_reduction with cleanup region");
485 return;
486 }
487 }
488 }
489 }
490 } else if (!op.getInReductionVars().empty() || op.getInReductionByref() ||
491 op.getInReductionSyms()) {
492 result = todo("in_reduction");
493 }
494 };
495 auto checkNowait = [&todo](auto op, LogicalResult &result) {
496 if (op.getNowait())
497 result = todo("nowait");
498 };
499 auto checkOrder = [&todo](auto op, LogicalResult &result) {
500 if (op.getOrder() || op.getOrderMod())
501 result = todo("order");
502 };
503 auto checkPrivate = [&todo](auto op, LogicalResult &result) {
504 if (!op.getPrivateVars().empty() || op.getPrivateSyms())
505 result = todo("privatization");
506 };
507 auto checkReduction = [&todo](auto op, LogicalResult &result) {
508 if (isa<omp::TeamsOp>(op))
509 if (!op.getReductionVars().empty() || op.getReductionByref() ||
510 op.getReductionSyms())
511 result = todo("reduction");
512 if (op.getReductionMod() &&
513 op.getReductionMod().value() != omp::ReductionModifier::defaultmod) {
514 omp::ReductionModifier mod = op.getReductionMod().value();
515 // The `task` reduction modifier is supported on the parallel and
516 // worksharing (do/for and sections) constructs. Other modifiers, and the
517 // `task` modifier on other constructs, are not yet implemented.
518 bool taskModifierSupported =
519 mod == omp::ReductionModifier::task &&
520 isa<omp::ParallelOp, omp::WsloopOp, omp::SectionsOp>(op);
521 if (!taskModifierSupported) {
522 result = todo("reduction with modifier");
523 } else if (auto byref = op.getReductionByref()) {
524 // The task reduction modifier lowering only handles non-byref
525 // reductions for now.
526 for (bool isByRef : *byref)
527 if (isByRef) {
528 result = todo("task reduction modifier with by-ref reduction");
529 break;
530 }
531 }
532 }
533 };
534 auto checkTaskReductionByref = [&todo](auto op, LogicalResult &result) {
535 if (auto byrefAttr = op.getTaskReductionByref())
536 for (bool isByRef : *byrefAttr)
537 if (isByRef) {
538 result = todo("task_reduction with byref modifier");
539 return;
540 }
541 };
542 auto checkReductionByref = [&todo](auto op, LogicalResult &result) {
543 if (auto byrefAttr = op.getReductionByref())
544 for (bool isByRef : *byrefAttr)
545 if (isByRef) {
546 result = todo("reduction with byref modifier");
547 return;
548 }
549 };
550 auto checkNumTeams = [&todo](auto op, LogicalResult &result) {
551 if (op.hasNumTeamsMultiDim())
552 result = todo("num_teams with multi-dimensional values");
553 };
554 auto checkNumThreads = [&todo](auto op, LogicalResult &result) {
555 if (op.hasNumThreadsMultiDim())
556 result = todo("num_threads with multi-dimensional values");
557 };
558
559 auto checkThreadLimit = [&todo](auto op, LogicalResult &result) {
560 if (op.hasThreadLimitMultiDim())
561 result = todo("thread_limit with multi-dimensional values");
562 };
563 auto checkMap = [&todo](auto op, LogicalResult &result) {
564 if (!op.getMapIterated().empty())
565 result = todo("map/motion clause with iterator modifier");
566 };
567
568 auto checkDynGroupprivate = [&todo](auto op, LogicalResult &result) {
569 if (op.getDynGroupprivateSize())
570 result = todo("dyn_groupprivate");
571 };
572
573 LogicalResult result = success();
575 .Case([&](omp::DistributeOp op) {
576 checkAllocate(op, result);
577 checkOrder(op, result);
578 })
579 .Case([&](omp::SectionsOp op) {
580 checkAllocate(op, result);
581 checkPrivate(op, result);
582 checkReduction(op, result);
583 })
584 .Case([&](omp::ScopeOp op) { checkReduction(op, result); })
585 .Case([&](omp::SingleOp op) {
586 checkAllocate(op, result);
587 checkPrivate(op, result);
588 })
589 .Case([&](omp::TeamsOp op) {
590 checkAllocate(op, result);
591 checkPrivate(op, result);
592 checkNumTeams(op, result);
593 checkThreadLimit(op, result);
594 checkDynGroupprivate(op, result);
595 })
596 .Case([&](omp::TaskOp op) {
597 checkAllocate(op, result);
598 checkInReduction(op, result);
599 })
600 .Case([&](omp::TaskgroupOp op) {
601 checkAllocate(op, result);
602 checkTaskReductionByref(op, result);
603 })
604 .Case([&](omp::DispatchOp op) {
605 // OpenMP 5.1 dispatch creates an explicit task; nowait controls whether
606 // it is included. Diagnose unsupported asynchronous tasking before 5.2,
607 // where the nowait property has no effect on dispatch.
609 op->getParentOfType<ModuleOp>(), /*fallback=*/51);
610 if (version < 52)
611 checkNowait(op, result);
612 })
613 .Case([&](omp::TaskloopContextOp op) {
614 checkAllocate(op, result);
615 checkInReduction(op, result);
616 checkReduction(op, result);
617 checkReductionByref(op, result);
618 })
619 .Case([&](omp::WsloopOp op) {
620 checkAllocate(op, result);
621 checkOrder(op, result);
622 checkReduction(op, result);
623 })
624 .Case([&](omp::ParallelOp op) {
625 checkReduction(op, result);
626 checkNumThreads(op, result);
627 })
628 .Case([&](omp::SimdOp op) { checkReduction(op, result); })
629 .Case<omp::AtomicReadOp, omp::AtomicWriteOp, omp::AtomicUpdateOp,
630 omp::AtomicCaptureOp>([&](auto op) { checkHint(op, result); })
631 .Case([&](omp::AtomicCompareOp op) {
632 checkHint(op, result);
633 Region &region = op.getRegion();
634 if (region.empty())
635 return;
636 mlir::Type argType = region.front().getArgument(0).getType();
637 auto structTy = dyn_cast<LLVM::LLVMStructType>(argType);
638 if (!structTy)
639 return;
640 DataLayout dl = DataLayout(op->getParentOfType<ModuleOp>());
641 unsigned totalBits = dl.getTypeSizeInBits(structTy);
642 if (totalBits > 128)
643 result = todo("compare for complex types wider than 128 bits");
644 })
645 .Case<omp::TargetEnterDataOp, omp::TargetExitDataOp>([&](auto op) {
646 checkDepend(op, result);
647 checkMap(op, result);
648 })
649 .Case([&](omp::TargetUpdateOp op) {
650 checkDepend(op, result);
651 checkMap(op, result);
652 })
653 .Case([&](omp::TargetOp op) {
654 checkAllocate(op, result);
655 checkBare(op, result);
656 checkInReduction(op, result);
657 checkMap(op, result);
658 checkThreadLimit(op, result);
659 })
660 .Case([&](omp::TargetDataOp op) { checkMap(op, result); })
661 .Case([&](omp::DeclareMapperInfoOp op) { checkMap(op, result); })
662 .Default([](Operation &) {
663 // Assume all clauses for an operation can be translated unless they are
664 // checked above.
665 });
666 return result;
667}
668
669static LogicalResult handleError(llvm::Error error, Operation &op) {
670 LogicalResult result = success();
671 if (error) {
672 llvm::handleAllErrors(
673 std::move(error),
674 [&](const PreviouslyReportedError &) { result = failure(); },
675 [&](const llvm::ErrorInfoBase &err) {
676 result = op.emitError(err.message());
677 });
678 }
679 return result;
680}
681
682template <typename T>
683static LogicalResult handleError(llvm::Expected<T> &result, Operation &op) {
684 if (!result)
685 return handleError(result.takeError(), op);
686
687 return success();
688}
689
690/// Find the insertion point for allocas given the current insertion point for
691/// normal operations in the builder.
692static llvm::OpenMPIRBuilder::InsertPointTy findAllocInsertPoints(
693 llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation,
694 llvm::SmallVectorImpl<llvm::BasicBlock *> *deallocBlocks = nullptr) {
695 // If there is an allocation insertion point on stack, i.e. we are in a nested
696 // operation and a specific point was provided by some surrounding operation,
697 // use it.
698 llvm::OpenMPIRBuilder::InsertPointTy allocInsertPoint;
699 llvm::ArrayRef<llvm::BasicBlock *> deallocInsertPoints;
700 WalkResult walkResult = moduleTranslation.stackWalk<OpenMPAllocStackFrame>(
701 [&](OpenMPAllocStackFrame &frame) {
702 allocInsertPoint = frame.allocBlock->getFirstInsertionPt();
703 deallocInsertPoints = frame.deallocBlocks;
704 return WalkResult::interrupt();
705 });
706 // In cases with multiple levels of outlining, the tree walk might find an
707 // insertion point that is inside the original function while the builder
708 // insertion point is inside the outlined function. We need to make sure that
709 // we do not use it in those cases.
710 if (walkResult.wasInterrupted() &&
711 allocInsertPoint.getNodeParent()->getParent() ==
712 builder.GetInsertBlock()->getParent()) {
713 if (deallocBlocks)
714 deallocBlocks->insert(deallocBlocks->end(), deallocInsertPoints.begin(),
715 deallocInsertPoints.end());
716 return allocInsertPoint;
717 }
718
719 // Otherwise, insert to the entry block of the surrounding function.
720 // If the current IRBuilder InsertPoint is the function's entry, it cannot
721 // also be used for alloca insertion which would result in insertion order
722 // confusion. Create a new BasicBlock for the Builder and use the entry block
723 // for the allocs.
724 // TODO: Create a dedicated alloca BasicBlock at function creation such that
725 // we do not need to move the current InsertPoint here.
726 if (builder.GetInsertBlock() ==
727 &builder.GetInsertBlock()->getParent()->getEntryBlock()) {
728 assert(builder.GetInsertPoint() == builder.GetInsertBlock()->end() &&
729 "Assuming end of basic block");
730 llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(
731 builder.getContext(), "entry", builder.GetInsertBlock()->getParent(),
732 builder.GetInsertBlock()->getNextNode());
733 builder.CreateBr(entryBB);
734 builder.SetInsertPoint(entryBB);
735 }
736
737 // Collect exit blocks, which is where explicit deallocations should happen in
738 // this case.
739 if (deallocBlocks) {
740 for (llvm::BasicBlock &block : *builder.GetInsertBlock()->getParent()) {
741 // TODO: This currently results in no blocks being added to the list when
742 // all exit blocks of the enclosing function have not been lowered before
743 // this is reached.
744 llvm::Instruction *terminator = block.getTerminatorOrNull();
745 if (isa_and_present<llvm::ReturnInst>(terminator))
746 deallocBlocks->emplace_back(&block);
747 }
748 }
749
750 llvm::BasicBlock &funcEntryBlock =
751 builder.GetInsertBlock()->getParent()->getEntryBlock();
752 return funcEntryBlock.getFirstInsertionPt();
753}
754
755/// Find the loop information structure for the loop nest being translated. It
756/// will return a `null` value unless called from the translation function for
757/// a loop wrapper operation after successfully translating its body.
758static llvm::CanonicalLoopInfo *
760 llvm::CanonicalLoopInfo *loopInfo = nullptr;
761 moduleTranslation.stackWalk<OpenMPLoopInfoStackFrame>(
762 [&](OpenMPLoopInfoStackFrame &frame) {
763 loopInfo = frame.loopInfo;
764 return WalkResult::interrupt();
765 });
766 return loopInfo;
767}
768
769/// Converts the given region that appears within an OpenMP dialect operation to
770/// LLVM IR, creating a branch from the `sourceBlock` to the entry block of the
771/// region, and a branch from any block with an successor-less OpenMP terminator
772/// to `continuationBlock`. Populates `continuationBlockPHIs` with the PHI nodes
773/// of the continuation block if provided.
775 Region &region, StringRef blockName, llvm::IRBuilderBase &builder,
776 LLVM::ModuleTranslation &moduleTranslation,
777 SmallVectorImpl<llvm::PHINode *> *continuationBlockPHIs = nullptr) {
778 bool isLoopWrapper = isa<omp::LoopWrapperInterface>(region.getParentOp());
779
780 llvm::BasicBlock *continuationBlock =
781 splitBB(builder, true, "omp.region.cont");
782 llvm::BasicBlock *sourceBlock = builder.GetInsertBlock();
783
784 llvm::LLVMContext &llvmContext = builder.getContext();
785 for (Block &bb : region) {
786 llvm::BasicBlock *llvmBB = llvm::BasicBlock::Create(
787 llvmContext, blockName, builder.GetInsertBlock()->getParent(),
788 builder.GetInsertBlock()->getNextNode());
789 moduleTranslation.mapBlock(&bb, llvmBB);
790 }
791
792 llvm::Instruction *sourceTerminator = sourceBlock->getTerminator();
793
794 // Terminators (namely YieldOp) may be forwarding values to the region that
795 // need to be available in the continuation block. Collect the types of these
796 // operands in preparation of creating PHI nodes. This is skipped for loop
797 // wrapper operations, for which we know in advance they have no terminators.
798 SmallVector<llvm::Type *> continuationBlockPHITypes;
799 unsigned numYields = 0;
800
801 if (!isLoopWrapper) {
802 bool operandsProcessed = false;
803 for (Block &bb : region.getBlocks()) {
804 if (omp::YieldOp yield = dyn_cast<omp::YieldOp>(bb.getTerminator())) {
805 if (!operandsProcessed) {
806 for (unsigned i = 0, e = yield->getNumOperands(); i < e; ++i) {
807 continuationBlockPHITypes.push_back(
808 moduleTranslation.convertType(yield->getOperand(i).getType()));
809 }
810 operandsProcessed = true;
811 } else {
812 assert(continuationBlockPHITypes.size() == yield->getNumOperands() &&
813 "mismatching number of values yielded from the region");
814 for (unsigned i = 0, e = yield->getNumOperands(); i < e; ++i) {
815 llvm::Type *operandType =
816 moduleTranslation.convertType(yield->getOperand(i).getType());
817 (void)operandType;
818 assert(continuationBlockPHITypes[i] == operandType &&
819 "values of mismatching types yielded from the region");
820 }
821 }
822 numYields++;
823 }
824 }
825 }
826
827 // Insert PHI nodes in the continuation block for any values forwarded by the
828 // terminators in this region.
829 if (!continuationBlockPHITypes.empty())
830 assert(
831 continuationBlockPHIs &&
832 "expected continuation block PHIs if converted regions yield values");
833 if (continuationBlockPHIs) {
834 llvm::IRBuilderBase::InsertPointGuard guard(builder);
835 continuationBlockPHIs->reserve(continuationBlockPHITypes.size());
836 builder.SetInsertPoint(continuationBlock->begin());
837 for (llvm::Type *ty : continuationBlockPHITypes)
838 continuationBlockPHIs->push_back(builder.CreatePHI(ty, numYields));
839 }
840
841 // Convert blocks one by one in topological order to ensure
842 // defs are converted before uses.
844 for (Block *bb : blocks) {
845 llvm::BasicBlock *llvmBB = moduleTranslation.lookupBlock(bb);
846 // Retarget the branch of the entry block to the entry block of the
847 // converted region (regions are single-entry).
848 if (bb->isEntryBlock()) {
849 assert(sourceTerminator->getNumSuccessors() == 1 &&
850 "provided entry block has multiple successors");
851 assert(sourceTerminator->getSuccessor(0) == continuationBlock &&
852 "ContinuationBlock is not the successor of the entry block");
853 sourceTerminator->setSuccessor(0, llvmBB);
854 }
855
856 llvm::IRBuilderBase::InsertPointGuard guard(builder);
857 if (failed(
858 moduleTranslation.convertBlock(*bb, bb->isEntryBlock(), builder)))
859 return llvm::make_error<PreviouslyReportedError>();
860
861 // Create a direct branch here for loop wrappers to prevent their lack of a
862 // terminator from causing a crash below.
863 if (isLoopWrapper) {
864 builder.CreateBr(continuationBlock);
865 continue;
866 }
867
868 // Special handling for `omp.yield` and `omp.terminator` (we may have more
869 // than one): they return the control to the parent OpenMP dialect operation
870 // so replace them with the branch to the continuation block. We handle this
871 // here to avoid relying inter-function communication through the
872 // ModuleTranslation class to set up the correct insertion point. This is
873 // also consistent with MLIR's idiom of handling special region terminators
874 // in the same code that handles the region-owning operation.
875 Operation *terminator = bb->getTerminator();
876 if (isa<omp::TerminatorOp, omp::YieldOp>(terminator)) {
877 builder.CreateBr(continuationBlock);
878
879 for (unsigned i = 0, e = terminator->getNumOperands(); i < e; ++i)
880 (*continuationBlockPHIs)[i]->addIncoming(
881 moduleTranslation.lookupValue(terminator->getOperand(i)), llvmBB);
882 }
883 }
884 // After all blocks have been traversed and values mapped, connect the PHI
885 // nodes to the results of preceding blocks.
886 LLVM::detail::connectPHINodes(region, moduleTranslation);
887
888 // Remove the blocks and values defined in this region from the mapping since
889 // they are not visible outside of this region. This allows the same region to
890 // be converted several times, that is cloned, without clashes, and slightly
891 // speeds up the lookups.
892 moduleTranslation.forgetMapping(region);
893
894 return continuationBlock;
895}
896
897/// Convert ProcBindKind from MLIR-generated enum to LLVM enum.
898static llvm::omp::ProcBindKind getProcBindKind(omp::ClauseProcBindKind kind) {
899 switch (kind) {
900 case omp::ClauseProcBindKind::Close:
901 return llvm::omp::ProcBindKind::OMP_PROC_BIND_close;
902 case omp::ClauseProcBindKind::Master:
903 return llvm::omp::ProcBindKind::OMP_PROC_BIND_master;
904 case omp::ClauseProcBindKind::Primary:
905 return llvm::omp::ProcBindKind::OMP_PROC_BIND_primary;
906 case omp::ClauseProcBindKind::Spread:
907 return llvm::omp::ProcBindKind::OMP_PROC_BIND_spread;
908 }
909 llvm_unreachable("Unknown ClauseProcBindKind kind");
910}
911
912/// Convert 'dispatch' operation into LLVM IR.
913static LogicalResult
914convertOmpDispatch(Operation &opInst, llvm::IRBuilderBase &builder,
915 LLVM::ModuleTranslation &moduleTranslation) {
916 auto dispatchOp = cast<omp::DispatchOp>(opInst);
917
918 if (failed(checkImplementationStatus(opInst)))
919 return failure();
920
921 auto &region = dispatchOp.getRegion();
922 auto result = convertOmpOpRegions(region, "omp.dispatch.region", builder,
923 moduleTranslation);
924 if (!result)
925 return handleError(result.takeError(), opInst);
926 builder.SetInsertPoint(*result);
927 return success();
928}
929
930/// Converts an OpenMP 'masked' operation into LLVM IR using OpenMPIRBuilder.
931static LogicalResult
932convertOmpMasked(Operation &opInst, llvm::IRBuilderBase &builder,
933 LLVM::ModuleTranslation &moduleTranslation) {
934 auto maskedOp = cast<omp::MaskedOp>(opInst);
935 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
936
937 if (failed(checkImplementationStatus(opInst)))
938 return failure();
939
940 auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
942 // MaskedOp has only one region associated with it.
943 auto &region = maskedOp.getRegion();
944 builder.restoreIP(codeGenIP);
945 return convertOmpOpRegions(region, "omp.masked.region", builder,
946 moduleTranslation)
947 .takeError();
948 };
949
950 // TODO: Perform finalization actions for variables. This has to be
951 // called for variables which have destructors/finalizers.
952 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
953
954 llvm::Value *filterVal = nullptr;
955 if (auto filterVar = maskedOp.getFilteredThreadId()) {
956 filterVal = moduleTranslation.lookupValue(filterVar);
957 } else {
958 llvm::LLVMContext &llvmContext = builder.getContext();
959 filterVal =
960 llvm::ConstantInt::get(llvm::Type::getInt32Ty(llvmContext), /*V=*/0);
961 }
962 assert(filterVal != nullptr);
963 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
964 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
965 moduleTranslation.getOpenMPBuilder()->createMasked(ompLoc, bodyGenCB,
966 finiCB, filterVal);
967
968 if (failed(handleError(afterIP, opInst)))
969 return failure();
970
971 builder.restoreIP(*afterIP);
972 return success();
973}
974
975/// Converts an OpenMP 'master' operation into LLVM IR using OpenMPIRBuilder.
976static LogicalResult
977convertOmpMaster(Operation &opInst, llvm::IRBuilderBase &builder,
978 LLVM::ModuleTranslation &moduleTranslation) {
979 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
980 auto masterOp = cast<omp::MasterOp>(opInst);
981
982 if (failed(checkImplementationStatus(opInst)))
983 return failure();
984
985 auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
987 // MasterOp has only one region associated with it.
988 auto &region = masterOp.getRegion();
989 builder.restoreIP(codeGenIP);
990 return convertOmpOpRegions(region, "omp.master.region", builder,
991 moduleTranslation)
992 .takeError();
993 };
994
995 // TODO: Perform finalization actions for variables. This has to be
996 // called for variables which have destructors/finalizers.
997 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
998
999 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
1000 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
1001 moduleTranslation.getOpenMPBuilder()->createMaster(ompLoc, bodyGenCB,
1002 finiCB);
1003
1004 if (failed(handleError(afterIP, opInst)))
1005 return failure();
1006
1007 builder.restoreIP(*afterIP);
1008 return success();
1009}
1010
1011/// Converts an OpenMP 'critical' operation into LLVM IR using OpenMPIRBuilder.
1012static LogicalResult
1013convertOmpCritical(Operation &opInst, llvm::IRBuilderBase &builder,
1014 LLVM::ModuleTranslation &moduleTranslation) {
1015 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
1016 auto criticalOp = cast<omp::CriticalOp>(opInst);
1017
1018 if (failed(checkImplementationStatus(opInst)))
1019 return failure();
1020
1021 auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
1022 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) {
1023 // CriticalOp has only one region associated with it.
1024 auto &region = cast<omp::CriticalOp>(opInst).getRegion();
1025 builder.restoreIP(codeGenIP);
1026 return convertOmpOpRegions(region, "omp.critical.region", builder,
1027 moduleTranslation)
1028 .takeError();
1029 };
1030
1031 // TODO: Perform finalization actions for variables. This has to be
1032 // called for variables which have destructors/finalizers.
1033 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
1034
1035 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
1036 llvm::LLVMContext &llvmContext = moduleTranslation.getLLVMContext();
1037 llvm::Constant *hint = nullptr;
1038
1039 // If it has a name, it probably has a hint too.
1040 if (criticalOp.getNameAttr()) {
1041 // The verifiers in OpenMP Dialect guarentee that all the pointers are
1042 // non-null
1043 auto symbolRef = cast<SymbolRefAttr>(criticalOp.getNameAttr());
1044 auto criticalDeclareOp =
1046 symbolRef);
1047 hint =
1048 llvm::ConstantInt::get(llvm::Type::getInt32Ty(llvmContext),
1049 static_cast<int>(criticalDeclareOp.getHint()));
1050 }
1051 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
1052 moduleTranslation.getOpenMPBuilder()->createCritical(
1053 ompLoc, bodyGenCB, finiCB, criticalOp.getName().value_or(""), hint);
1054
1055 if (failed(handleError(afterIP, opInst)))
1056 return failure();
1057
1058 builder.restoreIP(*afterIP);
1059 return success();
1060}
1061
1062/// A util to collect info needed to convert delayed privatizers from MLIR to
1063/// LLVM.
1066 llvm::Value *allocatedPtr;
1067 llvm::Value *allocator;
1068 };
1069
1070 template <typename OP>
1072 : blockArgs(llvm::to_vector(
1073 cast<omp::BlockArgOpenMPOpInterface>(*op).getPrivateBlockArgs())) {
1074 mlirVars.reserve(blockArgs.size());
1075 llvmVars.reserve(blockArgs.size());
1076 collectPrivatizationDecls<OP>(op);
1077
1078 for (mlir::Value privateVar : op.getPrivateVars())
1079 mlirVars.push_back(privateVar);
1080 }
1081
1082 /// Adds the linear variables of `op`, which are privatized by their
1083 /// `linear` privatizers, after the private variables.
1084 template <typename OP>
1085 void addLinearVars(OP op) {
1086 numPrivateVars = blockArgs.size();
1087 llvm::append_range(
1088 blockArgs,
1089 cast<omp::BlockArgOpenMPOpInterface>(*op).getLinearBlockArgs());
1090 llvm::append_range(mlirVars, op.getLinearVars());
1091 if (std::optional<ArrayAttr> attr = op.getLinearSyms())
1092 for (auto symbolRef : attr->getAsRange<SymbolRefAttr>())
1093 privatizers.push_back(findPrivatizer(op, symbolRef));
1094 }
1095
1096 /// Number of private variables before the linear ones.
1097 size_t numPrivateVars = 0;
1104
1105private:
1106 /// Populates `privatizations` with privatization declarations used for the
1107 /// given op.
1108 template <class OP>
1109 void collectPrivatizationDecls(OP op) {
1110 std::optional<ArrayAttr> attr = op.getPrivateSyms();
1111 if (!attr)
1112 return;
1113
1114 privatizers.reserve(privatizers.size() + attr->size());
1115 for (auto symbolRef : attr->getAsRange<SymbolRefAttr>()) {
1116 privatizers.push_back(findPrivatizer(op, symbolRef));
1117 }
1118 }
1119};
1120
1121/// Populates `reductions` with reduction declarations used in the given op.
1122template <typename T>
1123static void
1126 std::optional<ArrayAttr> attr = op.getReductionSyms();
1127 if (!attr)
1128 return;
1129
1130 reductions.reserve(reductions.size() + op.getNumReductionVars());
1131 for (auto symbolRef : attr->getAsRange<SymbolRefAttr>()) {
1132 reductions.push_back(
1134 op, symbolRef));
1135 }
1136}
1137
1138/// Look up and validate the declare_reduction ops referenced by a
1139/// reduction-like clause on the omp.taskloop.context translation path. Only
1140/// the non-byref, single-init-arg, no-cleanup form is supported in this
1141/// initial cut; richer shapes are rejected here with a diagnostic. \p syms
1142/// is the clause's symbol list (e.g. `getReductionSyms()` or
1143/// `getInReductionSyms()`), \p opName is the textual op name used in
1144/// diagnostics, and \p clauseName distinguishes "reduction" from
1145/// "in_reduction" in those diagnostics.
1147 Operation *contextOp, std::optional<ArrayAttr> syms, StringRef opName,
1148 StringRef clauseName, SmallVectorImpl<omp::DeclareReductionOp> &out) {
1149 if (!syms)
1150 return success();
1151 out.reserve(out.size() + syms->size());
1152 for (auto sym : syms->getAsRange<SymbolRefAttr>()) {
1154 contextOp, sym);
1155 if (!decl)
1156 return contextOp->emitError()
1157 << "failed to resolve " << clauseName
1158 << " declare_reduction symbol " << sym.getRootReference() << " in "
1159 << opName;
1160 if (decl.getInitializerRegion().front().getNumArguments() != 1)
1161 return contextOp->emitError()
1162 << "not yet implemented: " << clauseName
1163 << " with two-argument initializer in " << opName;
1164 if (!decl.getCleanupRegion().empty())
1165 return contextOp->emitError() << "not yet implemented: " << clauseName
1166 << " with cleanup region in " << opName;
1167 if (decl.getReductionRegion().empty())
1168 return contextOp->emitError()
1169 << clauseName << " declare_reduction is missing a combiner region";
1170 out.push_back(decl);
1171 }
1172 return success();
1173}
1174
1175/// Translates the blocks contained in the given region and appends them to at
1176/// the current insertion point of `builder`. The operations of the entry block
1177/// are appended to the current insertion block. If set, `continuationBlockArgs`
1178/// is populated with translated values that correspond to the values
1179/// omp.yield'ed from the region.
1180static LogicalResult inlineConvertOmpRegions(
1181 Region &region, StringRef blockName, llvm::IRBuilderBase &builder,
1182 LLVM::ModuleTranslation &moduleTranslation,
1183 SmallVectorImpl<llvm::Value *> *continuationBlockArgs = nullptr) {
1184 if (region.empty())
1185 return success();
1186
1187 // Special case for single-block regions that don't create additional blocks:
1188 // insert operations without creating additional blocks.
1189 if (region.hasOneBlock()) {
1190 llvm::BasicBlock *insertBlock = builder.GetInsertBlock();
1191 llvm::Instruction *potentialTerminator =
1192 insertBlock->empty() ? nullptr : &insertBlock->back();
1193
1194 if (potentialTerminator && potentialTerminator->isTerminator()) {
1195 assert(builder.GetInsertPoint() == potentialTerminator->getIterator() &&
1196 "Insertion point should be the terminator");
1197 potentialTerminator->removeFromParent();
1198 // Reset insertion point to the end of the block.
1199 builder.SetInsertPoint(insertBlock);
1200 }
1201 moduleTranslation.mapBlock(&region.front(), insertBlock);
1202
1203 // convertBlock leaves the location of the region's last op current. Restore
1204 // the caller's, as convertOmpOpRegions does for every block it converts.
1205 llvm::DebugLoc loc = builder.getCurrentDebugLocation();
1206 if (failed(moduleTranslation.convertBlock(
1207 region.front(), /*ignoreArguments=*/true, builder)))
1208 return failure();
1209 builder.SetCurrentDebugLocation(loc);
1210
1211 // The continuation arguments are simply the translated terminator operands.
1212 if (continuationBlockArgs)
1213 llvm::append_range(
1214 *continuationBlockArgs,
1215 moduleTranslation.lookupValues(region.front().back().getOperands()));
1216
1217 // Drop the mapping that is no longer necessary so that the same region can
1218 // be processed multiple times.
1219 moduleTranslation.forgetMapping(region);
1220
1221 if (potentialTerminator && potentialTerminator->isTerminator()) {
1222 llvm::BasicBlock *block = builder.GetInsertBlock();
1223 if (block->empty()) {
1224 // this can happen for really simple reduction init regions e.g.
1225 // %0 = llvm.mlir.constant(0 : i32) : i32
1226 // omp.yield(%0 : i32)
1227 // because the llvm.mlir.constant (MLIR op) isn't converted into any
1228 // llvm op
1229 potentialTerminator->insertInto(block, block->begin());
1230 } else {
1231 potentialTerminator->insertAfter(&block->back());
1232 }
1233 }
1234
1235 return success();
1236 }
1237
1239 llvm::Expected<llvm::BasicBlock *> continuationBlock =
1240 convertOmpOpRegions(region, blockName, builder, moduleTranslation, &phis);
1241
1242 if (failed(handleError(continuationBlock, *region.getParentOp())))
1243 return failure();
1244
1245 if (continuationBlockArgs)
1246 llvm::append_range(*continuationBlockArgs, phis);
1247 builder.SetInsertPoint((*continuationBlock)->getFirstInsertionPt());
1248 return success();
1249}
1250
1251namespace {
1252/// Owning equivalents of OpenMPIRBuilder::(Atomic)ReductionGen that are used to
1253/// store lambdas with capture.
1254using OwningReductionGen =
1255 std::function<llvm::OpenMPIRBuilder::InsertPointOrErrorTy(
1256 llvm::OpenMPIRBuilder::InsertPointTy, llvm::Value *, llvm::Value *,
1257 llvm::Value *&)>;
1258using OwningAtomicReductionGen =
1259 std::function<llvm::OpenMPIRBuilder::InsertPointOrErrorTy(
1260 llvm::OpenMPIRBuilder::InsertPointTy, llvm::Type *, llvm::Value *,
1261 llvm::Value *)>;
1262using OwningDataPtrPtrReductionGen =
1263 std::function<llvm::OpenMPIRBuilder::InsertPointOrErrorTy(
1264 llvm::OpenMPIRBuilder::InsertPointTy, llvm::Value *, llvm::Value *&)>;
1265} // namespace
1266
1267/// Create an OpenMPIRBuilder-compatible reduction generator for the given
1268/// reduction declaration. The generator uses `builder` but ignores its
1269/// insertion point.
1270static OwningReductionGen
1271makeReductionGen(omp::DeclareReductionOp decl, llvm::IRBuilderBase &builder,
1272 LLVM::ModuleTranslation &moduleTranslation) {
1273 // The lambda is mutable because we need access to non-const methods of decl
1274 // (which aren't actually mutating it), and we must capture decl by-value to
1275 // avoid the dangling reference after the parent function returns.
1276 OwningReductionGen gen =
1277 [&, decl](llvm::OpenMPIRBuilder::InsertPointTy insertPoint,
1278 llvm::Value *lhs, llvm::Value *rhs,
1279 llvm::Value *&result) mutable
1280 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
1281 moduleTranslation.mapValue(decl.getReductionLhsArg(), lhs);
1282 moduleTranslation.mapValue(decl.getReductionRhsArg(), rhs);
1283 builder.restoreIP(insertPoint);
1285 if (failed(inlineConvertOmpRegions(decl.getReductionRegion(),
1286 "omp.reduction.nonatomic.body", builder,
1287 moduleTranslation, &phis)))
1288 return llvm::createStringError(
1289 "failed to inline `combiner` region of `omp.declare_reduction`");
1290 result = llvm::getSingleElement(phis);
1291 return builder.saveIP();
1292 };
1293 return gen;
1294}
1295
1296/// Create an OpenMPIRBuilder-compatible atomic reduction generator for the
1297/// given reduction declaration. The generator uses `builder` but ignores its
1298/// insertion point. Returns null if there is no atomic region available in the
1299/// reduction declaration.
1300static OwningAtomicReductionGen
1301makeAtomicReductionGen(omp::DeclareReductionOp decl,
1302 llvm::IRBuilderBase &builder,
1303 LLVM::ModuleTranslation &moduleTranslation) {
1304 if (decl.getAtomicReductionRegion().empty())
1305 return OwningAtomicReductionGen();
1306
1307 // The lambda is mutable because we need access to non-const methods of decl
1308 // (which aren't actually mutating it), and we must capture decl by-value to
1309 // avoid the dangling reference after the parent function returns.
1310 OwningAtomicReductionGen atomicGen =
1311 [&, decl](llvm::OpenMPIRBuilder::InsertPointTy insertPoint, llvm::Type *,
1312 llvm::Value *lhs, llvm::Value *rhs) mutable
1313 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
1314 moduleTranslation.mapValue(decl.getAtomicReductionLhsArg(), lhs);
1315 moduleTranslation.mapValue(decl.getAtomicReductionRhsArg(), rhs);
1316 builder.restoreIP(insertPoint);
1318 if (failed(inlineConvertOmpRegions(decl.getAtomicReductionRegion(),
1319 "omp.reduction.atomic.body", builder,
1320 moduleTranslation, &phis)))
1321 return llvm::createStringError(
1322 "failed to inline `atomic` region of `omp.declare_reduction`");
1323 assert(phis.empty());
1324 return builder.saveIP();
1325 };
1326 return atomicGen;
1327}
1328
1329/// Create an OpenMPIRBuilder-compatible `data_ptr_ptr` reduction generator for
1330/// the given reduction declaration. The generator uses `builder` but ignores
1331/// its insertion point. Returns null if there is no `data_ptr_ptr` region
1332/// available in the reduction declaration.
1333static OwningDataPtrPtrReductionGen
1334makeRefDataPtrGen(omp::DeclareReductionOp decl, llvm::IRBuilderBase &builder,
1335 LLVM::ModuleTranslation &moduleTranslation, bool isByRef) {
1336 if (!isByRef || decl.getDataPtrPtrRegion().empty())
1337 return OwningDataPtrPtrReductionGen();
1338
1339 OwningDataPtrPtrReductionGen refDataPtrGen =
1340 [&, decl](llvm::OpenMPIRBuilder::InsertPointTy insertPoint,
1341 llvm::Value *byRefVal, llvm::Value *&result) mutable
1342 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
1343 moduleTranslation.mapValue(decl.getDataPtrPtrRegionArg(), byRefVal);
1344 builder.restoreIP(insertPoint);
1346 if (failed(inlineConvertOmpRegions(decl.getDataPtrPtrRegion(),
1347 "omp.data_ptr_ptr.body", builder,
1348 moduleTranslation, &phis)))
1349 return llvm::createStringError(
1350 "failed to inline `data_ptr_ptr` region of `omp.declare_reduction`");
1351 result = llvm::getSingleElement(phis);
1352 return builder.saveIP();
1353 };
1354
1355 return refDataPtrGen;
1356}
1357
1358/// Converts an OpenMP 'ordered' operation into LLVM IR using OpenMPIRBuilder.
1359static LogicalResult
1360convertOmpOrdered(Operation &opInst, llvm::IRBuilderBase &builder,
1361 LLVM::ModuleTranslation &moduleTranslation) {
1362 auto orderedOp = cast<omp::OrderedOp>(opInst);
1363
1364 if (failed(checkImplementationStatus(opInst)))
1365 return failure();
1366
1367 omp::ClauseDepend dependType = *orderedOp.getDoacrossDependType();
1368 bool isDependSource = dependType == omp::ClauseDepend::dependsource;
1369 unsigned numLoops = *orderedOp.getDoacrossNumLoops();
1370 SmallVector<llvm::Value *> vecValues =
1371 moduleTranslation.lookupValues(orderedOp.getDoacrossDependVars());
1372
1373 size_t indexVecValues = 0;
1374 while (indexVecValues < vecValues.size()) {
1375 SmallVector<llvm::Value *> storeValues;
1376 storeValues.reserve(numLoops);
1377 for (unsigned i = 0; i < numLoops; i++) {
1378 storeValues.push_back(vecValues[indexVecValues]);
1379 indexVecValues++;
1380 }
1381 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
1382 findAllocInsertPoints(builder, moduleTranslation);
1383 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
1384 builder.restoreIP(moduleTranslation.getOpenMPBuilder()->createOrderedDepend(
1385 ompLoc, allocaIP, numLoops, storeValues, ".cnt.addr", isDependSource));
1386 }
1387 return success();
1388}
1389
1390/// Converts an OpenMP 'ordered_region' operation into LLVM IR using
1391/// OpenMPIRBuilder.
1392static LogicalResult
1393convertOmpOrderedRegion(Operation &opInst, llvm::IRBuilderBase &builder,
1394 LLVM::ModuleTranslation &moduleTranslation) {
1395 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
1396 auto orderedRegionOp = cast<omp::OrderedRegionOp>(opInst);
1397
1398 if (failed(checkImplementationStatus(opInst)))
1399 return failure();
1400
1401 auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
1402 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) {
1403 // OrderedOp has only one region associated with it.
1404 auto &region = cast<omp::OrderedRegionOp>(opInst).getRegion();
1405 builder.restoreIP(codeGenIP);
1406 return convertOmpOpRegions(region, "omp.ordered.region", builder,
1407 moduleTranslation)
1408 .takeError();
1409 };
1410
1411 // TODO: Perform finalization actions for variables. This has to be
1412 // called for variables which have destructors/finalizers.
1413 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
1414
1415 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
1416 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
1417 moduleTranslation.getOpenMPBuilder()->createOrderedThreadsSimd(
1418 ompLoc, bodyGenCB, finiCB, !orderedRegionOp.getParLevelSimd());
1419
1420 if (failed(handleError(afterIP, opInst)))
1421 return failure();
1422
1423 builder.restoreIP(*afterIP);
1424 return success();
1425}
1426
1427namespace {
1428/// Contains the arguments for an LLVM store operation
1429struct DeferredStore {
1430 DeferredStore(llvm::Value *value, llvm::Value *address)
1431 : value(value), address(address) {}
1432
1433 llvm::Value *value;
1434 llvm::Value *address;
1435};
1436} // namespace
1437
1438/// Allocate space for privatized reduction variables.
1439/// `deferredStores` contains information to create store operations which needs
1440/// to be inserted after all allocas
1441template <typename T>
1442static LogicalResult
1444 llvm::IRBuilderBase &builder,
1445 LLVM::ModuleTranslation &moduleTranslation,
1446 const llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
1448 SmallVectorImpl<llvm::Value *> &privateReductionVariables,
1449 DenseMap<Value, llvm::Value *> &reductionVariableMap,
1450 SmallVectorImpl<DeferredStore> &deferredStores,
1451 llvm::ArrayRef<bool> isByRefs) {
1452 llvm::IRBuilderBase::InsertPointGuard guard(builder);
1453 builder.SetInsertPoint(allocaIP.getNodeParent()->getTerminator());
1454
1455 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
1456 bool useDeviceSharedMem = omp::opInSharedDeviceContext(*op);
1457
1458 // delay creating stores until after all allocas
1459 deferredStores.reserve(op.getNumReductionVars());
1460
1461 for (std::size_t i = 0; i < op.getNumReductionVars(); ++i) {
1462 Region &allocRegion = reductionDecls[i].getAllocRegion();
1463 if (isByRefs[i]) {
1464 if (allocRegion.empty())
1465 continue;
1466
1468 if (failed(inlineConvertOmpRegions(allocRegion, "omp.reduction.alloc",
1469 builder, moduleTranslation, &phis)))
1470 return op.emitError(
1471 "failed to inline `alloc` region of `omp.declare_reduction`");
1472
1473 assert(phis.size() == 1 && "expected one allocation to be yielded");
1474 builder.SetInsertPoint(allocaIP.getNodeParent()->getTerminator());
1475
1476 // Allocate reduction variable (which is a pointer to the real reduction
1477 // variable allocated in the inlined region)
1478 llvm::Type *ptrTy = builder.getPtrTy();
1479 llvm::Type *varTy =
1480 moduleTranslation.convertType(reductionDecls[i].getType());
1481 llvm::Value *var;
1482 if (useDeviceSharedMem) {
1483 var = ompBuilder->createOMPAllocShared(builder, varTy);
1484 } else {
1485 var = builder.CreateAlloca(varTy);
1486 var = builder.CreatePointerBitCastOrAddrSpaceCast(var, ptrTy);
1487 }
1488
1489 llvm::Value *castPhi =
1490 builder.CreatePointerBitCastOrAddrSpaceCast(phis[0], ptrTy);
1491
1492 deferredStores.emplace_back(castPhi, var);
1493
1494 privateReductionVariables[i] = var;
1495 moduleTranslation.mapValue(reductionArgs[i], castPhi);
1496 reductionVariableMap.try_emplace(op.getReductionVars()[i], castPhi);
1497 } else {
1498 assert(allocRegion.empty() &&
1499 "allocaction is implicit for by-val reduction");
1500
1501 llvm::Type *ptrTy = builder.getPtrTy();
1502 llvm::Type *varTy =
1503 moduleTranslation.convertType(reductionDecls[i].getType());
1504 llvm::Value *var;
1505 if (useDeviceSharedMem) {
1506 var = ompBuilder->createOMPAllocShared(builder, varTy);
1507 } else {
1508 var = builder.CreateAlloca(varTy);
1509 var = builder.CreatePointerBitCastOrAddrSpaceCast(var, ptrTy);
1510 }
1511
1512 moduleTranslation.mapValue(reductionArgs[i], var);
1513 privateReductionVariables[i] = var;
1514 reductionVariableMap.try_emplace(op.getReductionVars()[i], var);
1515 }
1516 }
1517
1518 return success();
1519}
1520
1521/// Map input arguments to reduction initialization region
1522template <typename T>
1523static void
1525 llvm::IRBuilderBase &builder,
1527 DenseMap<Value, llvm::Value *> &reductionVariableMap,
1528 unsigned i) {
1529 // map input argument to the initialization region
1530 mlir::omp::DeclareReductionOp &reduction = reductionDecls[i];
1531 Region &initializerRegion = reduction.getInitializerRegion();
1532 Block &entry = initializerRegion.front();
1533
1534 mlir::Value mlirSource = loop.getReductionVars()[i];
1535 llvm::Value *llvmSource = moduleTranslation.lookupValue(mlirSource);
1536 llvm::Value *origVal = llvmSource;
1537 // If a non-pointer value is expected, load the value from the source pointer.
1538 if (!isa<LLVM::LLVMPointerType>(
1539 reduction.getInitializerMoldArg().getType()) &&
1540 isa<LLVM::LLVMPointerType>(mlirSource.getType())) {
1541 origVal =
1542 builder.CreateLoad(moduleTranslation.convertType(
1543 reduction.getInitializerMoldArg().getType()),
1544 llvmSource, "omp_orig");
1545 }
1546 moduleTranslation.mapValue(reduction.getInitializerMoldArg(), origVal);
1547
1548 if (entry.getNumArguments() > 1) {
1549 llvm::Value *allocation =
1550 reductionVariableMap.lookup(loop.getReductionVars()[i]);
1551 moduleTranslation.mapValue(reduction.getInitializerAllocArg(), allocation);
1552 }
1553}
1554
1555static void
1556setInsertPointForPossiblyEmptyBlock(llvm::IRBuilderBase &builder,
1557 llvm::BasicBlock *block = nullptr) {
1558 if (block == nullptr)
1559 block = builder.GetInsertBlock();
1560
1561 if (!block->hasTerminator())
1562 builder.SetInsertPoint(block);
1563 else
1564 builder.SetInsertPoint(block->getTerminator());
1565}
1566
1567/// Inline reductions' `init` regions. This functions assumes that the
1568/// `builder`'s insertion point is where the user wants the `init` regions to be
1569/// inlined; i.e. it does not try to find a proper insertion location for the
1570/// `init` regions. It also leaves the `builder's insertions point in a state
1571/// where the user can continue the code-gen directly afterwards.
1572template <typename OP>
1573static LogicalResult
1574initReductionVars(OP op, ArrayRef<BlockArgument> reductionArgs,
1575 llvm::IRBuilderBase &builder,
1576 LLVM::ModuleTranslation &moduleTranslation,
1577 llvm::BasicBlock *latestAllocaBlock,
1579 SmallVectorImpl<llvm::Value *> &privateReductionVariables,
1580 DenseMap<Value, llvm::Value *> &reductionVariableMap,
1581 llvm::ArrayRef<bool> isByRef,
1582 SmallVectorImpl<DeferredStore> &deferredStores) {
1583 if (op.getNumReductionVars() == 0)
1584 return success();
1585
1586 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
1587 bool useDeviceSharedMem = omp::opInSharedDeviceContext(*op);
1588
1589 llvm::BasicBlock *initBlock = splitBB(builder, true, "omp.reduction.init");
1590 SmallVector<llvm::Value *> byRefVars(op.getNumReductionVars());
1591 {
1592 llvm::IRBuilderBase::InsertPointGuard guard(builder);
1593 auto allocaIP = latestAllocaBlock->getTerminator()->getIterator();
1594 builder.restoreIP(allocaIP);
1595
1596 for (unsigned i = 0; i < op.getNumReductionVars(); ++i) {
1597 if (isByRef[i]) {
1598 if (!reductionDecls[i].getAllocRegion().empty())
1599 continue;
1600
1601 // TODO: remove after all users of by-ref are updated to use the alloc
1602 // region: Allocate reduction variable (which is a pointer to the real
1603 // reduciton variable allocated in the inlined region)
1604 llvm::Type *varTy =
1605 moduleTranslation.convertType(reductionDecls[i].getType());
1606 if (useDeviceSharedMem)
1607 byRefVars[i] = ompBuilder->createOMPAllocShared(builder, varTy);
1608 else
1609 byRefVars[i] = builder.CreateAlloca(varTy);
1610 }
1611 }
1612 }
1613
1614 setInsertPointForPossiblyEmptyBlock(builder, initBlock);
1615
1616 // store result of the alloc region to the allocated pointer to the real
1617 // reduction variable
1618 for (auto [data, addr] : deferredStores)
1619 builder.CreateStore(data, addr);
1620
1621 // Before the loop, store the initial values of reductions into reduction
1622 // variables. Although this could be done after allocas, we don't want to mess
1623 // up with the alloca insertion point.
1624 for (unsigned i = 0; i < op.getNumReductionVars(); ++i) {
1626
1627 // map block argument to initializer region
1628 mapInitializationArgs(op, moduleTranslation, builder, reductionDecls,
1629 reductionVariableMap, i);
1630
1631 // TODO In some cases (specially on the GPU), the init regions may
1632 // contains stack alloctaions. If the region is inlined in a loop, this is
1633 // problematic. Instead of just inlining the region, handle allocations by
1634 // hoisting fixed length allocations to the function entry and using
1635 // stacksave and restore for variable length ones.
1636 if (failed(inlineConvertOmpRegions(reductionDecls[i].getInitializerRegion(),
1637 "omp.reduction.neutral", builder,
1638 moduleTranslation, &phis)))
1639 return failure();
1640
1641 assert(phis.size() == 1 && "expected one value to be yielded from the "
1642 "reduction neutral element declaration region");
1643
1645
1646 if (isByRef[i]) {
1647 if (!reductionDecls[i].getAllocRegion().empty())
1648 // done in allocReductionVars
1649 continue;
1650
1651 // TODO: this path can be removed once all users of by-ref are updated to
1652 // use an alloc region
1653
1654 // Store the result of the inlined region to the allocated reduction var
1655 // ptr
1656 builder.CreateStore(phis[0], byRefVars[i]);
1657
1658 privateReductionVariables[i] = byRefVars[i];
1659 moduleTranslation.mapValue(reductionArgs[i], phis[0]);
1660 reductionVariableMap.try_emplace(op.getReductionVars()[i], phis[0]);
1661 } else {
1662 // for by-ref case the store is inside of the reduction region
1663 builder.CreateStore(phis[0], privateReductionVariables[i]);
1664 // the rest was handled in allocByValReductionVars
1665 }
1666
1667 // forget the mapping for the initializer region because we might need a
1668 // different mapping if this reduction declaration is re-used for a
1669 // different variable
1670 moduleTranslation.forgetMapping(reductionDecls[i].getInitializerRegion());
1671 }
1672
1673 return success();
1674}
1675
1676/// Collect reduction info
1677template <typename T>
1678static void collectReductionInfo(
1679 T loop, llvm::IRBuilderBase &builder,
1680 LLVM::ModuleTranslation &moduleTranslation,
1683 SmallVectorImpl<OwningAtomicReductionGen> &owningAtomicReductionGens,
1685 const ArrayRef<llvm::Value *> privateReductionVariables,
1687 ArrayRef<bool> isByRef) {
1688 unsigned numReductions = loop.getNumReductionVars();
1689
1690 for (unsigned i = 0; i < numReductions; ++i) {
1691 owningReductionGens.push_back(
1692 makeReductionGen(reductionDecls[i], builder, moduleTranslation));
1693 owningAtomicReductionGens.push_back(
1694 makeAtomicReductionGen(reductionDecls[i], builder, moduleTranslation));
1696 reductionDecls[i], builder, moduleTranslation, isByRef[i]));
1697 }
1698
1699 // Collect the reduction information.
1700 reductionInfos.reserve(numReductions);
1701 for (unsigned i = 0; i < numReductions; ++i) {
1702 llvm::OpenMPIRBuilder::ReductionGenAtomicCBTy atomicGen = nullptr;
1703 if (owningAtomicReductionGens[i])
1704 atomicGen = owningAtomicReductionGens[i];
1705 llvm::Value *variable =
1706 moduleTranslation.lookupValue(loop.getReductionVars()[i]);
1707 mlir::Type allocatedType;
1708 reductionDecls[i].getAllocRegion().walk([&](mlir::Operation *op) {
1709 if (auto alloca = mlir::dyn_cast<LLVM::AllocaOp>(op)) {
1710 allocatedType = alloca.getElemType();
1712 }
1713
1715 });
1716
1717 reductionInfos.push_back(
1718 {moduleTranslation.convertType(reductionDecls[i].getType()), variable,
1719 privateReductionVariables[i],
1720 /*EvaluationKind=*/llvm::OpenMPIRBuilder::EvalKind::Scalar,
1722 /*ReductionGenClang=*/nullptr, atomicGen,
1724 allocatedType ? moduleTranslation.convertType(allocatedType) : nullptr,
1725 reductionDecls[i].getByrefElementType()
1726 ? moduleTranslation.convertType(
1727 *reductionDecls[i].getByrefElementType())
1728 : nullptr});
1729 }
1730}
1731
1732/// handling of DeclareReductionOp's cleanup region
1733static LogicalResult
1735 llvm::ArrayRef<llvm::Value *> privateVariables,
1736 LLVM::ModuleTranslation &moduleTranslation,
1737 llvm::IRBuilderBase &builder, StringRef regionName,
1738 bool shouldLoadCleanupRegionArg = true) {
1739 for (auto [i, cleanupRegion] : llvm::enumerate(cleanupRegions)) {
1740 if (cleanupRegion->empty())
1741 continue;
1742
1743 // map the argument to the cleanup region
1744 Block &entry = cleanupRegion->front();
1745
1746 llvm::Instruction *potentialTerminator =
1747 builder.GetInsertBlock()->empty() ? nullptr
1748 : &builder.GetInsertBlock()->back();
1749 if (potentialTerminator && potentialTerminator->isTerminator())
1750 builder.SetInsertPoint(potentialTerminator);
1751 llvm::Value *privateVarValue =
1752 shouldLoadCleanupRegionArg
1753 ? builder.CreateLoad(
1754 moduleTranslation.convertType(entry.getArgument(0).getType()),
1755 privateVariables[i])
1756 : privateVariables[i];
1757
1758 moduleTranslation.mapValue(entry.getArgument(0), privateVarValue);
1759
1760 if (failed(inlineConvertOmpRegions(*cleanupRegion, regionName, builder,
1761 moduleTranslation)))
1762 return failure();
1763
1764 // clear block argument mapping in case it needs to be re-created with a
1765 // different source for another use of the same reduction decl
1766 moduleTranslation.forgetMapping(*cleanupRegion);
1767 }
1768 return success();
1769}
1770
1771// TODO: not used by ParallelOp
1772template <class OP>
1773static LogicalResult createReductionsAndCleanup(
1774 OP op, llvm::IRBuilderBase &builder,
1775 LLVM::ModuleTranslation &moduleTranslation,
1776 llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
1778 ArrayRef<llvm::Value *> privateReductionVariables, ArrayRef<bool> isByRef,
1779 bool isNowait = false, bool isTeamsReduction = false) {
1780 // Process the reductions if required.
1781 if (op.getNumReductionVars() == 0)
1782 return success();
1783
1785 SmallVector<OwningAtomicReductionGen> owningAtomicReductionGens;
1786 SmallVector<OwningDataPtrPtrReductionGen> owningReductionGenRefDataPtrGens;
1788
1789 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
1790
1791 // Create the reduction generators. We need to own them here because
1792 // ReductionInfo only accepts references to the generators.
1793 collectReductionInfo(op, builder, moduleTranslation, reductionDecls,
1794 owningReductionGens, owningAtomicReductionGens,
1795 owningReductionGenRefDataPtrGens,
1796 privateReductionVariables, reductionInfos, isByRef);
1797
1798 // The call to createReductions below expects the block to have a
1799 // terminator. Create an unreachable instruction to serve as terminator
1800 // and remove it later.
1801 llvm::UnreachableInst *tempTerminator = builder.CreateUnreachable();
1802 builder.SetInsertPoint(tempTerminator);
1803 llvm::DebugLoc reductionLoc = builder.getCurrentDebugLocation();
1804 llvm::OpenMPIRBuilder::InsertPointOrErrorTy contInsertPoint =
1805 ompBuilder->createReductions(builder, allocaIP, reductionInfos, isByRef,
1806 isNowait, isTeamsReduction);
1807
1808 if (failed(handleError(contInsertPoint, *op)))
1809 return failure();
1810
1811 if (!contInsertPoint->isValid())
1812 return op->emitOpError() << "failed to convert reductions";
1813
1814 llvm::OpenMPIRBuilder::InsertPointTy afterIP = *contInsertPoint;
1815 if (!isTeamsReduction) {
1816 llvm::OpenMPIRBuilder::InsertPointOrErrorTy barrierIP =
1817 ompBuilder->createBarrier({*contInsertPoint, reductionLoc},
1818 llvm::omp::OMPD_for);
1819
1820 if (failed(handleError(barrierIP, *op)))
1821 return failure();
1822 afterIP = *barrierIP;
1823 }
1824
1825 tempTerminator->eraseFromParent();
1826 builder.restoreIP(afterIP);
1827
1828 // after the construct, deallocate private reduction variables
1829 SmallVector<Region *> reductionRegions;
1830 llvm::transform(reductionDecls, std::back_inserter(reductionRegions),
1831 [](omp::DeclareReductionOp reductionDecl) {
1832 return &reductionDecl.getCleanupRegion();
1833 });
1834 LogicalResult result = inlineOmpRegionCleanup(
1835 reductionRegions, privateReductionVariables, moduleTranslation, builder,
1836 "omp.reduction.cleanup");
1837
1838 bool useDeviceSharedMem = omp::opInSharedDeviceContext(*op);
1839 if (useDeviceSharedMem) {
1840 for (auto [var, reductionDecl] :
1841 llvm::zip_equal(privateReductionVariables, reductionDecls))
1842 ompBuilder->createOMPFreeShared(
1843 builder, var, moduleTranslation.convertType(reductionDecl.getType()));
1844 }
1845
1846 return result;
1847}
1848
1849static ArrayRef<bool> getIsByRef(std::optional<ArrayRef<bool>> attr) {
1850 if (!attr)
1851 return {};
1852 return *attr;
1853}
1854
1855// TODO: not used by omp.parallel
1856template <typename OP>
1858 OP op, ArrayRef<BlockArgument> reductionArgs, llvm::IRBuilderBase &builder,
1859 LLVM::ModuleTranslation &moduleTranslation,
1860 llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
1862 SmallVectorImpl<llvm::Value *> &privateReductionVariables,
1863 DenseMap<Value, llvm::Value *> &reductionVariableMap,
1864 llvm::ArrayRef<bool> isByRef) {
1865 if (op.getNumReductionVars() == 0)
1866 return success();
1867
1868 SmallVector<DeferredStore> deferredStores;
1869
1870 if (failed(allocReductionVars(op, reductionArgs, builder, moduleTranslation,
1871 allocaIP, reductionDecls,
1872 privateReductionVariables, reductionVariableMap,
1873 deferredStores, isByRef)))
1874 return failure();
1875
1876 return initReductionVars(op, reductionArgs, builder, moduleTranslation,
1877 allocaIP.getNodeParent(), reductionDecls,
1878 privateReductionVariables, reductionVariableMap,
1879 isByRef, deferredStores);
1880}
1881
1882/// Return the llvm::Value * corresponding to the `privateVar` that
1883/// is being privatized. It isn't always as simple as looking up
1884/// moduleTranslation with privateVar. For instance, in case of
1885/// an allocatable, the descriptor for the allocatable is privatized.
1886/// This descriptor is mapped using an MapInfoOp. So, this function
1887/// will return a pointer to the llvm::Value corresponding to the
1888/// block argument for the mapped descriptor.
1889static llvm::Value *
1890findAssociatedValue(Value privateVar, llvm::IRBuilderBase &builder,
1891 LLVM::ModuleTranslation &moduleTranslation,
1892 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1893 if (mappedPrivateVars == nullptr || !mappedPrivateVars->contains(privateVar))
1894 return moduleTranslation.lookupValue(privateVar);
1895
1896 Value blockArg = (*mappedPrivateVars)[privateVar];
1897 Type privVarType = privateVar.getType();
1898 Type blockArgType = blockArg.getType();
1899 assert(isa<LLVM::LLVMPointerType>(blockArgType) &&
1900 "A block argument corresponding to a mapped var should have "
1901 "!llvm.ptr type");
1902
1903 if (privVarType == blockArgType)
1904 return moduleTranslation.lookupValue(blockArg);
1905
1906 // This typically happens when the privatized type is lowered from
1907 // boxchar<KIND> and gets lowered to !llvm.struct<(ptr, i64)>. That is the
1908 // struct/pair is passed by value. But, mapped values are passed only as
1909 // pointers, so before we privatize, we must load the pointer.
1910 if (!isa<LLVM::LLVMPointerType>(privVarType))
1911 return builder.CreateLoad(moduleTranslation.convertType(privVarType),
1912 moduleTranslation.lookupValue(blockArg));
1913
1914 return moduleTranslation.lookupValue(privateVar);
1915}
1916
1917// Privatizer region arguments may be by-value even when the available LLVM
1918// value is storage for that value, e.g. lowered Fortran boxchar descriptors in
1919// task context structs. Materialize the value expected by the region argument
1920// while preserving the existing pointer mapping for pointer arguments.
1921static llvm::Value *
1922materializeRegionArgValue(llvm::IRBuilderBase &builder,
1923 LLVM::ModuleTranslation &moduleTranslation,
1924 BlockArgument regionArg, llvm::Value *value) {
1925 if (!regionArg)
1926 return value;
1927
1928 llvm::Type *regionArgType =
1929 moduleTranslation.convertType(regionArg.getType());
1930 if (regionArgType->isPointerTy() || !value->getType()->isPointerTy())
1931 return value;
1932
1933 return builder.CreateLoad(regionArgType, value);
1934}
1935
1936/// Returns the address of the data in `var`, via the `data_address` region.
1938getPrivateDataAddress(llvm::IRBuilderBase &builder,
1939 LLVM::ModuleTranslation &moduleTranslation,
1940 omp::PrivateClauseOp privatizer, llvm::Value *var) {
1941 Region &region = privatizer.getDataAddressRegion();
1942 if (region.empty())
1943 return var;
1944
1945 BlockArgument arg = privatizer.getDataAddressArg();
1946 moduleTranslation.mapValue(
1947 arg, materializeRegionArgValue(builder, moduleTranslation, arg, var));
1949 if (failed(inlineConvertOmpRegions(region, "omp.private.data_address",
1950 builder, moduleTranslation, &address)))
1951 return llvm::make_error<PreviouslyReportedError>();
1953 return address.front();
1954}
1955
1956/// Initialize a single (first)private variable. You probably want to use
1957/// allocateAndInitPrivateVars instead of this.
1958/// This returns the private variable which has been initialized. This
1959/// variable should be mapped before constructing the body of the Op.
1961initPrivateVar(llvm::IRBuilderBase &builder,
1962 LLVM::ModuleTranslation &moduleTranslation,
1963 omp::PrivateClauseOp &privDecl, llvm::Value *nonPrivateVar,
1964 BlockArgument &blockArg, llvm::Value *llvmPrivateVar,
1965 llvm::BasicBlock *privInitBlock,
1966 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1967 Region &initRegion = privDecl.getInitRegion();
1968 if (initRegion.empty())
1969 return llvmPrivateVar;
1970
1971 assert(nonPrivateVar);
1972 moduleTranslation.mapValue(privDecl.getInitMoldArg(), nonPrivateVar);
1973 moduleTranslation.mapValue(privDecl.getInitPrivateArg(), llvmPrivateVar);
1974
1975 // in-place convert the private initialization region
1977 if (failed(inlineConvertOmpRegions(initRegion, "omp.private.init", builder,
1978 moduleTranslation, &phis)))
1979 return llvm::createStringError(
1980 "failed to inline `init` region of `omp.private`");
1981
1982 assert(phis.size() == 1 && "expected one allocation to be yielded");
1983
1984 // clear init region block argument mapping in case it needs to be
1985 // re-created with a different source for another use of the same
1986 // reduction decl
1987 moduleTranslation.forgetMapping(initRegion);
1988
1989 // Prefer the value yielded from the init region to the allocated private
1990 // variable in case the region is operating on arguments by-value (e.g.
1991 // Fortran character boxes).
1992 return phis[0];
1993}
1994
1995/// Version of initPrivateVar which looks up the nonPrivateVar from mlirPrivVar.
1997 llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation,
1998 omp::PrivateClauseOp &privDecl, Value mlirPrivVar, BlockArgument &blockArg,
1999 llvm::Value *llvmPrivateVar, llvm::BasicBlock *privInitBlock,
2000 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
2001 return initPrivateVar(
2002 builder, moduleTranslation, privDecl,
2003 findAssociatedValue(mlirPrivVar, builder, moduleTranslation,
2004 mappedPrivateVars),
2005 blockArg, llvmPrivateVar, privInitBlock, mappedPrivateVars);
2006}
2007
2008static llvm::Error
2009initPrivateVars(llvm::IRBuilderBase &builder,
2010 LLVM::ModuleTranslation &moduleTranslation,
2011 PrivateVarsInfo &privateVarsInfo,
2012 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
2013 if (privateVarsInfo.blockArgs.empty())
2014 return llvm::Error::success();
2015
2016 llvm::BasicBlock *privInitBlock = splitBB(builder, true, "omp.private.init");
2017 setInsertPointForPossiblyEmptyBlock(builder, privInitBlock);
2018
2019 for (auto [idx, zip] : llvm::enumerate(llvm::zip_equal(
2020 privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
2021 privateVarsInfo.blockArgs, privateVarsInfo.llvmVars))) {
2022 auto [privDecl, mlirPrivVar, blockArg, llvmPrivateVar] = zip;
2024 builder, moduleTranslation, privDecl, mlirPrivVar, blockArg,
2025 llvmPrivateVar, privInitBlock, mappedPrivateVars);
2026
2027 if (!privVarOrErr)
2028 return privVarOrErr.takeError();
2029
2030 llvmPrivateVar = privVarOrErr.get();
2031 moduleTranslation.mapValue(blockArg, llvmPrivateVar);
2032
2034 }
2035
2036 return llvm::Error::success();
2037}
2038
2039static LogicalResult
2041 llvm::IRBuilderBase &builder,
2042 LLVM::ModuleTranslation &moduleTranslation,
2043 PrivateVarsInfo &privateVarsInfo) {
2044 for (Value allocatorVar : allocatorVars) {
2045 if (privateVarsInfo.convertedAllocators.contains(allocatorVar))
2046 continue;
2047
2048 llvm::Value *allocator = moduleTranslation.lookupValue(allocatorVar);
2049 if (!allocator)
2050 return op.emitError("failed to translate OpenMP allocator operand");
2051 if (allocator->getType()->isIntegerTy())
2052 allocator = builder.CreateIntToPtr(allocator, builder.getPtrTy());
2053 else if (allocator->getType()->isPointerTy())
2054 allocator = builder.CreatePointerBitCastOrAddrSpaceCast(
2055 allocator, builder.getPtrTy());
2056 else
2057 return op.emitError(
2058 "OpenMP allocator operand must have integer or pointer type");
2059
2060 privateVarsInfo.convertedAllocators.try_emplace(allocatorVar, allocator);
2061 }
2062 return success();
2063}
2064
2065/// Allocate and initialize delayed private variables. Returns the basic block
2066/// which comes after all of these allocations. llvm::Value * for each of these
2067/// private variables are populated in llvmPrivateVars.
2068template <typename T>
2070 T op, llvm::IRBuilderBase &builder,
2071 LLVM::ModuleTranslation &moduleTranslation,
2072 PrivateVarsInfo &privateVarsInfo,
2073 llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
2074 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr,
2075 std::optional<llvm::OpenMPIRBuilder::InsertPointTy> allocatorIP =
2076 std::nullopt) {
2077 // Allocate private vars
2078 // Save blocks before splits since allocaIP/allocatorIP iterators may follow
2079 // spliced instructions to the new block.
2080 llvm::BasicBlock *allocaBB = allocaIP.getNodeParent();
2081 llvm::Instruction *allocaTerminator = allocaBB->getTerminator();
2082 splitBB(allocaTerminator->getIterator(), true,
2083 allocaTerminator->getDebugLoc(), "omp.region.after_alloca");
2084 // Update the allocaTerminator since the alloca block was split above.
2085 allocaTerminator = allocaBB->getTerminator();
2086 // The new terminator is an uncondition branch created by the splitBB above.
2087 assert(allocaTerminator->getNumSuccessors() == 1 &&
2088 "This is an unconditional branch created by splitBB");
2089 allocaIP = allocaTerminator->getIterator();
2090
2091 llvm::Instruction *allocatorTerminator = nullptr;
2092 llvm::BasicBlock *afterAllocatorAllocations = nullptr;
2093 if (allocatorIP) {
2094 llvm::BasicBlock *allocatorBB = allocatorIP->getNodeParent();
2095 allocatorTerminator = allocatorBB->getTerminator();
2096 afterAllocatorAllocations = splitBB(
2097 allocatorTerminator->getIterator(), true,
2098 allocatorTerminator->getDebugLoc(), "omp.region.after_allocate");
2099 allocatorTerminator = allocatorBB->getTerminator();
2100 assert(allocatorTerminator->getNumSuccessors() == 1 &&
2101 "This is an unconditional branch created by splitBB");
2102 }
2103
2104 std::optional<llvm::IRBuilderBase::InsertPointGuard> guard;
2105 if (!allocatorIP)
2106 guard.emplace(builder);
2107 builder.SetInsertPoint(allocaTerminator);
2108
2109 llvm::DataLayout dataLayout = builder.getDataLayout();
2110 llvm::BasicBlock *afterAllocas = allocaTerminator->getSuccessor(0);
2111
2112 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
2113 bool mightUseDeviceSharedMem = omp::opInSharedDeviceContext(*op);
2114 unsigned int allocaAS =
2115 moduleTranslation.getLLVMModule()->getDataLayout().getAllocaAddrSpace();
2116 unsigned int defaultAS = moduleTranslation.getLLVMModule()
2117 ->getDataLayout()
2118 .getProgramAddressSpace();
2119
2120 SmallVector<int64_t> allocateItemForPrivate(privateVarsInfo.blockArgs.size(),
2121 -1);
2122 ValueRange allocatorVars;
2123 DenseI64ArrayAttr allocateAlignments;
2124 if constexpr (std::is_same_v<T, omp::ParallelOp> ||
2125 std::is_same_v<T, omp::ScopeOp>) {
2126 allocatorVars = op.getAllocatorVars();
2127 allocateAlignments = op.getAllocateAlignmentsAttr();
2128 if (auto privateIndices = op.getAllocatePrivateIndicesAttr())
2129 for (auto [allocateIndex, privateIndex] :
2130 llvm::enumerate(privateIndices.asArrayRef()))
2131 allocateItemForPrivate[privateIndex] = allocateIndex;
2132 }
2133
2134 for (auto [privateIndex, tuple] : llvm::enumerate(llvm::zip_equal(
2135 privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
2136 privateVarsInfo.blockArgs))) {
2137 auto [privDecl, mlirPrivVar, blockArg] = tuple;
2138 llvm::Type *llvmAllocType =
2139 moduleTranslation.convertType(privDecl.getType());
2140 llvm::Value *llvmPrivateVar = nullptr;
2141 int64_t allocateIndex = allocateItemForPrivate[privateIndex];
2142 builder.SetInsertPoint(allocateIndex >= 0 && allocatorTerminator
2143 ? allocatorTerminator
2144 : allocaTerminator);
2145 if (allocateIndex >= 0) {
2146 if (ompBuilder->Config.isTargetDevice() ||
2147 op->template getParentOfType<omp::TargetOp>())
2148 return llvm::createStringError(
2149 "allocate clause in an OpenMP device context is not supported");
2150 if (!llvmAllocType->isSized())
2151 return llvm::createStringError(
2152 "allocate clause private type must have a fixed size");
2153 llvm::TypeSize size = dataLayout.getTypeAllocSize(llvmAllocType);
2154 if (size.isScalable())
2155 return llvm::createStringError(
2156 "allocate clause private type must have a fixed size");
2157 llvm::IntegerType *sizeTy =
2158 moduleTranslation.getLLVMModule()->getDataLayout().getIntPtrType(
2159 moduleTranslation.getLLVMModule()->getContext());
2160 if (!llvm::isUIntN(sizeTy->getBitWidth(), size.getFixedValue()))
2161 return llvm::createStringError(
2162 "OpenMP allocation size cannot be represented by the target size "
2163 "type");
2164 llvm::Value *sizeValue =
2165 llvm::ConstantInt::get(sizeTy, size.getFixedValue());
2166
2167 Value allocatorVar = allocatorVars[allocateIndex];
2168 auto allocator = privateVarsInfo.convertedAllocators.find(allocatorVar);
2169 if (allocator == privateVarsInfo.convertedAllocators.end())
2170 return llvm::createStringError(
2171 "failed to find converted OpenMP allocator operand");
2172 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2173 int64_t alignment =
2174 allocateAlignments ? allocateAlignments[allocateIndex] : 0;
2175 if (alignment != 0) {
2176 // The allocation must be aligned to at least the maximum of the
2177 // requested alignment and the alignment the base language requires
2178 // for the type being allocated.
2179 uint64_t alignmentValue = std::max<uint64_t>(
2180 static_cast<uint64_t>(alignment),
2181 dataLayout.getABITypeAlign(llvmAllocType).value());
2182 if (!llvm::isUIntN(sizeTy->getBitWidth(), alignmentValue))
2183 return llvm::createStringError(
2184 "OpenMP allocation alignment cannot be represented by the "
2185 "target size type");
2186 llvmPrivateVar = ompBuilder->createOMPAlignedAlloc(
2187 ompLoc, llvm::ConstantInt::get(sizeTy, alignmentValue), sizeValue,
2188 allocator->second, "omp.private.alloc");
2189 } else {
2190 llvmPrivateVar = ompBuilder->createOMPAlloc(
2191 ompLoc, sizeValue, allocator->second, "omp.private.alloc");
2192 }
2193 if (!llvmPrivateVar)
2194 return llvm::createStringError(
2195 "failed to create OpenMP private allocation");
2196 privateVarsInfo.allocatorPrivates.push_back(
2197 {llvmPrivateVar, allocator->second});
2198 } else if (mightUseDeviceSharedMem &&
2200 llvmPrivateVar = ompBuilder->createOMPAllocShared(builder, llvmAllocType);
2201 } else {
2202 llvmPrivateVar = builder.CreateAlloca(
2203 llvmAllocType, /*ArraySize=*/nullptr, "omp.private.alloc");
2204 if (allocaAS != defaultAS)
2205 llvmPrivateVar = builder.CreateAddrSpaceCast(
2206 llvmPrivateVar, builder.getPtrTy(defaultAS));
2207 }
2208
2209 privateVarsInfo.llvmVars.push_back(llvmPrivateVar);
2210 }
2211
2212 return afterAllocatorAllocations ? afterAllocatorAllocations : afterAllocas;
2213}
2214
2215/// A compiler-generated barrier is unsafe when only part of the current team
2216/// can reach it. This cannot always be determined statically (e.g. across a
2217/// function call), but the enclosing OpenMP constructs can rule it out.
2219 for (mlir::Operation *parent = op; parent != nullptr;
2220 parent = parent->getParentOp()) {
2221 // An inner parallel construct creates a new team, even when it is nested
2222 // inside a construct that only some threads of the outer team encounter.
2223 if (mlir::isa<omp::ParallelOp>(parent))
2224 return true;
2225
2226 if (mlir::isa<omp::SingleOp, omp::CriticalOp, omp::MaskedOp, omp::MasterOp,
2227 omp::SectionOp>(parent))
2228 return false;
2229 }
2230 return true;
2231}
2232
2233static LogicalResult
2234emitPrivatizationBarrier(mlir::Operation *op, llvm::IRBuilderBase &builder,
2235 LLVM::ModuleTranslation &moduleTranslation) {
2236 if (!opMightBeSafeForBarriers(op))
2237 return success();
2238
2239 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
2240 llvm::OpenMPIRBuilder::InsertPointOrErrorTy res =
2241 ompBuilder->createBarrier(builder, llvm::omp::OMPD_barrier);
2242 return handleError(res, *op);
2243}
2244
2245static LogicalResult
2246completePrivateVars(mlir::Operation *op, llvm::IRBuilderBase &builder,
2247 LLVM::ModuleTranslation &moduleTranslation,
2249 ArrayRef<llvm::Value *> llvmPrivateVars,
2251 bool insertBarrier,
2252 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
2253 // Apply copy region for firstprivate.
2254 bool needsFirstprivate =
2255 llvm::any_of(privateDecls, [](omp::PrivateClauseOp &privOp) {
2256 return privOp.getDataSharingType() ==
2257 omp::DataSharingClauseType::FirstPrivate;
2258 });
2259
2260 if (!needsFirstprivate)
2261 return insertBarrier
2262 ? emitPrivatizationBarrier(op, builder, moduleTranslation)
2263 : success();
2264
2265 llvm::BasicBlock *copyBlock =
2266 splitBB(builder, /*CreateBranch=*/true, "omp.private.copy");
2267 setInsertPointForPossiblyEmptyBlock(builder, copyBlock);
2268
2269 for (auto [decl, moldVar, llvmVar] :
2270 llvm::zip_equal(privateDecls, moldVars, llvmPrivateVars)) {
2271 if (decl.getDataSharingType() != omp::DataSharingClauseType::FirstPrivate)
2272 continue;
2273
2274 // copyRegion implements `lhs = rhs`
2275 Region &copyRegion = decl.getCopyRegion();
2276
2277 llvm::Value *copyMoldVar = materializeRegionArgValue(
2278 builder, moduleTranslation, decl.getCopyMoldArg(), moldVar);
2279 llvm::Value *copyPrivateVar = materializeRegionArgValue(
2280 builder, moduleTranslation, decl.getCopyPrivateArg(), llvmVar);
2281
2282 moduleTranslation.mapValue(decl.getCopyMoldArg(), copyMoldVar);
2283
2284 // map copyRegion lhs arg
2285 moduleTranslation.mapValue(decl.getCopyPrivateArg(), copyPrivateVar);
2286
2287 // in-place convert copy region
2288 if (failed(inlineConvertOmpRegions(copyRegion, "omp.private.copy", builder,
2289 moduleTranslation)))
2290 return decl.emitError("failed to inline `copy` region of `omp.private`");
2291
2293
2294 // ignore unused value yielded from copy region
2295
2296 // clear copy region block argument mapping in case it needs to be
2297 // re-created with different sources for reuse of the same reduction
2298 // decl
2299 moduleTranslation.forgetMapping(copyRegion);
2300 }
2301
2302 return insertBarrier
2303 ? emitPrivatizationBarrier(op, builder, moduleTranslation)
2304 : success();
2305}
2306
2307static LogicalResult
2308completePrivateVars(mlir::Operation *op, llvm::IRBuilderBase &builder,
2309 LLVM::ModuleTranslation &moduleTranslation,
2310 SmallVectorImpl<mlir::Value> &mlirPrivateVars,
2311 ArrayRef<llvm::Value *> llvmPrivateVars,
2313 bool insertBarrier,
2314 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
2315 llvm::SmallVector<llvm::Value *> moldVars(mlirPrivateVars.size());
2316 llvm::transform(mlirPrivateVars, moldVars.begin(), [&](mlir::Value mlirVar) {
2317 // map copyRegion rhs arg
2318 llvm::Value *moldVar = findAssociatedValue(
2319 mlirVar, builder, moduleTranslation, mappedPrivateVars);
2320 assert(moldVar);
2321 return moldVar;
2322 });
2323 return completePrivateVars(op, builder, moduleTranslation, moldVars,
2324 llvmPrivateVars, privateDecls, insertBarrier,
2325 mappedPrivateVars);
2326}
2327
2328template <typename T>
2329static LogicalResult
2330cleanupPrivateVars(T op, llvm::IRBuilderBase &builder,
2331 LLVM::ModuleTranslation &moduleTranslation, Location loc,
2332 PrivateVarsInfo &privateVarsInfo) {
2333 // private variable deallocation
2334 SmallVector<Region *> privateCleanupRegions;
2335 llvm::transform(privateVarsInfo.privatizers,
2336 std::back_inserter(privateCleanupRegions),
2337 [](omp::PrivateClauseOp privatizer) {
2338 return &privatizer.getDeallocRegion();
2339 });
2340
2341 if (failed(inlineOmpRegionCleanup(privateCleanupRegions,
2342 privateVarsInfo.llvmVars, moduleTranslation,
2343 builder, "omp.private.dealloc",
2344 /*shouldLoadCleanupRegionArg=*/false)))
2345 return mlir::emitError(loc, "failed to inline `dealloc` region of an "
2346 "`omp.private` op in");
2348
2349 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
2350 bool mightUseDeviceSharedMem = omp::opInSharedDeviceContext(*op);
2351 for (auto [privDecl, llvmPrivVar, blockArg] :
2352 llvm::zip_equal(privateVarsInfo.privatizers, privateVarsInfo.llvmVars,
2353 privateVarsInfo.blockArgs)) {
2354 if (mightUseDeviceSharedMem && omp::allocaUsesRequireSharedMem(blockArg)) {
2355 ompBuilder->createOMPFreeShared(
2356 builder, llvmPrivVar,
2357 moduleTranslation.convertType(privDecl.getType()));
2358 }
2359 }
2360
2361 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2362 for (const PrivateVarsInfo::AllocatorPrivateInfo &allocation :
2363 llvm::reverse(privateVarsInfo.allocatorPrivates))
2364 ompBuilder->createOMPFree(ompLoc, allocation.allocatedPtr,
2365 allocation.allocator);
2366
2367 return success();
2368}
2369
2370/// Returns true if the construct contains omp.cancel or omp.cancellation_point
2372 // omp.cancel and omp.cancellation_point must be "closely nested" so they will
2373 // be visible and not inside of function calls. This is enforced by the
2374 // verifier.
2375 return op
2376 ->walk([](Operation *child) {
2377 if (mlir::isa<omp::CancelOp, omp::CancellationPointOp>(child))
2378 return WalkResult::interrupt();
2379 return WalkResult::advance();
2380 })
2381 .wasInterrupted();
2382}
2383
2384// Forward declarations for the task-reduction helpers defined alongside the
2385// omp.taskgroup lowering further down in this file. These are shared by the
2386// `reduction(task, ...)` modifier lowering on the parallel/worksharing
2387// constructs and by the omp.taskgroup / omp.taskloop.context task_reduction
2388// lowering. When \p isModifier is set, `__kmpc_taskred_modifier_init` is
2389// emitted (opening a task-reduction scope) instead of `__kmpc_taskred_init`,
2390// with \p isWorksharing selecting the runtime `is_ws` argument.
2391static llvm::Value *emitTaskReductionInitCall(
2393 ArrayRef<llvm::Value *> origPtrs, StringRef helperNamePrefix,
2394 llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder::InsertPointTy allocaIP,
2395 LLVM::ModuleTranslation &moduleTranslation, bool isModifier = false,
2396 bool isWorksharing = false);
2397static void
2398emitTaskReductionModifierFini(bool isWorksharing, llvm::IRBuilderBase &builder,
2399 LLVM::ModuleTranslation &moduleTranslation);
2400
2401static LogicalResult
2402convertOmpSections(Operation &opInst, llvm::IRBuilderBase &builder,
2403 LLVM::ModuleTranslation &moduleTranslation) {
2404 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2405 using StorableBodyGenCallbackTy =
2406 llvm::OpenMPIRBuilder::StorableBodyGenCallbackTy;
2407
2408 auto sectionsOp = cast<omp::SectionsOp>(opInst);
2409
2410 if (failed(checkImplementationStatus(opInst)))
2411 return failure();
2412
2413 llvm::ArrayRef<bool> isByRef = getIsByRef(sectionsOp.getReductionByref());
2414 assert(isByRef.size() == sectionsOp.getNumReductionVars());
2415
2417 collectReductionDecls(sectionsOp, reductionDecls);
2418 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
2419 findAllocInsertPoints(builder, moduleTranslation);
2420
2421 SmallVector<llvm::Value *> privateReductionVariables(
2422 sectionsOp.getNumReductionVars());
2423 DenseMap<Value, llvm::Value *> reductionVariableMap;
2424
2425 MutableArrayRef<BlockArgument> reductionArgs =
2426 cast<omp::BlockArgOpenMPOpInterface>(opInst).getReductionBlockArgs();
2427
2429 sectionsOp, reductionArgs, builder, moduleTranslation, allocaIP,
2430 reductionDecls, privateReductionVariables, reductionVariableMap,
2431 isByRef)))
2432 return failure();
2433
2434 bool isTaskReductionMod =
2435 sectionsOp.getReductionMod() == omp::ReductionModifier::task &&
2436 sectionsOp.getNumReductionVars() > 0;
2437
2439
2440 for (Operation &op : *sectionsOp.getRegion().begin()) {
2441 auto sectionOp = dyn_cast<omp::SectionOp>(op);
2442 if (!sectionOp) // omp.terminator
2443 continue;
2444
2445 Region &region = sectionOp.getRegion();
2446 auto sectionCB = [&sectionsOp, &region, &builder, &moduleTranslation](
2447 InsertPointTy allocaIP, InsertPointTy codeGenIP,
2448 ArrayRef<llvm::BasicBlock *> deallocBlocks) {
2449 builder.restoreIP(codeGenIP);
2450
2451 // map the omp.section reduction block argument to the omp.sections block
2452 // arguments
2453 // TODO: this assumes that the only block arguments are reduction
2454 // variables
2455 assert(region.getNumArguments() ==
2456 sectionsOp.getRegion().getNumArguments());
2457 for (auto [sectionsArg, sectionArg] : llvm::zip_equal(
2458 sectionsOp.getRegion().getArguments(), region.getArguments())) {
2459 llvm::Value *llvmVal = moduleTranslation.lookupValue(sectionsArg);
2460 assert(llvmVal);
2461 moduleTranslation.mapValue(sectionArg, llvmVal);
2462 }
2463
2464 return convertOmpOpRegions(region, "omp.section.region", builder,
2465 moduleTranslation)
2466 .takeError();
2467 };
2468 sectionCBs.push_back(sectionCB);
2469 }
2470
2471 // No sections within omp.sections operation - skip generation. This situation
2472 // is only possible if there is only a terminator operation inside the
2473 // sections operation
2474 if (sectionCBs.empty())
2475 return success();
2476
2477 // For `reduction(task, ...)` open a task-reduction scope for the worksharing
2478 // region. Participating explicit tasks accumulate into the per-thread private
2479 // copies, which the worksharing reduction then combines across threads. This
2480 // is emitted only after the empty-sections early return above, so it stays
2481 // balanced with the matching fini emitted after the sections region.
2482 if (isTaskReductionMod &&
2483 !emitTaskReductionInitCall(reductionDecls, privateReductionVariables,
2484 "__omp_taskred_mod_", builder, allocaIP,
2485 moduleTranslation, /*isModifier=*/true,
2486 /*isWorksharing=*/true))
2487 return sectionsOp.emitError(
2488 "failed to emit task reduction modifier initialization");
2489
2490 assert(isa<omp::SectionOp>(*sectionsOp.getRegion().op_begin()));
2491
2492 // TODO: Perform appropriate actions according to the data-sharing
2493 // attribute (shared, private, firstprivate, ...) of variables.
2494 // Currently defaults to shared.
2495 auto privCB = [&](InsertPointTy, InsertPointTy codeGenIP, llvm::Value &,
2496 llvm::Value &vPtr, llvm::Value *&replacementValue)
2497 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
2498 replacementValue = &vPtr;
2499 return codeGenIP;
2500 };
2501
2502 // TODO: Perform finalization actions for variables. This has to be
2503 // called for variables which have destructors/finalizers.
2504 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
2505
2506 allocaIP = findAllocInsertPoints(builder, moduleTranslation);
2507 bool isCancellable = constructIsCancellable(sectionsOp);
2508 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2509 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2510 moduleTranslation.getOpenMPBuilder()->createSections(
2511 ompLoc, allocaIP, sectionCBs, privCB, finiCB, isCancellable,
2512 sectionsOp.getNowait());
2513
2514 if (failed(handleError(afterIP, opInst)))
2515 return failure();
2516
2517 builder.restoreIP(*afterIP);
2518
2519 // Close the task-reduction scope before combining the worksharing copies.
2520 if (isTaskReductionMod)
2521 emitTaskReductionModifierFini(/*isWorksharing=*/true, builder,
2522 moduleTranslation);
2523
2524 // Process the reductions if required.
2526 sectionsOp, builder, moduleTranslation, allocaIP, reductionDecls,
2527 privateReductionVariables, isByRef, sectionsOp.getNowait());
2528}
2529
2530/// Converts an OpenMP scope construct into LLVM IR.
2531static LogicalResult
2532convertOmpScope(omp::ScopeOp &scopeOp, llvm::IRBuilderBase &builder,
2533 LLVM::ModuleTranslation &moduleTranslation) {
2534 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2535 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
2536
2537 if (failed(checkImplementationStatus(*scopeOp)))
2538 return failure();
2539
2540 llvm::ArrayRef<bool> isByRef = getIsByRef(scopeOp.getReductionByref());
2541 assert(isByRef.size() == scopeOp.getNumReductionVars());
2542
2543 PrivateVarsInfo privateVarsInfo(scopeOp);
2544 if (failed(convertAllocatorVars(*scopeOp, scopeOp.getAllocatorVars(), builder,
2545 moduleTranslation, privateVarsInfo)))
2546 return failure();
2547
2549 collectReductionDecls(scopeOp, reductionDecls);
2550 InsertPointTy privateAllocaIP =
2551 findAllocInsertPoints(builder, moduleTranslation);
2552
2553 SmallVector<llvm::Value *> privateReductionVariables(
2554 scopeOp.getNumReductionVars());
2555 DenseMap<Value, llvm::Value *> reductionVariableMap;
2556
2557 MutableArrayRef<BlockArgument> reductionArgs =
2558 cast<omp::BlockArgOpenMPOpInterface>(*scopeOp).getReductionBlockArgs();
2559
2560 if (scopeOp.getAllocateVars().empty()) {
2562 scopeOp, builder, moduleTranslation, privateVarsInfo, privateAllocaIP);
2563 if (failed(handleError(afterAllocas, *scopeOp)))
2564 return failure();
2565 }
2566
2568 scopeOp, reductionArgs, builder, moduleTranslation, privateAllocaIP,
2569 reductionDecls, privateReductionVariables, reductionVariableMap,
2570 isByRef)))
2571 return failure();
2572
2573 auto bodyCB =
2574 [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
2575 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
2576 if (!scopeOp.getAllocateVars().empty()) {
2577 // Runtime storage must be allocated on each dynamic Scope entry. Ordinary
2578 // private allocas still use the enclosing alloca insertion point.
2580 scopeOp, builder, moduleTranslation, privateVarsInfo, privateAllocaIP,
2581 /*mappedPrivateVars=*/nullptr, codeGenIP);
2582 if (handleError(afterAllocas, *scopeOp).failed())
2583 return llvm::make_error<PreviouslyReportedError>();
2584 builder.SetInsertPoint(afterAllocas.get()->getTerminator());
2585 } else {
2586 builder.restoreIP(codeGenIP);
2587 }
2588
2589 if (handleError(
2590 initPrivateVars(builder, moduleTranslation, privateVarsInfo),
2591 *scopeOp)
2592 .failed())
2593 return llvm::make_error<PreviouslyReportedError>();
2594
2595 if (failed(completePrivateVars(
2596 scopeOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
2597 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
2598 scopeOp.getPrivateNeedsBarrier())))
2599 return llvm::make_error<PreviouslyReportedError>();
2600
2601 return convertOmpOpRegions(scopeOp.getRegion(), "omp.scope.region", builder,
2602 moduleTranslation)
2603 .takeError();
2604 };
2605
2606 auto finiCB = [&](InsertPointTy codeGenIP) -> llvm::Error {
2607 InsertPointTy oldIP = builder.saveIP();
2608 builder.restoreIP(codeGenIP);
2609 if (failed(cleanupPrivateVars(scopeOp, builder, moduleTranslation,
2610 scopeOp.getLoc(), privateVarsInfo)))
2611 return llvm::make_error<PreviouslyReportedError>();
2612 builder.restoreIP(oldIP);
2613 return llvm::Error::success();
2614 };
2615
2616 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2617 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2618 ompBuilder->createScope(ompLoc, bodyCB, finiCB, scopeOp.getNowait());
2619
2620 if (failed(handleError(afterIP, *scopeOp)))
2621 return failure();
2622
2623 builder.restoreIP(*afterIP);
2624
2625 // Process the reductions if required.
2627 scopeOp, builder, moduleTranslation, privateAllocaIP, reductionDecls,
2628 privateReductionVariables, isByRef, scopeOp.getNowait(),
2629 /*isTeamsReduction=*/false);
2630}
2631
2632/// Converts an OpenMP single construct into LLVM IR using OpenMPIRBuilder.
2633static LogicalResult
2634convertOmpSingle(omp::SingleOp &singleOp, llvm::IRBuilderBase &builder,
2635 LLVM::ModuleTranslation &moduleTranslation) {
2636 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2637 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2638
2639 if (failed(checkImplementationStatus(*singleOp)))
2640 return failure();
2641
2642 auto bodyCB = [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
2643 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) {
2644 builder.restoreIP(codegenIP);
2645 return convertOmpOpRegions(singleOp.getRegion(), "omp.single.region",
2646 builder, moduleTranslation)
2647 .takeError();
2648 };
2649 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
2650
2651 // Handle copyprivate
2652 Operation::operand_range cpVars = singleOp.getCopyprivateVars();
2653 std::optional<ArrayAttr> cpFuncs = singleOp.getCopyprivateSyms();
2656 for (size_t i = 0, e = cpVars.size(); i < e; ++i) {
2657 llvmCPVars.push_back(moduleTranslation.lookupValue(cpVars[i]));
2659 singleOp, cast<SymbolRefAttr>((*cpFuncs)[i]));
2660 llvmCPFuncs.push_back(
2661 moduleTranslation.lookupFunction(llvmFuncOp.getName()));
2662 }
2663
2664 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2665 moduleTranslation.getOpenMPBuilder()->createSingle(
2666 ompLoc, bodyCB, finiCB, singleOp.getNowait(), llvmCPVars,
2667 llvmCPFuncs);
2668
2669 if (failed(handleError(afterIP, *singleOp)))
2670 return failure();
2671
2672 builder.restoreIP(*afterIP);
2673 return success();
2674}
2675
2676static omp::DistributeOp
2678 // Early return if we found more than one distribute op or if we can't find
2679 // any distribute op in the teams region.
2680 omp::DistributeOp distOp;
2681 WalkResult walk = teamsOp.getRegion().walk([&](omp::DistributeOp op) {
2682 if (distOp)
2683 return WalkResult::interrupt();
2684 distOp = op;
2685 return WalkResult::skip();
2686 });
2687 if (walk.wasInterrupted() || !distOp)
2688 return {};
2689
2690 auto iface =
2691 llvm::cast<mlir::omp::BlockArgOpenMPOpInterface>(teamsOp.getOperation());
2692 // Check that all uses of the reduction block arg has the same distribute op
2693 // parent.
2695 for (auto ra : iface.getReductionBlockArgs())
2696 for (auto &use : ra.getUses()) {
2697 auto *useOp = use.getOwner();
2698 // Ignore debug uses.
2699 if (mlir::isa<LLVM::DbgDeclareOp, LLVM::DbgValueOp>(useOp)) {
2700 debugUses.push_back(useOp);
2701 continue;
2702 }
2703 if (!distOp->isProperAncestor(useOp))
2704 return {};
2705 }
2706
2707 // If we are going to use distribute reduction then remove any debug uses of
2708 // the reduction parameters in teamsOp. Otherwise they will be left without
2709 // any mapped value in moduleTranslation and will eventually error out.
2710 for (auto *use : debugUses)
2711 use->erase();
2712 return distOp;
2713}
2714
2715// Convert an OpenMP Teams construct to LLVM IR using OpenMPIRBuilder
2716static LogicalResult
2717convertOmpTeams(omp::TeamsOp op, llvm::IRBuilderBase &builder,
2718 LLVM::ModuleTranslation &moduleTranslation) {
2719 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2720 if (failed(checkImplementationStatus(*op)))
2721 return failure();
2722
2723 DenseMap<Value, llvm::Value *> reductionVariableMap;
2724 unsigned numReductionVars = op.getNumReductionVars();
2726 SmallVector<llvm::Value *> privateReductionVariables(numReductionVars);
2727 llvm::ArrayRef<bool> isByRef;
2728 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
2729 findAllocInsertPoints(builder, moduleTranslation);
2730
2731 // Only do teams reduction if there is no distribute op that captures the
2732 // reduction instead.
2733 bool doTeamsReduction = !getDistributeCapturingTeamsReduction(op);
2734 if (doTeamsReduction) {
2735 isByRef = getIsByRef(op.getReductionByref());
2736
2737 assert(isByRef.size() == op.getNumReductionVars());
2738
2739 MutableArrayRef<BlockArgument> reductionArgs =
2740 llvm::cast<omp::BlockArgOpenMPOpInterface>(*op).getReductionBlockArgs();
2741
2742 collectReductionDecls(op, reductionDecls);
2743
2745 op, reductionArgs, builder, moduleTranslation, allocaIP,
2746 reductionDecls, privateReductionVariables, reductionVariableMap,
2747 isByRef)))
2748 return failure();
2749 }
2750
2751 auto bodyCB = [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
2752 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) {
2754 moduleTranslation, allocaIP.getNodeParent(), deallocBlocks);
2755 builder.restoreIP(codegenIP);
2756 return convertOmpOpRegions(op.getRegion(), "omp.teams.region", builder,
2757 moduleTranslation)
2758 .takeError();
2759 };
2760
2761 llvm::Value *numTeamsLower = nullptr;
2762 if (Value numTeamsLowerVar = op.getNumTeamsLower())
2763 numTeamsLower = moduleTranslation.lookupValue(numTeamsLowerVar);
2764
2765 llvm::Value *numTeamsUpper = nullptr;
2766 if (!op.getNumTeamsUpperVars().empty())
2767 numTeamsUpper = moduleTranslation.lookupValue(op.getNumTeams(0));
2768
2769 llvm::Value *threadLimit = nullptr;
2770 if (!op.getThreadLimitVars().empty())
2771 threadLimit = moduleTranslation.lookupValue(op.getThreadLimit(0));
2772
2773 llvm::Value *ifExpr = nullptr;
2774 if (Value ifVar = op.getIfExpr())
2775 ifExpr = moduleTranslation.lookupValue(ifVar);
2776
2777 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2778 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2779 moduleTranslation.getOpenMPBuilder()->createTeams(
2780 ompLoc, bodyCB, numTeamsLower, numTeamsUpper, threadLimit, ifExpr);
2781
2782 if (failed(handleError(afterIP, *op)))
2783 return failure();
2784
2785 builder.restoreIP(*afterIP);
2786 if (doTeamsReduction) {
2787 // Process the reductions if required.
2789 op, builder, moduleTranslation, allocaIP, reductionDecls,
2790 privateReductionVariables, isByRef,
2791 /*isNoWait*/ false, /*isTeamsReduction*/ true);
2792 }
2793 return success();
2794}
2795
2796static llvm::omp::RTLDependenceKindTy
2797convertDependKind(mlir::omp::ClauseTaskDepend kind) {
2798 switch (kind) {
2799 case mlir::omp::ClauseTaskDepend::taskdependin:
2800 return llvm::omp::RTLDependenceKindTy::DepIn;
2801 // The OpenMP runtime requires that the codegen for 'depend' clause for
2802 // 'out' dependency kind must be the same as codegen for 'depend' clause
2803 // with 'inout' dependency.
2804 case mlir::omp::ClauseTaskDepend::taskdependout:
2805 case mlir::omp::ClauseTaskDepend::taskdependinout:
2806 return llvm::omp::RTLDependenceKindTy::DepInOut;
2807 case mlir::omp::ClauseTaskDepend::taskdependmutexinoutset:
2808 return llvm::omp::RTLDependenceKindTy::DepMutexInOutSet;
2809 case mlir::omp::ClauseTaskDepend::taskdependinoutset:
2810 return llvm::omp::RTLDependenceKindTy::DepInOutSet;
2811 }
2812 llvm_unreachable("unhandled depend kind");
2813}
2814
2816 std::optional<ArrayAttr> dependKinds, OperandRange dependVars,
2817 LLVM::ModuleTranslation &moduleTranslation,
2819 if (dependVars.empty())
2820 return;
2821 for (auto dep : llvm::zip(dependVars, dependKinds->getValue())) {
2822 auto kind =
2823 cast<mlir::omp::ClauseTaskDependAttr>(std::get<1>(dep)).getValue();
2824 llvm::omp::RTLDependenceKindTy type = convertDependKind(kind);
2825 llvm::Value *depVal = moduleTranslation.lookupValue(std::get<0>(dep));
2826 llvm::OpenMPIRBuilder::DependData dd(type, depVal->getType(), depVal);
2827 dds.emplace_back(dd);
2828 }
2829}
2830
2831/// Shared implementation of a callback which adds a termiator for the new block
2832/// created for the branch taken when an openmp construct is cancelled. The
2833/// terminator is saved in \p cancelTerminators. This callback is invoked only
2834/// if there is cancellation inside of the taskgroup body.
2835/// The terminator will need to be fixed to branch to the correct block to
2836/// cleanup the construct.
2838 SmallVectorImpl<llvm::UncondBrInst *> &cancelTerminators,
2839 llvm::IRBuilderBase &llvmBuilder, llvm::OpenMPIRBuilder &ompBuilder,
2840 mlir::Operation *op, llvm::omp::Directive cancelDirective) {
2841 auto finiCB = [&](llvm::OpenMPIRBuilder::InsertPointTy ip) -> llvm::Error {
2842 llvm::IRBuilderBase::InsertPointGuard guard(llvmBuilder);
2843
2844 // ip is currently in the block branched to if cancellation occurred.
2845 // We need to create a branch to terminate that block.
2846 llvmBuilder.restoreIP(ip);
2847
2848 // We must still clean up the construct after cancelling it, so we need to
2849 // branch to the block that finalizes the taskgroup.
2850 // That block has not been created yet so use this block as a dummy for now
2851 // and fix this after creating the operation.
2852 cancelTerminators.push_back(llvmBuilder.CreateBr(ip.getNodeParent()));
2853 return llvm::Error::success();
2854 };
2855 // We have to add the cleanup to the OpenMPIRBuilder before the body gets
2856 // created in case the body contains omp.cancel (which will then expect to be
2857 // able to find this cleanup callback).
2858 ompBuilder.pushFinalizationCB(
2859 {finiCB, cancelDirective, constructIsCancellable(op)});
2860}
2861
2862/// If we cancelled the construct, we should branch to the finalization block of
2863/// that construct. OMPIRBuilder structures the CFG such that the cleanup block
2864/// is immediately before the continuation block. Now this finalization has
2865/// been created we can fix the branch.
2866static void
2868 llvm::OpenMPIRBuilder &ompBuilder,
2869 llvm::BasicBlock *afterBB) {
2870 ompBuilder.popFinalizationCB();
2871 llvm::BasicBlock *constructFini = afterBB->getSinglePredecessor();
2872 for (llvm::UncondBrInst *cancelBranch : cancelTerminators)
2873 cancelBranch->setSuccessor(constructFini);
2874}
2875
2876namespace {
2877/// TaskContextStructManager takes care of creating and freeing a structure
2878/// containing information needed by the task body to execute.
2879class TaskContextStructManager {
2880public:
2881 TaskContextStructManager(llvm::IRBuilderBase &builder,
2882 LLVM::ModuleTranslation &moduleTranslation,
2883 MutableArrayRef<omp::PrivateClauseOp> privateDecls)
2884 : builder{builder}, moduleTranslation{moduleTranslation},
2885 privateDecls{privateDecls} {}
2886
2887 /// Creates a heap allocated struct containing space for each private
2888 /// variable. Invariant: privateVarTypes, privateDecls, and the elements of
2889 /// the structure should all have the same order (although privateDecls which
2890 /// do not read from the mold argument are skipped).
2891 void generateTaskContextStruct();
2892
2893 /// Create GEPs to access each member of the structure representing a private
2894 /// variable, adding them to llvmPrivateVars. Null values are added where
2895 /// private decls were skipped so that the ordering continues to match the
2896 /// private decls.
2897 void createGEPsToPrivateVars();
2898
2899 /// Given the address of the structure, return a GEP for each private variable
2900 /// in the structure. Null values are added where private decls were skipped
2901 /// so that the ordering continues to match the private decls.
2902 /// Must be called after generateTaskContextStruct().
2903 SmallVector<llvm::Value *>
2904 createGEPsToPrivateVars(llvm::Value *altStructPtr) const;
2905
2906 /// De-allocate the task context structure.
2907 void freeStructPtr();
2908
2909 MutableArrayRef<llvm::Value *> getLLVMPrivateVarGEPs() {
2910 return llvmPrivateVarGEPs;
2911 }
2912
2913 llvm::Value *getStructPtr() { return structPtr; }
2914
2915private:
2916 llvm::IRBuilderBase &builder;
2917 LLVM::ModuleTranslation &moduleTranslation;
2918 MutableArrayRef<omp::PrivateClauseOp> privateDecls;
2919
2920 /// The type of each member of the structure, in order.
2921 SmallVector<llvm::Type *> privateVarTypes;
2922
2923 /// LLVM values for each private variable, or null if that private variable is
2924 /// not included in the task context structure
2925 SmallVector<llvm::Value *> llvmPrivateVarGEPs;
2926
2927 /// A pointer to the structure containing context for this task.
2928 llvm::Value *structPtr = nullptr;
2929 /// The type of the structure
2930 llvm::Type *structTy = nullptr;
2931};
2932
2933/// IteratorInfo extracts and prepares loop bounds information from an
2934/// mlir::omp::IteratorOp for lowering to LLVM IR.
2935///
2936/// It computes the per-dimension trip counts and the total linearized trip
2937/// count as i64. Bounds and steps keep their declared type so that the
2938/// physical induction variables can be reconstructed in that type inside the
2939/// loop body.
2940class IteratorInfo {
2941private:
2942 llvm::SmallVector<llvm::Value *> lowerBounds;
2943 llvm::SmallVector<llvm::Value *> upperBounds;
2944 llvm::SmallVector<llvm::Value *> steps;
2945 llvm::SmallVector<llvm::Value *> trips;
2946 unsigned dims;
2947 llvm::Value *totalTrips;
2948
2949public:
2950 IteratorInfo(mlir::omp::IteratorOp itersOp,
2951 mlir::LLVM::ModuleTranslation &moduleTranslation,
2952 llvm::IRBuilderBase &builder) {
2953 dims = itersOp.getLoopLowerBounds().size();
2954 lowerBounds.resize(dims);
2955 upperBounds.resize(dims);
2956 steps.resize(dims);
2957 trips.resize(dims);
2958
2959 for (unsigned d = 0; d < dims; ++d) {
2960 llvm::Value *lb =
2961 moduleTranslation.lookupValue(itersOp.getLoopLowerBounds()[d]);
2962 llvm::Value *ub =
2963 moduleTranslation.lookupValue(itersOp.getLoopUpperBounds()[d]);
2964 llvm::Value *st =
2965 moduleTranslation.lookupValue(itersOp.getLoopSteps()[d]);
2966 assert(lb && ub && st &&
2967 "Expect lowerBounds, upperBounds, and steps in IteratorOp");
2968 assert((!llvm::isa<llvm::ConstantInt>(st) ||
2969 !llvm::cast<llvm::ConstantInt>(st)->isZero()) &&
2970 "Expect non-zero step in IteratorOp");
2971
2972 lowerBounds[d] = lb;
2973 upperBounds[d] = ub;
2974 steps[d] = st;
2975
2976 // Use a direction-aware count so an empty range contributes no entries.
2977 // Widen by one bit so the span between the bounds cannot overflow.
2978 llvm::Type *countTy =
2979 builder.getIntNTy(lb->getType()->getIntegerBitWidth() + 1);
2980 llvm::Value *start = builder.CreateSExt(lb, countTy);
2981 llvm::Value *stop = builder.CreateSExt(ub, countTy);
2982 llvm::Value *step = builder.CreateSExt(st, countTy);
2983 llvm::Value *count =
2984 moduleTranslation.getOpenMPBuilder()->calculateCanonicalLoopTripCount(
2985 builder, start, stop, step, /*IsSigned=*/true,
2986 /*InclusiveStop=*/true);
2987 trips[d] = builder.CreateZExtOrTrunc(count, builder.getInt64Ty());
2988 }
2989
2990 totalTrips = llvm::ConstantInt::get(builder.getInt64Ty(), 1);
2991 for (unsigned d = 0; d < dims; ++d)
2992 totalTrips = builder.CreateMul(totalTrips, trips[d]);
2993 }
2994
2995 unsigned getDims() const { return dims; }
2996 llvm::ArrayRef<llvm::Value *> getLowerBounds() const { return lowerBounds; }
2997 llvm::ArrayRef<llvm::Value *> getUpperBounds() const { return upperBounds; }
2998 llvm::ArrayRef<llvm::Value *> getSteps() const { return steps; }
2999 llvm::ArrayRef<llvm::Value *> getTrips() const { return trips; }
3000 llvm::Value *getTotalTrips() const { return totalTrips; }
3001};
3002
3003} // namespace
3004
3005void TaskContextStructManager::generateTaskContextStruct() {
3006 if (privateDecls.empty())
3007 return;
3008 privateVarTypes.reserve(privateDecls.size());
3009
3010 for (omp::PrivateClauseOp &privOp : privateDecls) {
3011 // Skip private variables which can safely be allocated and initialised
3012 // inside of the task
3013 if (!privOp.readsFromMold())
3014 continue;
3015 Type mlirType = privOp.getType();
3016 privateVarTypes.push_back(moduleTranslation.convertType(mlirType));
3017 }
3018
3019 if (privateVarTypes.empty())
3020 return;
3021
3022 structTy = llvm::StructType::get(moduleTranslation.getLLVMContext(),
3023 privateVarTypes);
3024
3025 llvm::DataLayout dataLayout = builder.getDataLayout();
3026 llvm::Type *intPtrTy = builder.getIntPtrTy(dataLayout);
3027 llvm::Value *allocSize =
3028 builder.CreateTypeSize(intPtrTy, dataLayout.getTypeAllocSize(structTy));
3029
3030 // Heap allocate the structure
3031 structPtr = builder.CreateMalloc(intPtrTy, allocSize,
3032 /*ArraySize=*/nullptr, /*MallocF=*/nullptr,
3033 "omp.task.context_ptr");
3034}
3035
3036SmallVector<llvm::Value *> TaskContextStructManager::createGEPsToPrivateVars(
3037 llvm::Value *altStructPtr) const {
3038 SmallVector<llvm::Value *> ret;
3039
3040 // Create GEPs for each struct member
3041 ret.reserve(privateDecls.size());
3042 llvm::Value *zero = builder.getInt32(0);
3043 unsigned i = 0;
3044 for (auto privDecl : privateDecls) {
3045 if (!privDecl.readsFromMold()) {
3046 // Handle this inside of the task so we don't pass unnessecary vars in
3047 ret.push_back(nullptr);
3048 continue;
3049 }
3050 llvm::Value *iVal = builder.getInt32(i);
3051 llvm::Value *gep = builder.CreateGEP(structTy, altStructPtr, {zero, iVal});
3052 ret.push_back(gep);
3053 i += 1;
3054 }
3055 return ret;
3056}
3057
3058void TaskContextStructManager::createGEPsToPrivateVars() {
3059 if (!structPtr)
3060 assert(privateVarTypes.empty());
3061 // Still need to run createGEPsToPrivateVars to populate llvmPrivateVarGEPs
3062 // with null values for skipped private decls
3063
3064 llvmPrivateVarGEPs = createGEPsToPrivateVars(structPtr);
3065}
3066
3067void TaskContextStructManager::freeStructPtr() {
3068 if (!structPtr)
3069 return;
3070
3071 llvm::IRBuilderBase::InsertPointGuard guard{builder};
3072 // Ensure we don't put the call to free() after the terminator
3073 builder.SetInsertPoint(builder.GetInsertBlock()->getTerminator());
3074 builder.CreateFree(structPtr);
3075}
3076
3077static void storeAffinityEntry(llvm::IRBuilderBase &builder,
3078 llvm::OpenMPIRBuilder &ompBuilder,
3079 llvm::Value *affinityList, llvm::Value *index,
3080 llvm::Value *addr, llvm::Value *len) {
3081 llvm::StructType *kmpTaskAffinityInfoTy =
3082 ompBuilder.getKmpTaskAffinityInfoTy();
3083 llvm::Value *entry = builder.CreateInBoundsGEP(
3084 kmpTaskAffinityInfoTy, affinityList, index, "omp.affinity.entry");
3085
3086 addr = builder.CreatePtrToInt(addr, kmpTaskAffinityInfoTy->getElementType(0));
3087 len = builder.CreateIntCast(len, kmpTaskAffinityInfoTy->getElementType(1),
3088 /*isSigned=*/false);
3089 llvm::Value *flags = builder.getInt32(0);
3090
3091 builder.CreateStore(addr,
3092 builder.CreateStructGEP(kmpTaskAffinityInfoTy, entry, 0));
3093 builder.CreateStore(len,
3094 builder.CreateStructGEP(kmpTaskAffinityInfoTy, entry, 1));
3095 builder.CreateStore(flags,
3096 builder.CreateStructGEP(kmpTaskAffinityInfoTy, entry, 2));
3097}
3098
3100 llvm::IRBuilderBase &builder,
3101 LLVM::ModuleTranslation &moduleTranslation,
3102 llvm::Value *affinityList) {
3103 for (auto [i, affinityVar] : llvm::enumerate(affinityVars)) {
3104 auto entryOp = affinityVar.getDefiningOp<mlir::omp::AffinityEntryOp>();
3105 assert(entryOp && "affinity item must be omp.affinity_entry");
3106
3107 llvm::Value *addr = moduleTranslation.lookupValue(entryOp.getAddr());
3108 llvm::Value *len = moduleTranslation.lookupValue(entryOp.getLen());
3109 assert(addr && len && "expect affinity addr and len to be non-null");
3110 storeAffinityEntry(builder, *moduleTranslation.getOpenMPBuilder(),
3111 affinityList, builder.getInt64(i), addr, len);
3112 }
3113}
3114
3115static mlir::LogicalResult convertIteratorRegion(
3116 llvm::Value *linearIV, IteratorInfo &iterInfo, mlir::Region &iteratorRegion,
3117 llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation) {
3118 mlir::Block &entryBlock = iteratorRegion.front();
3119 llvm::Value *tmp = linearIV;
3120 for (int d = (int)iterInfo.getDims() - 1; d >= 0; --d) {
3121 llvm::Value *trip = iterInfo.getTrips()[d];
3122 // idx_d = tmp % trip_d
3123 llvm::Value *idx = builder.CreateURem(tmp, trip);
3124 // tmp = tmp / trip_d
3125 tmp = builder.CreateUDiv(tmp, trip);
3126
3127 // physIV_d = lb_d + idx_d * step_d
3128 idx =
3129 builder.CreateZExtOrTrunc(idx, iterInfo.getLowerBounds()[d]->getType());
3130 llvm::Value *physIV = builder.CreateAdd(
3131 iterInfo.getLowerBounds()[d],
3132 builder.CreateMul(idx, iterInfo.getSteps()[d]), "omp.it.phys_iv");
3133
3134 moduleTranslation.mapValue(entryBlock.getArgument(d), physIV);
3135 }
3136
3137 // Translate the iterator region into the loop body.
3138 if (iteratorRegion.hasOneBlock()) {
3139 moduleTranslation.mapBlock(&entryBlock, builder.GetInsertBlock());
3140 return moduleTranslation.convertBlock(entryBlock, /*ignoreArguments=*/true,
3141 builder);
3142 }
3143
3144 // Unlike convertOmpOpRegions, do not forward the yielded value through a PHI:
3145 // it may have no LLVM type (e.g. `!omp.affinity_entry_ty`). The unique
3146 // `omp.yield` block is the continuation's only predecessor, so callers can
3147 // look up the yielded value directly.
3148 llvm::BasicBlock *continuation =
3149 splitBB(builder, true, "omp.iterator.region.cont");
3150 llvm::Instruction *sourceBranch = builder.GetInsertBlock()->getTerminator();
3151 for (mlir::Block &block : iteratorRegion)
3152 moduleTranslation.mapBlock(
3153 &block,
3154 llvm::BasicBlock::Create(builder.getContext(), "omp.iterator.region",
3155 continuation->getParent(), continuation));
3156 sourceBranch->setSuccessor(0, moduleTranslation.lookupBlock(&entryBlock));
3157
3158 for (mlir::Block *block : getBlocksSortedByDominance(iteratorRegion)) {
3159 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3160 if (failed(moduleTranslation.convertBlock(*block, block->isEntryBlock(),
3161 builder)))
3162 return mlir::failure();
3163 if (isa<omp::YieldOp>(block->getTerminator()))
3164 builder.CreateBr(continuation);
3165 }
3166 LLVM::detail::connectPHINodes(iteratorRegion, moduleTranslation);
3167
3168 builder.SetInsertPoint(continuation);
3169 return mlir::success();
3170}
3171
3173 llvm::function_ref<void(llvm::Value *linearIV, mlir::omp::YieldOp yield)>;
3174
3175static mlir::LogicalResult
3176fillIteratorLoop(mlir::omp::IteratorOp itersOp, llvm::IRBuilderBase &builder,
3177 mlir::LLVM::ModuleTranslation &moduleTranslation,
3178 IteratorInfo &iterInfo, llvm::StringRef loopName,
3179 IteratorStoreEntryTy genStoreEntry) {
3180 mlir::Region &itersRegion = itersOp.getRegion();
3181
3182 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
3183
3184 auto bodyGen = [&](llvm::OpenMPIRBuilder::InsertPointTy bodyIP,
3185 llvm::Value *linearIV) -> llvm::Error {
3186 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3187 builder.restoreIP(bodyIP);
3188
3189 if (failed(convertIteratorRegion(linearIV, iterInfo, itersRegion, builder,
3190 moduleTranslation))) {
3191 return llvm::make_error<llvm::StringError>(
3192 "failed to convert iterator region", llvm::inconvertibleErrorCode());
3193 }
3194
3195 mlir::omp::YieldOp yield = itersOp.getYieldOp();
3196 assert(yield && yield.getResults().size() == 1 &&
3197 "expect omp.yield in iterator region to have one result");
3198
3199 genStoreEntry(linearIV, yield);
3200
3201 // Iterator-region block/value mappings are temporary for this conversion,
3202 // clear them to avoid stale entries in ModuleTranslation.
3203 moduleTranslation.forgetMapping(itersRegion);
3204
3205 return llvm::Error::success();
3206 };
3207
3208 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
3209 moduleTranslation.getOpenMPBuilder()->createIteratorLoop(
3210 loc, iterInfo.getTotalTrips(), bodyGen, loopName);
3211 if (failed(handleError(afterIP, *itersOp)))
3212 return failure();
3213
3214 builder.restoreIP(*afterIP);
3215
3216 return mlir::success();
3217}
3218
3219static mlir::LogicalResult
3220buildAffinityData(mlir::omp::TaskOp &taskOp, llvm::IRBuilderBase &builder,
3221 mlir::LLVM::ModuleTranslation &moduleTranslation,
3222 llvm::OpenMPIRBuilder::AffinityData &ad) {
3223
3224 if (taskOp.getAffinityVars().empty() && taskOp.getIterated().empty()) {
3225 ad.Count = nullptr;
3226 ad.Info = nullptr;
3227 return mlir::success();
3228 }
3229
3231 llvm::StructType *kmpTaskAffinityInfoTy =
3232 moduleTranslation.getOpenMPBuilder()->getKmpTaskAffinityInfoTy();
3233
3234 auto allocateAffinityList = [&](llvm::Value *count) -> llvm::Value * {
3235 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3236 if (llvm::isa<llvm::Constant>(count) || llvm::isa<llvm::Argument>(count))
3237 builder.restoreIP(findAllocInsertPoints(builder, moduleTranslation));
3238 return builder.CreateAlloca(kmpTaskAffinityInfoTy, count,
3239 "omp.affinity_list");
3240 };
3241
3242 auto createAffinity =
3243 [&](llvm::Value *count,
3244 llvm::Value *info) -> llvm::OpenMPIRBuilder::AffinityData {
3245 llvm::OpenMPIRBuilder::AffinityData ad{};
3246 ad.Count = builder.CreateTrunc(count, builder.getInt32Ty());
3247 ad.Info =
3248 builder.CreatePointerBitCastOrAddrSpaceCast(info, builder.getPtrTy(0));
3249 return ad;
3250 };
3251
3252 if (!taskOp.getAffinityVars().empty()) {
3253 llvm::Value *count = llvm::ConstantInt::get(
3254 builder.getInt64Ty(), taskOp.getAffinityVars().size());
3255 llvm::Value *list = allocateAffinityList(count);
3256 fillAffinityLocators(taskOp.getAffinityVars(), builder, moduleTranslation,
3257 list);
3258 ads.emplace_back(createAffinity(count, list));
3259 }
3260
3261 if (!taskOp.getIterated().empty()) {
3262 for (auto [i, iter] : llvm::enumerate(taskOp.getIterated())) {
3263 auto itersOp = iter.getDefiningOp<omp::IteratorOp>();
3264 assert(itersOp && "iterated value must be defined by omp.iterator");
3265 IteratorInfo iterInfo(itersOp, moduleTranslation, builder);
3266 llvm::Value *affList = allocateAffinityList(iterInfo.getTotalTrips());
3267 if (failed(fillIteratorLoop(
3268 itersOp, builder, moduleTranslation, iterInfo, "iterator",
3269 [&](llvm::Value *linearIV, mlir::omp::YieldOp yield) {
3270 auto entryOp = yield.getResults()[0]
3271 .getDefiningOp<mlir::omp::AffinityEntryOp>();
3272 assert(entryOp && "expect yield produce an affinity entry");
3273 llvm::Value *addr =
3274 moduleTranslation.lookupValue(entryOp.getAddr());
3275 llvm::Value *len =
3276 moduleTranslation.lookupValue(entryOp.getLen());
3277 storeAffinityEntry(builder,
3278 *moduleTranslation.getOpenMPBuilder(),
3279 affList, linearIV, addr, len);
3280 })))
3281 return llvm::failure();
3282 ads.emplace_back(createAffinity(iterInfo.getTotalTrips(), affList));
3283 }
3284 }
3285
3286 llvm::Value *totalAffinityCount = builder.getInt32(0);
3287 for (const auto &affinity : ads)
3288 totalAffinityCount = builder.CreateAdd(
3289 totalAffinityCount,
3290 builder.CreateIntCast(affinity.Count, builder.getInt32Ty(),
3291 /*isSigned=*/false));
3292
3293 llvm::Value *affinityInfo = ads.front().Info;
3294 if (ads.size() > 1) {
3295 llvm::StructType *kmpTaskAffinityInfoTy =
3296 moduleTranslation.getOpenMPBuilder()->getKmpTaskAffinityInfoTy();
3297 llvm::Value *affinityInfoElemSize = builder.getInt64(
3298 moduleTranslation.getLLVMModule()->getDataLayout().getTypeAllocSize(
3299 kmpTaskAffinityInfoTy));
3300
3301 llvm::Value *packedAffinityInfo = allocateAffinityList(totalAffinityCount);
3302 llvm::Value *packedAffinityInfoOffset = builder.getInt32(0);
3303 for (const auto &affinity : ads) {
3304 llvm::Value *affinityCount = builder.CreateIntCast(
3305 affinity.Count, builder.getInt32Ty(), /*isSigned=*/false);
3306 llvm::Value *affinityCountInt64 = builder.CreateIntCast(
3307 affinityCount, builder.getInt64Ty(), /*isSigned=*/false);
3308 llvm::Value *affinityInfoSize =
3309 builder.CreateMul(affinityCountInt64, affinityInfoElemSize);
3310
3311 llvm::Value *packedAffinityInfoIndex = builder.CreateIntCast(
3312 packedAffinityInfoOffset, kmpTaskAffinityInfoTy->getElementType(0),
3313 /*isSigned=*/false);
3314 packedAffinityInfoIndex = builder.CreateInBoundsGEP(
3315 kmpTaskAffinityInfoTy, packedAffinityInfo, packedAffinityInfoIndex);
3316
3317 builder.CreateMemCpy(
3318 packedAffinityInfoIndex, llvm::Align(1),
3319 builder.CreatePointerBitCastOrAddrSpaceCast(
3320 affinity.Info, builder.getPtrTy(packedAffinityInfoIndex->getType()
3321 ->getPointerAddressSpace())),
3322 llvm::Align(1), affinityInfoSize);
3323
3324 packedAffinityInfoOffset =
3325 builder.CreateAdd(packedAffinityInfoOffset, affinityCount);
3326 }
3327
3328 affinityInfo = packedAffinityInfo;
3329 }
3330
3331 ad.Count = totalAffinityCount;
3332 ad.Info = affinityInfo;
3333
3334 return mlir::success();
3335}
3336
3337// Allocates a single kmp_dep_info array sized to hold both locator
3338// (non-iterated) and iterated entries, fills the locator entries first, then
3339// runs an iterator loop for each iterator modifier object.
3340static mlir::LogicalResult
3341buildDependData(OperandRange dependVars, std::optional<ArrayAttr> dependKinds,
3342 OperandRange dependIterated,
3343 std::optional<ArrayAttr> dependIteratedKinds,
3344 llvm::IRBuilderBase &builder,
3345 mlir::LLVM::ModuleTranslation &moduleTranslation,
3346 llvm::OpenMPIRBuilder::DependenciesInfo &taskDeps) {
3347 if (dependIterated.empty()) {
3348 buildDependDataLocator(dependKinds, dependVars, moduleTranslation,
3349 taskDeps.Deps);
3350 return mlir::success();
3351 }
3352
3353 llvm::OpenMPIRBuilder &ompBuilder = *moduleTranslation.getOpenMPBuilder();
3354 llvm::Type *dependInfoTy = ompBuilder.DependInfo;
3355 unsigned numLocator = dependVars.size();
3356
3357 // Compute total count: locator deps + sum of iterator trip counts.
3358 llvm::Value *totalCount =
3359 llvm::ConstantInt::get(builder.getInt64Ty(), numLocator);
3360
3362 for (auto iter : dependIterated) {
3363 auto itersOp = iter.getDefiningOp<mlir::omp::IteratorOp>();
3364 assert(itersOp && "depend_iterated value must be defined by omp.iterator");
3365 iterInfos.emplace_back(itersOp, moduleTranslation, builder);
3366 totalCount =
3367 builder.CreateAdd(totalCount, iterInfos.back().getTotalTrips());
3368 }
3369
3370 // Heap-allocate the kmp_depend_info array so we don't risk
3371 // dynamic-sized alloca outside the entry block (e.g. inside loops).
3372 llvm::DataLayout dataLayout = builder.getDataLayout();
3373 llvm::Value *allocSize = builder.CreateTypeSize(
3374 ompBuilder.SizeTy, dataLayout.getTypeAllocSize(dependInfoTy));
3375 llvm::Value *depArray =
3376 builder.CreateMalloc(ompBuilder.SizeTy, allocSize, totalCount,
3377 /*MallocF=*/nullptr, ".dep.arr.addr");
3378
3379 // Fill non-iterated entries at indices [0, numLocator).
3380 if (numLocator > 0) {
3382 buildDependDataLocator(dependKinds, dependVars, moduleTranslation, dds);
3383 for (auto [i, dd] : llvm::enumerate(dds)) {
3384 llvm::Value *idx = llvm::ConstantInt::get(builder.getInt64Ty(), i);
3385 llvm::Value *entry =
3386 builder.CreateInBoundsGEP(dependInfoTy, depArray, idx);
3387 ompBuilder.emitTaskDependency(builder, entry, dd);
3388 }
3389 }
3390
3391 // Fill iterated entries starting at index numLocator.
3392 llvm::Value *offset =
3393 llvm::ConstantInt::get(builder.getInt64Ty(), numLocator);
3394 for (auto [i, iterInfo] : llvm::enumerate(iterInfos)) {
3395 auto kindAttr = cast<mlir::omp::ClauseTaskDependAttr>(
3396 dependIteratedKinds->getValue()[i]);
3397 llvm::omp::RTLDependenceKindTy rtlKind =
3398 convertDependKind(kindAttr.getValue());
3399
3400 auto itersOp = dependIterated[i].getDefiningOp<mlir::omp::IteratorOp>();
3401 if (failed(fillIteratorLoop(
3402 itersOp, builder, moduleTranslation, iterInfo, "dep_iterator",
3403 [&](llvm::Value *linearIV, mlir::omp::YieldOp yield) {
3404 llvm::Value *addr =
3405 moduleTranslation.lookupValue(yield.getResults()[0]);
3406 llvm::Value *idx = builder.CreateAdd(offset, linearIV);
3407 llvm::Value *entry =
3408 builder.CreateInBoundsGEP(dependInfoTy, depArray, idx);
3409 ompBuilder.emitTaskDependency(
3410 builder, entry,
3411 llvm::OpenMPIRBuilder::DependData{rtlKind, addr->getType(),
3412 addr});
3413 })))
3414 return mlir::failure();
3415
3416 // Advance offset by the trip count of this iterator.
3417 offset = builder.CreateAdd(offset, iterInfo.getTotalTrips());
3418 }
3419
3420 taskDeps.DepArray = depArray;
3421 taskDeps.NumDeps = builder.CreateTrunc(totalCount, builder.getInt32Ty());
3422 return mlir::success();
3423}
3424
3425/// Converts an OpenMP task construct into LLVM IR using OpenMPIRBuilder.
3426static LogicalResult
3427convertOmpTaskOp(omp::TaskOp taskOp, llvm::IRBuilderBase &builder,
3428 LLVM::ModuleTranslation &moduleTranslation) {
3429 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
3430 if (failed(checkImplementationStatus(*taskOp)))
3431 return failure();
3432
3433 PrivateVarsInfo privateVarsInfo(taskOp);
3434 TaskContextStructManager taskStructMgr{builder, moduleTranslation,
3435 privateVarsInfo.privatizers};
3436
3437 // Allocate and copy private variables before creating the task. This avoids
3438 // accessing invalid memory if (after this scope ends) the private variables
3439 // are initialized from host variables or if the variables are copied into
3440 // from host variables (firstprivate). The insertion point is just before
3441 // where the code for creating and scheduling the task will go. That puts this
3442 // code outside of the outlined task region, which is what we want because
3443 // this way the initialization and copy regions are executed immediately while
3444 // the host variable data are still live.
3446 InsertPointTy allocaIP =
3447 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
3448
3449 // Not using splitBB() because that requires the current block to have a
3450 // terminator.
3451 assert(builder.GetInsertPoint() == builder.GetInsertBlock()->end());
3452 llvm::BasicBlock *taskStartBlock = llvm::BasicBlock::Create(
3453 builder.getContext(), "omp.task.start",
3454 /*Parent=*/builder.GetInsertBlock()->getParent());
3455 llvm::Instruction *branchToTaskStartBlock = builder.CreateBr(taskStartBlock);
3456 builder.SetInsertPoint(branchToTaskStartBlock);
3457
3458 // Now do this again to make the initialization and copy blocks
3459 llvm::BasicBlock *copyBlock =
3460 splitBB(builder, /*CreateBranch=*/true, "omp.private.copy");
3461 llvm::BasicBlock *initBlock =
3462 splitBB(builder, /*CreateBranch=*/true, "omp.private.init");
3463
3464 // Now the control flow graph should look like
3465 // starter_block:
3466 // <---- where we started when convertOmpTaskOp was called
3467 // br %omp.private.init
3468 // omp.private.init:
3469 // br %omp.private.copy
3470 // omp.private.copy:
3471 // br %omp.task.start
3472 // omp.task.start:
3473 // <---- where we want the insertion point to be when we call createTask()
3474
3475 // Save the alloca insertion point on ModuleTranslation stack for use in
3476 // nested regions.
3478 moduleTranslation, allocaIP.getNodeParent(), deallocBlocks);
3479
3480 // Allocate and initialize private variables
3481 builder.SetInsertPoint(initBlock->getTerminator());
3482
3483 // Create task variable structure
3484 taskStructMgr.generateTaskContextStruct();
3485 // GEPs so that we can initialize the variables. Don't use these GEPs inside
3486 // of the body otherwise it will be the GEP not the struct which is fowarded
3487 // to the outlined function. GEPs forwarded in this way are passed in a
3488 // stack-allocated (by OpenMPIRBuilder) structure which is not safe for tasks
3489 // which may not be executed until after the current stack frame goes out of
3490 // scope.
3491 taskStructMgr.createGEPsToPrivateVars();
3492
3493 for (auto [privDecl, mlirPrivVar, blockArg, llvmPrivateVarAlloc] :
3494 llvm::zip_equal(privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
3495 privateVarsInfo.blockArgs,
3496 taskStructMgr.getLLVMPrivateVarGEPs())) {
3497 // To be handled inside the task.
3498 if (!privDecl.readsFromMold())
3499 continue;
3500 assert(llvmPrivateVarAlloc &&
3501 "reads from mold so shouldn't have been skipped");
3502
3503 llvm::Expected<llvm::Value *> privateVarOrErr =
3504 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
3505 blockArg, llvmPrivateVarAlloc, initBlock);
3506 if (!privateVarOrErr)
3507 return handleError(privateVarOrErr, *taskOp.getOperation());
3508
3510
3511 // TODO: this is a bit of a hack for Fortran character boxes.
3512 // Character boxes are passed by value into the init region and then the
3513 // initialized character box is yielded by value. Here we need to store the
3514 // yielded value into the private allocation, and load the private
3515 // allocation to match the type expected by region block arguments.
3516 [[maybe_unused]] llvm::Value *llvmPrivateVar = llvmPrivateVarAlloc;
3517 if ((privateVarOrErr.get() != llvmPrivateVarAlloc) &&
3518 !mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3519 builder.CreateStore(privateVarOrErr.get(), llvmPrivateVarAlloc);
3520 // Load it so we have the value pointed to by the GEP
3521 llvmPrivateVar = builder.CreateLoad(privateVarOrErr.get()->getType(),
3522 llvmPrivateVarAlloc);
3523 }
3524 assert(llvmPrivateVar->getType() ==
3525 moduleTranslation.convertType(blockArg.getType()));
3526
3527 // Mapping blockArg -> llvmPrivateVarAlloc is done inside the body callback
3528 // so that OpenMPIRBuilder doesn't try to pass each GEP address through a
3529 // stack allocated structure.
3530 }
3531
3532 // firstprivate copy region
3533 setInsertPointForPossiblyEmptyBlock(builder, copyBlock);
3534 if (failed(completePrivateVars(
3535 taskOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
3536 taskStructMgr.getLLVMPrivateVarGEPs(), privateVarsInfo.privatizers,
3537 taskOp.getPrivateNeedsBarrier())))
3538 return llvm::failure();
3539
3540 llvm::OpenMPIRBuilder::AffinityData ad;
3541 if (failed(buildAffinityData(taskOp, builder, moduleTranslation, ad)))
3542 return llvm::failure();
3543
3544 // Resolve and validate in_reduction declarations. Byref in_reduction has
3545 // already been rejected by checkImplementationStatus; the helper rejects the
3546 // remaining richer declare_reduction shapes (two-argument initializer,
3547 // cleanup region, missing combiner). This is pure MLIR symbol-table work and
3548 // emits no IR. The matching task_reduction descriptor is registered by an
3549 // enclosing taskgroup; here we only look the per-task storage up at runtime.
3552 taskOp.getOperation(), taskOp.getInReductionSyms(), "omp.task",
3553 "in_reduction", inRedDecls)))
3554 return failure();
3555 SmallVector<llvm::Value *> inRedOrigPtrs;
3556 inRedOrigPtrs.reserve(inRedDecls.size());
3557 for (Value v : taskOp.getInReductionVars())
3558 inRedOrigPtrs.push_back(moduleTranslation.lookupValue(v));
3559
3560 // Set up for call to createTask()
3561 builder.SetInsertPoint(taskStartBlock);
3562
3563 auto bodyCB =
3564 [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
3565 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
3566 // Save the alloca insertion point on ModuleTranslation stack for use in
3567 // nested regions.
3569 moduleTranslation, allocaIP.getNodeParent(), deallocBlocks);
3570
3571 // translate the body of the task:
3572 builder.restoreIP(codegenIP);
3573
3574 llvm::BasicBlock *privInitBlock = nullptr;
3575 privateVarsInfo.llvmVars.resize(privateVarsInfo.blockArgs.size());
3576 for (auto [i, zip] : llvm::enumerate(llvm::zip_equal(
3577 privateVarsInfo.blockArgs, privateVarsInfo.privatizers,
3578 privateVarsInfo.mlirVars))) {
3579 auto [blockArg, privDecl, mlirPrivVar] = zip;
3580 // This is handled before the task executes
3581 if (privDecl.readsFromMold())
3582 continue;
3583
3584 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3585 llvm::Type *llvmAllocType =
3586 moduleTranslation.convertType(privDecl.getType());
3587 builder.SetInsertPoint(allocaIP.getNodeParent()->getTerminator());
3588 llvm::Value *llvmPrivateVar = builder.CreateAlloca(
3589 llvmAllocType, /*ArraySize=*/nullptr, "omp.private.alloc");
3590
3591 llvm::Expected<llvm::Value *> privateVarOrError =
3592 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
3593 blockArg, llvmPrivateVar, privInitBlock);
3594 if (!privateVarOrError)
3595 return privateVarOrError.takeError();
3596 moduleTranslation.mapValue(blockArg, privateVarOrError.get());
3597 privateVarsInfo.llvmVars[i] = privateVarOrError.get();
3598 }
3599
3600 taskStructMgr.createGEPsToPrivateVars();
3601 for (auto [i, llvmPrivVar] :
3602 llvm::enumerate(taskStructMgr.getLLVMPrivateVarGEPs())) {
3603 if (!llvmPrivVar) {
3604 assert(privateVarsInfo.llvmVars[i] &&
3605 "This is added in the loop above");
3606 continue;
3607 }
3608 privateVarsInfo.llvmVars[i] = llvmPrivVar;
3609 }
3610
3611 // Find and map the addresses of each variable within the task context
3612 // structure
3613 for (auto [blockArg, llvmPrivateVar, privateDecl] :
3614 llvm::zip_equal(privateVarsInfo.blockArgs, privateVarsInfo.llvmVars,
3615 privateVarsInfo.privatizers)) {
3616 // This was handled above.
3617 if (!privateDecl.readsFromMold())
3618 continue;
3619 // Fix broken pass-by-value case for Fortran character boxes
3620 if (!mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3621 llvmPrivateVar = builder.CreateLoad(
3622 moduleTranslation.convertType(blockArg.getType()), llvmPrivateVar);
3623 }
3624 assert(llvmPrivateVar->getType() ==
3625 moduleTranslation.convertType(blockArg.getType()));
3626 moduleTranslation.mapValue(blockArg, llvmPrivateVar);
3627 }
3628
3629 // Map in_reduction block arguments to the per-task private storage returned
3630 // by __kmpc_task_reduction_get_th_data. This call must be emitted inside
3631 // the to-be-outlined task body so that it returns the *executing* thread's
3632 // gtid (not the encountering thread's). The descriptor is NULL: the runtime
3633 // walks up enclosing taskgroups to find the matching task_reduction
3634 // registration for `origPtr`. The original pointers are auto-captured into
3635 // the task shareds aggregate by CodeExtractor during
3636 // OpenMPIRBuilder::finalize.
3637 if (!inRedDecls.empty()) {
3638 auto iface = cast<omp::BlockArgOpenMPOpInterface>(taskOp.getOperation());
3639 llvm::OpenMPIRBuilder &ompB = *moduleTranslation.getOpenMPBuilder();
3640 llvm::Module *m = moduleTranslation.getLLVMModule();
3641 llvm::LLVMContext &llvmCtx = m->getContext();
3642 llvm::OpenMPIRBuilder::LocationDescription bodyLoc(builder);
3643 uint32_t srcLocSize;
3644 llvm::Constant *srcLocStr =
3645 ompB.getOrCreateSrcLocStr(bodyLoc, srcLocSize);
3646 llvm::Value *bodyIdent = ompB.getOrCreateIdent(srcLocStr, srcLocSize);
3647 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the gtid
3648 // call lands inside the to-be-outlined task body.
3649 ompB.updateToLocation(bodyLoc);
3650 llvm::Value *bodyGtid = ompB.getOrCreateThreadID(bodyIdent);
3651 llvm::FunctionCallee getThData = ompB.getOrCreateRuntimeFunction(
3652 *m, llvm::omp::OMPRTL___kmpc_task_reduction_get_th_data);
3653 llvm::Type *ptrTy = llvm::PointerType::getUnqual(llvmCtx);
3654 llvm::Value *nullDesc = llvm::ConstantPointerNull::get(ptrTy);
3655 ArrayRef<BlockArgument> inRedBlockArgs = iface.getInReductionBlockArgs();
3656 for (auto [blockArg, origPtr] :
3657 llvm::zip_equal(inRedBlockArgs, inRedOrigPtrs)) {
3658 // __kmpc_task_reduction_get_th_data takes and returns a generic,
3659 // default-address-space `ptr`. Normalize a non-default-address-space
3660 // original pointer to the generic address space before the call, and
3661 // cast the returned private pointer back to the block argument's
3662 // address space when it differs (mirrors the taskloop reduction
3663 // remapping in convertOmpTaskloopContextOp).
3664 llvm::Value *lookupPtr = origPtr;
3665 if (auto *origPtrTy =
3666 llvm::dyn_cast<llvm::PointerType>(lookupPtr->getType());
3667 origPtrTy && origPtrTy->getAddressSpace() != 0)
3668 lookupPtr = builder.CreateAddrSpaceCast(lookupPtr, ptrTy);
3669 llvm::Value *priv = builder.CreateCall(
3670 getThData, {bodyGtid, nullDesc, lookupPtr}, "omp.inred.priv");
3671 if (auto *argPtrTy = llvm::dyn_cast<llvm::PointerType>(
3672 moduleTranslation.convertType(blockArg.getType()));
3673 argPtrTy && argPtrTy->getAddressSpace() != 0)
3674 priv = builder.CreateAddrSpaceCast(priv, argPtrTy);
3675 moduleTranslation.mapValue(blockArg, priv);
3676 }
3677 }
3678
3679 auto continuationBlockOrError = convertOmpOpRegions(
3680 taskOp.getRegion(), "omp.task.region", builder, moduleTranslation);
3681 if (failed(handleError(continuationBlockOrError, *taskOp)))
3682 return llvm::make_error<PreviouslyReportedError>();
3683
3684 builder.SetInsertPoint(continuationBlockOrError.get()->getTerminator());
3685
3686 if (failed(cleanupPrivateVars(taskOp, builder, moduleTranslation,
3687 taskOp.getLoc(), privateVarsInfo)))
3688 return llvm::make_error<PreviouslyReportedError>();
3689
3690 // Free heap allocated task context structure at the end of the task.
3691 taskStructMgr.freeStructPtr();
3692
3693 return llvm::Error::success();
3694 };
3695
3696 llvm::OpenMPIRBuilder &ompBuilder = *moduleTranslation.getOpenMPBuilder();
3697 SmallVector<llvm::UncondBrInst *> cancelTerminators;
3698 // The directive to match here is OMPD_taskgroup because it is the taskgroup
3699 // which is canceled. This is handled here because it is the task's cleanup
3700 // block which should be branched to.
3701 pushCancelFinalizationCB(cancelTerminators, builder, ompBuilder, taskOp,
3702 llvm::omp::Directive::OMPD_taskgroup);
3703
3704 llvm::OpenMPIRBuilder::DependenciesInfo dependencies;
3705 if (failed(buildDependData(taskOp.getDependVars(), taskOp.getDependKinds(),
3706 taskOp.getDependIterated(),
3707 taskOp.getDependIteratedKinds(), builder,
3708 moduleTranslation, dependencies)))
3709 return failure();
3710
3711 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
3712 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
3713 moduleTranslation.getOpenMPBuilder()->createTask(
3714 ompLoc, allocaIP, deallocBlocks, bodyCB, !taskOp.getUntied(),
3715 moduleTranslation.lookupValue(taskOp.getFinal()),
3716 moduleTranslation.lookupValue(taskOp.getIfExpr()), dependencies, ad,
3717 taskOp.getMergeable(),
3718 moduleTranslation.lookupValue(taskOp.getEventHandle()),
3719 moduleTranslation.lookupValue(taskOp.getPriority()),
3720 taskOp.getThreadset() == omp::ThreadsetPolicy::omp_pool);
3721
3722 if (failed(handleError(afterIP, *taskOp)))
3723 return failure();
3724
3725 // Set the correct branch target for task cancellation
3726 popCancelFinalizationCB(cancelTerminators, ompBuilder,
3727 afterIP->getNodeParent());
3728
3729 builder.restoreIP(*afterIP);
3730
3731 if (dependencies.DepArray)
3732 builder.CreateFree(dependencies.DepArray);
3733
3734 return success();
3735}
3736
3737/// The correct entry point is convertOmpTaskloopContextOp. This gets called
3738/// whilst lowering the body of the taskloop context (i.e. the task function).
3739static LogicalResult
3740convertOmpTaskloopWrapperOp(omp::TaskloopWrapperOp loopWrapperOp,
3741 llvm::IRBuilderBase &builder,
3742 LLVM::ModuleTranslation &moduleTranslation) {
3743 mlir::Operation &opInst = *loopWrapperOp.getOperation();
3744 if (failed(checkImplementationStatus(opInst)))
3745 return failure();
3746
3747 // Recurse into the loop body.
3748 auto continuationBlockOrError = convertOmpOpRegions(
3749 loopWrapperOp.getRegion(), "omp.taskloop.wrapper.region", builder,
3750 moduleTranslation);
3751
3752 if (failed(handleError(continuationBlockOrError, opInst)))
3753 return failure();
3754
3755 builder.SetInsertPoint(continuationBlockOrError.get());
3756 return success();
3757}
3758
3759/// Look up the given value in the mapping, and if it's not there, translate its
3760/// defining operation at the current builder insertion point. Only pure,
3761/// regionless operations are supported because the same operation will later be
3762/// translated again when the taskloop body itself is lowered.
3763static llvm::Expected<llvm::Value *>
3765 LLVM::ModuleTranslation &moduleTranslation,
3766 llvm::IRBuilderBase &builder) {
3767 if (llvm::Value *mapped = moduleTranslation.lookupValue(value))
3768 return mapped;
3769
3770 Operation *defOp = value.getDefiningOp();
3771 if (!defOp)
3772 return llvm::make_error<llvm::StringError>(
3773 "value is a block argument and is not mapped",
3774 llvm::inconvertibleErrorCode());
3775 if (defOp->getNumRegions() != 0 || !isPure(defOp))
3776 return llvm::make_error<llvm::StringError>(
3777 "unsupported op defining taskloop loop bound",
3778 llvm::inconvertibleErrorCode());
3779
3780 SmallVector<Value> mappingsToRemove;
3781 mappingsToRemove.reserve(defOp->getNumOperands() + defOp->getNumResults());
3782 for (Value operand : defOp->getOperands()) {
3783 if (moduleTranslation.lookupValue(operand))
3784 continue;
3785
3786 llvm::Expected<llvm::Value *> operandOrError =
3787 lookupOrTranslatePureValue(operand, moduleTranslation, builder);
3788 if (!operandOrError)
3789 return operandOrError.takeError();
3790 moduleTranslation.mapValue(operand, *operandOrError);
3791 mappingsToRemove.push_back(operand);
3792 }
3793
3794 if (failed(moduleTranslation.convertOperation(*defOp, builder)))
3795 return llvm::make_error<llvm::StringError>(
3796 "failed to convert op defining taskloop loop bound",
3797 llvm::inconvertibleErrorCode());
3798
3799 llvm::Value *result = moduleTranslation.lookupValue(value);
3800 assert(result && "expected conversion of loop bound op to produce a value");
3801
3802 for (Value resultValue : defOp->getResults()) {
3803 if (moduleTranslation.lookupValue(resultValue))
3804 mappingsToRemove.push_back(resultValue);
3805 }
3806 for (Value mappedValue : mappingsToRemove)
3807 moduleTranslation.forgetMapping(mappedValue);
3808
3809 return result;
3810}
3811
3812static llvm::Error
3813computeTaskloopBounds(omp::LoopNestOp loopOp, llvm::IRBuilderBase &builder,
3814 LLVM::ModuleTranslation &moduleTranslation,
3815 llvm::Value *&lbVal, llvm::Value *&ubVal,
3816 llvm::Value *&stepVal) {
3817 Operation::operand_range lowerBounds = loopOp.getLoopLowerBounds();
3818 Operation::operand_range upperBounds = loopOp.getLoopUpperBounds();
3819 Operation::operand_range steps = loopOp.getLoopSteps();
3820
3821 llvm::Expected<llvm::Value *> firstLbOrErr =
3822 lookupOrTranslatePureValue(lowerBounds[0], moduleTranslation, builder);
3823 if (!firstLbOrErr)
3824 return firstLbOrErr.takeError();
3825
3826 llvm::Type *boundType = (*firstLbOrErr)->getType();
3827 ubVal = builder.getIntN(boundType->getIntegerBitWidth(), 1);
3828 if (loopOp.getCollapseNumLoops() > 1) {
3829 // In cases where Collapse is used with Taskloop, the upper bound of the
3830 // iteration space needs to be recalculated to cater for the collapsed loop.
3831 // The Collapsed Loop UpperBound is the product of all collapsed
3832 // loop's tripcount.
3833 // The LowerBound for collapsed loops is always 1. When the loops are
3834 // collapsed, it will reset the bounds and introduce processing to ensure
3835 // the index's are presented as expected. As this happens after creating
3836 // Taskloop, these bounds need predicting. Example:
3837 // !$omp taskloop collapse(2)
3838 // do i = 1, 10
3839 // do j = 1, 5
3840 // ..
3841 // end do
3842 // end do
3843 // This loop above has a total of 50 iterations, so the lb will be 1, and
3844 // the ub will be 50. collapseLoops in OMPIRBuilder then handles ensuring
3845 // that i and j are properly presented when used in the loop.
3846 for (uint64_t i = 0; i < loopOp.getCollapseNumLoops(); i++) {
3848 i == 0 ? std::move(firstLbOrErr)
3849 : lookupOrTranslatePureValue(lowerBounds[i], moduleTranslation,
3850 builder);
3851 if (!lbOrErr)
3852 return lbOrErr.takeError();
3854 upperBounds[i], moduleTranslation, builder);
3855 if (!ubOrErr)
3856 return ubOrErr.takeError();
3858 lookupOrTranslatePureValue(steps[i], moduleTranslation, builder);
3859 if (!stepOrErr)
3860 return stepOrErr.takeError();
3861
3862 llvm::Value *loopLb = *lbOrErr;
3863 llvm::Value *loopUb = *ubOrErr;
3864 llvm::Value *loopStep = *stepOrErr;
3865 // In some cases, such as where the ub is less than the lb so the loop
3866 // steps down, the calculation for the loopTripCount is swapped. To ensure
3867 // the correct value is found, calculate both UB - LB and LB - UB then
3868 // select which value to use depending on how the loop has been
3869 // configured.
3870 llvm::Value *loopLbMinusOne = builder.CreateSub(
3871 loopLb, builder.getIntN(boundType->getIntegerBitWidth(), 1));
3872 llvm::Value *loopUbMinusOne = builder.CreateSub(
3873 loopUb, builder.getIntN(boundType->getIntegerBitWidth(), 1));
3874 llvm::Value *boundsCmp = builder.CreateICmpSLT(loopLb, loopUb);
3875 llvm::Value *ubMinusLb = builder.CreateSub(loopUb, loopLbMinusOne);
3876 llvm::Value *lbMinusUb = builder.CreateSub(loopLb, loopUbMinusOne);
3877 llvm::Value *loopTripCount =
3878 builder.CreateSelect(boundsCmp, ubMinusLb, lbMinusUb);
3879 loopTripCount = builder.CreateBinaryIntrinsic(
3880 llvm::Intrinsic::abs, loopTripCount, builder.getFalse());
3881 // For loops that have a step value not equal to 1, we need to adjust the
3882 // trip count to ensure the correct number of iterations for the loop is
3883 // captured.
3884 llvm::Value *loopTripCountDivStep =
3885 builder.CreateSDiv(loopTripCount, loopStep);
3886 loopTripCountDivStep = builder.CreateBinaryIntrinsic(
3887 llvm::Intrinsic::abs, loopTripCountDivStep, builder.getFalse());
3888 llvm::Value *loopTripCountRem =
3889 builder.CreateSRem(loopTripCount, loopStep);
3890 loopTripCountRem = builder.CreateBinaryIntrinsic(
3891 llvm::Intrinsic::abs, loopTripCountRem, builder.getFalse());
3892 llvm::Value *needsRoundUp = builder.CreateICmpNE(
3893 loopTripCountRem,
3894 builder.getIntN(loopTripCountRem->getType()->getIntegerBitWidth(),
3895 0));
3896 loopTripCount =
3897 builder.CreateAdd(loopTripCountDivStep,
3898 builder.CreateZExtOrTrunc(
3899 needsRoundUp, loopTripCountDivStep->getType()));
3900 ubVal = builder.CreateMul(ubVal, loopTripCount);
3901 }
3902 lbVal = builder.getIntN(boundType->getIntegerBitWidth(), 1);
3903 stepVal = builder.getIntN(boundType->getIntegerBitWidth(), 1);
3904 } else {
3906 lookupOrTranslatePureValue(upperBounds[0], moduleTranslation, builder);
3907 if (!ubOrErr)
3908 return ubOrErr.takeError();
3910 lookupOrTranslatePureValue(steps[0], moduleTranslation, builder);
3911 if (!stepOrErr)
3912 return stepOrErr.takeError();
3913 lbVal = *firstLbOrErr;
3914 ubVal = *ubOrErr;
3915 stepVal = *stepOrErr;
3916 }
3917
3918 assert(lbVal != nullptr && "Expected value for lbVal");
3919 assert(ubVal != nullptr && "Expected value for ubVal");
3920 assert(stepVal != nullptr && "Expected value for stepVal");
3921 return llvm::Error::success();
3922}
3923
3924// Converts an OpenMP taskloop construct into LLVM IR using OpenMPIRBuilder.
3925static LogicalResult
3926convertOmpTaskloopContextOp(omp::TaskloopContextOp contextOp,
3927 llvm::IRBuilderBase &builder,
3928 LLVM::ModuleTranslation &moduleTranslation) {
3929 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
3930 mlir::Operation &opInst = *contextOp.getOperation();
3931 omp::TaskloopWrapperOp loopWrapperOp = contextOp.getLoopOp();
3932 if (failed(checkImplementationStatus(opInst)))
3933 return failure();
3934
3935 // It stores the pointer of allocated firstprivate copies,
3936 // which can be used later for freeing the allocated space.
3937 SmallVector<llvm::Value *> llvmFirstPrivateVars;
3938 PrivateVarsInfo privateVarsInfo(contextOp);
3939 TaskContextStructManager taskStructMgr{builder, moduleTranslation,
3940 privateVarsInfo.privatizers};
3941
3943 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
3944 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
3945
3946 assert(builder.GetInsertPoint() == builder.GetInsertBlock()->end());
3947 llvm::BasicBlock *taskloopStartBlock = llvm::BasicBlock::Create(
3948 builder.getContext(), "omp.taskloop.wrapper.start",
3949 /*Parent=*/builder.GetInsertBlock()->getParent());
3950 llvm::Instruction *branchToTaskloopStartBlock =
3951 builder.CreateBr(taskloopStartBlock);
3952 builder.SetInsertPoint(branchToTaskloopStartBlock);
3953
3954 llvm::BasicBlock *copyBlock =
3955 splitBB(builder, /*CreateBranch=*/true, "omp.private.copy");
3956 llvm::BasicBlock *initBlock =
3957 splitBB(builder, /*CreateBranch=*/true, "omp.private.init");
3958
3960 moduleTranslation, allocaIP.getNodeParent(), deallocBlocks);
3961
3962 // Allocate and initialize private variables
3963 builder.SetInsertPoint(initBlock->getTerminator());
3964
3965 // TODO: don't allocate if the loop has zero iterations.
3966 taskStructMgr.generateTaskContextStruct();
3967 taskStructMgr.createGEPsToPrivateVars();
3968
3969 llvmFirstPrivateVars.resize(privateVarsInfo.blockArgs.size());
3970
3971 for (auto [i, zip] : llvm::enumerate(llvm::zip_equal(
3972 privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
3973 privateVarsInfo.blockArgs, taskStructMgr.getLLVMPrivateVarGEPs()))) {
3974 auto [privDecl, mlirPrivVar, blockArg, llvmPrivateVarAlloc] = zip;
3975 // To be handled inside the taskloop.
3976 if (!privDecl.readsFromMold())
3977 continue;
3978 assert(llvmPrivateVarAlloc &&
3979 "reads from mold so shouldn't have been skipped");
3980
3981 llvm::Expected<llvm::Value *> privateVarOrErr =
3982 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
3983 blockArg, llvmPrivateVarAlloc, initBlock);
3984 if (!privateVarOrErr)
3985 return handleError(privateVarOrErr, *contextOp.getOperation());
3986
3987 llvmFirstPrivateVars[i] = privateVarOrErr.get();
3988
3989 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3990 builder.SetInsertPoint(builder.GetInsertBlock()->getTerminator());
3991
3992 [[maybe_unused]] llvm::Value *llvmPrivateVar = llvmPrivateVarAlloc;
3993 if ((privateVarOrErr.get() != llvmPrivateVarAlloc) &&
3994 !mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3995 builder.CreateStore(privateVarOrErr.get(), llvmPrivateVarAlloc);
3996 // Load it so we have the value pointed to by the GEP
3997 llvmPrivateVar = builder.CreateLoad(privateVarOrErr.get()->getType(),
3998 llvmPrivateVarAlloc);
3999 }
4000 assert(llvmPrivateVar->getType() ==
4001 moduleTranslation.convertType(blockArg.getType()));
4002 }
4003
4004 // firstprivate copy region
4005 setInsertPointForPossiblyEmptyBlock(builder, copyBlock);
4006 if (failed(completePrivateVars(
4007 contextOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
4008 taskStructMgr.getLLVMPrivateVarGEPs(), privateVarsInfo.privatizers,
4009 contextOp.getPrivateNeedsBarrier())))
4010 return llvm::failure();
4011
4012 // Resolve and validate reduction / in_reduction declarations up front.
4013 // This is pure MLIR symbol-table work and does not emit IR, so do it
4014 // before moving the builder to the taskloop start block. Richer
4015 // declare_reduction shapes (byref) have been rejected already by
4016 // checkImplementationStatus; the rest (two-argument initializer, cleanup
4017 // region, missing combiner) are rejected by the helper.
4020 contextOp.getOperation(), contextOp.getReductionSyms(),
4021 "omp.taskloop.context", "reduction", redDecls)))
4022 return failure();
4025 contextOp.getOperation(), contextOp.getInReductionSyms(),
4026 "omp.taskloop.context", "in_reduction", inRedDecls)))
4027 return failure();
4028
4029 // The op verifier rejects nogroup + reduction, so no check is needed here.
4030
4031 SmallVector<llvm::Value *> redOrigPtrs;
4032 redOrigPtrs.reserve(redDecls.size());
4033 for (Value v : contextOp.getReductionVars())
4034 redOrigPtrs.push_back(moduleTranslation.lookupValue(v));
4035 SmallVector<llvm::Value *> inRedOrigPtrs;
4036 inRedOrigPtrs.reserve(inRedDecls.size());
4037 for (Value v : contextOp.getInReductionVars())
4038 inRedOrigPtrs.push_back(moduleTranslation.lookupValue(v));
4039
4040 // Set up insertion point for emitting the implicit-taskgroup reduction
4041 // setup (if any) and for the subsequent call to createTaskloop().
4042 builder.SetInsertPoint(taskloopStartBlock);
4043
4044 llvm::OpenMPIRBuilder &ompBuilderRef = *moduleTranslation.getOpenMPBuilder();
4045 llvm::Module *module = moduleTranslation.getLLVMModule();
4046
4047 // If we have task_reduction items, we must emit our own implicit
4048 // __kmpc_taskgroup so that the descriptor returned by __kmpc_taskred_init
4049 // is associated with that taskgroup. We then force NoGroup=true so that
4050 // OpenMPIRBuilder::createTaskloop does not emit a second taskgroup.
4051 bool implicitTaskgroup = !redDecls.empty();
4052 llvm::Value *redDesc = nullptr;
4053 if (implicitTaskgroup) {
4054 llvm::OpenMPIRBuilder::LocationDescription redLoc(builder);
4055 uint32_t srcLocSize;
4056 llvm::Constant *srcLocStr =
4057 ompBuilderRef.getOrCreateSrcLocStr(redLoc, srcLocSize);
4058 llvm::Value *ident = ompBuilderRef.getOrCreateIdent(srcLocStr, srcLocSize);
4059 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the
4060 // gtid call lands at our insertion point.
4061 ompBuilderRef.updateToLocation(redLoc);
4062 llvm::Value *outerGtid = ompBuilderRef.getOrCreateThreadID(ident);
4063 llvm::FunctionCallee taskgroupFn = ompBuilderRef.getOrCreateRuntimeFunction(
4064 *module, llvm::omp::OMPRTL___kmpc_taskgroup);
4065 builder.CreateCall(taskgroupFn, {ident, outerGtid});
4066
4067 redDesc = emitTaskReductionInitCall(redDecls, redOrigPtrs,
4068 "__omp_taskloop_taskred_", builder,
4069 allocaIP, moduleTranslation);
4070 if (!redDesc)
4071 return failure();
4072 }
4073
4074 auto loopOp = cast<omp::LoopNestOp>(loopWrapperOp.getWrappedLoop());
4075 llvm::Value *lbVal = nullptr;
4076 llvm::Value *ubVal = nullptr;
4077 llvm::Value *stepVal = nullptr;
4078 if (llvm::Error err = computeTaskloopBounds(
4079 loopOp, builder, moduleTranslation, lbVal, ubVal, stepVal))
4080 return handleError(std::move(err), opInst);
4081
4082 auto bodyCB =
4083 [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
4084 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
4085 // Save the alloca insertion point on ModuleTranslation stack for use in
4086 // nested regions.
4088 moduleTranslation, allocaIP.getNodeParent(), deallocBlocks);
4089
4090 // translate the body of the taskloop:
4091 builder.restoreIP(codegenIP);
4092
4093 llvm::BasicBlock *privInitBlock = nullptr;
4094 privateVarsInfo.llvmVars.resize(privateVarsInfo.blockArgs.size());
4095 for (auto [i, zip] : llvm::enumerate(llvm::zip_equal(
4096 privateVarsInfo.blockArgs, privateVarsInfo.privatizers,
4097 privateVarsInfo.mlirVars))) {
4098 auto [blockArg, privDecl, mlirPrivVar] = zip;
4099 // This is handled before the task executes
4100 if (privDecl.readsFromMold())
4101 continue;
4102
4103 llvm::IRBuilderBase::InsertPointGuard guard(builder);
4104 llvm::Type *llvmAllocType =
4105 moduleTranslation.convertType(privDecl.getType());
4106 builder.SetInsertPoint(allocaIP.getNodeParent()->getTerminator());
4107 llvm::Value *llvmPrivateVar = builder.CreateAlloca(
4108 llvmAllocType, /*ArraySize=*/nullptr, "omp.private.alloc");
4109
4110 llvm::Expected<llvm::Value *> privateVarOrError =
4111 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
4112 blockArg, llvmPrivateVar, privInitBlock);
4113 if (!privateVarOrError)
4114 return privateVarOrError.takeError();
4115 moduleTranslation.mapValue(blockArg, privateVarOrError.get());
4116 privateVarsInfo.llvmVars[i] = privateVarOrError.get();
4117 }
4118
4119 taskStructMgr.createGEPsToPrivateVars();
4120 for (auto [i, llvmPrivVar] :
4121 llvm::enumerate(taskStructMgr.getLLVMPrivateVarGEPs())) {
4122 if (!llvmPrivVar) {
4123 assert(privateVarsInfo.llvmVars[i] &&
4124 "This is added in the loop above");
4125 continue;
4126 }
4127 privateVarsInfo.llvmVars[i] = llvmPrivVar;
4128 }
4129
4130 // Find and map the addresses of each variable within the taskloop context
4131 // structure
4132 for (auto [blockArg, llvmPrivateVar, privateDecl] :
4133 llvm::zip_equal(privateVarsInfo.blockArgs, privateVarsInfo.llvmVars,
4134 privateVarsInfo.privatizers)) {
4135 // This was handled above.
4136 if (!privateDecl.readsFromMold())
4137 continue;
4138 // Fix broken pass-by-value case for Fortran character boxes
4139 if (!mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
4140 llvmPrivateVar = builder.CreateLoad(
4141 moduleTranslation.convertType(blockArg.getType()), llvmPrivateVar);
4142 }
4143 assert(llvmPrivateVar->getType() ==
4144 moduleTranslation.convertType(blockArg.getType()));
4145 moduleTranslation.mapValue(blockArg, llvmPrivateVar);
4146 }
4147
4148 // Map reduction and in_reduction block arguments to the per-task private
4149 // storage returned by __kmpc_task_reduction_get_th_data. This call must
4150 // be emitted inside the to-be-outlined task body so that it returns the
4151 // *executing* thread's gtid (not the encountering thread's). The
4152 // taskgroup descriptor `redDesc` is computed in the outer scope and is
4153 // auto-captured into the task shareds aggregate by CodeExtractor during
4154 // OpenMPIRBuilder::finalize. For in_reduction the descriptor is NULL:
4155 // the runtime walks up enclosing taskgroups to find the matching
4156 // task_reduction registration for `origPtr`.
4157 if (!redDecls.empty() || !inRedDecls.empty()) {
4158 auto iface =
4159 cast<omp::BlockArgOpenMPOpInterface>(contextOp.getOperation());
4160 llvm::OpenMPIRBuilder &ompB = *moduleTranslation.getOpenMPBuilder();
4161 llvm::Module *m = moduleTranslation.getLLVMModule();
4162 llvm::LLVMContext &llvmCtx = m->getContext();
4163 llvm::OpenMPIRBuilder::LocationDescription bodyLoc(builder);
4164 uint32_t srcLocSize;
4165 llvm::Constant *srcLocStr =
4166 ompB.getOrCreateSrcLocStr(bodyLoc, srcLocSize);
4167 llvm::Value *bodyIdent = ompB.getOrCreateIdent(srcLocStr, srcLocSize);
4168 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the
4169 // gtid call lands inside the to-be-outlined task body.
4170 ompB.updateToLocation(bodyLoc);
4171 llvm::Value *bodyGtid = ompB.getOrCreateThreadID(bodyIdent);
4172 llvm::FunctionCallee getThData = ompB.getOrCreateRuntimeFunction(
4173 *m, llvm::omp::OMPRTL___kmpc_task_reduction_get_th_data);
4174 llvm::Type *ptrTy = llvm::PointerType::getUnqual(llvmCtx);
4175
4176 // Emit one __kmpc_task_reduction_get_th_data lookup for a reduction /
4177 // in_reduction item and map its block argument to the per-task private
4178 // storage the runtime returns. The runtime entry point takes (and
4179 // returns) a generic, default-address-space `ptr`, so normalize a
4180 // non-default-address-space original pointer to the generic address
4181 // space before the call (mirroring the descriptor setup in
4182 // emitTaskReductionInitCall), and cast the returned private pointer back
4183 // to the block argument's address space when that differs.
4184 auto remapReductionArg = [&](BlockArgument blockArg, llvm::Value *desc,
4185 llvm::Value *origPtr,
4186 const llvm::Twine &name) {
4187 if (auto *origPtrTy =
4188 llvm::dyn_cast<llvm::PointerType>(origPtr->getType());
4189 origPtrTy && origPtrTy->getAddressSpace() != 0)
4190 origPtr = builder.CreateAddrSpaceCast(origPtr, ptrTy);
4191 llvm::Value *priv =
4192 builder.CreateCall(getThData, {bodyGtid, desc, origPtr}, name);
4193 if (auto *argPtrTy = llvm::dyn_cast<llvm::PointerType>(
4194 moduleTranslation.convertType(blockArg.getType()));
4195 argPtrTy && argPtrTy->getAddressSpace() != 0)
4196 priv = builder.CreateAddrSpaceCast(priv, argPtrTy);
4197 moduleTranslation.mapValue(blockArg, priv);
4198 };
4199
4200 ArrayRef<BlockArgument> redBlockArgs = iface.getReductionBlockArgs();
4201 for (auto [blockArg, origPtr] :
4202 llvm::zip_equal(redBlockArgs, redOrigPtrs))
4203 remapReductionArg(blockArg, redDesc, origPtr, "omp.taskred.priv");
4204 ArrayRef<BlockArgument> inRedBlockArgs = iface.getInReductionBlockArgs();
4205 llvm::Value *nullDesc = llvm::ConstantPointerNull::get(ptrTy);
4206 for (auto [blockArg, origPtr] :
4207 llvm::zip_equal(inRedBlockArgs, inRedOrigPtrs))
4208 remapReductionArg(blockArg, nullDesc, origPtr, "omp.inred.priv");
4209 }
4210
4211 // Lower the contents of the taskloop context region: this is the body of
4212 // the generated task, not the loop.
4213 auto continuationBlockOrError = convertOmpOpRegions(
4214 contextOp.getRegion(), "omp.taskloop.context.region", builder,
4215 moduleTranslation);
4216
4217 if (failed(handleError(continuationBlockOrError, opInst)))
4218 return llvm::make_error<PreviouslyReportedError>();
4219
4220 builder.SetInsertPoint(continuationBlockOrError.get()->getTerminator());
4221
4222 // This is freeing the private variables as mapped inside of the task: these
4223 // will be per-task private copies possibly after task duplication. This is
4224 // handled transparently by how these are passed to the structure passed
4225 // into the outlined function. When the task is duplicated, that structure
4226 // is duplicated too.
4227 if (failed(cleanupPrivateVars(contextOp, builder, moduleTranslation,
4228 contextOp.getLoc(), privateVarsInfo)))
4229 return llvm::make_error<PreviouslyReportedError>();
4230 // Similarly, the task context structure freed inside the task is the
4231 // per-task copy after task duplication.
4232 taskStructMgr.freeStructPtr();
4233
4234 return llvm::Error::success();
4235 };
4236
4237 // Taskloop divides into an appropriate number of tasks by repeatedly
4238 // duplicating the original task. Each time this is done, the task context
4239 // structure must be duplicated too.
4240 auto taskDupCB = [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
4241 llvm::Value *destPtr, llvm::Value *srcPtr)
4243 llvm::IRBuilderBase::InsertPointGuard guard(builder);
4244 builder.restoreIP(codegenIP);
4245
4246 llvm::Type *ptrTy =
4247 builder.getPtrTy(srcPtr->getType()->getPointerAddressSpace());
4248 llvm::Value *src =
4249 builder.CreateLoad(ptrTy, srcPtr, "omp.taskloop.context.src");
4250
4251 TaskContextStructManager &srcStructMgr = taskStructMgr;
4252 TaskContextStructManager destStructMgr(builder, moduleTranslation,
4253 privateVarsInfo.privatizers);
4254 destStructMgr.generateTaskContextStruct();
4255 llvm::Value *dest = destStructMgr.getStructPtr();
4256 dest->setName("omp.taskloop.context.dest");
4257 builder.CreateStore(dest, destPtr);
4258
4260 srcStructMgr.createGEPsToPrivateVars(src);
4262 destStructMgr.createGEPsToPrivateVars(dest);
4263
4264 // Inline init regions.
4265 for (auto [privDecl, mold, blockArg, llvmPrivateVarAlloc] :
4266 llvm::zip_equal(privateVarsInfo.privatizers, srcGEPs,
4267 privateVarsInfo.blockArgs, destGEPs)) {
4268 // To be handled inside task body.
4269 if (!privDecl.readsFromMold())
4270 continue;
4271 assert(llvmPrivateVarAlloc &&
4272 "reads from mold so shouldn't have been skipped");
4273
4274 llvm::Value *moldArg = materializeRegionArgValue(
4275 builder, moduleTranslation, privDecl.getInitMoldArg(), mold);
4277 builder, moduleTranslation, privDecl, moldArg, blockArg,
4278 llvmPrivateVarAlloc, builder.GetInsertBlock());
4279 if (!privateVarOrErr)
4280 return privateVarOrErr.takeError();
4281
4283
4284 // TODO: this is a bit of a hack for Fortran character boxes.
4285 // Character boxes are passed by value into the init region and then the
4286 // initialized character box is yielded by value. Here we need to store
4287 // the yielded value into the private allocation, and load the private
4288 // allocation to match the type expected by region block arguments.
4289 [[maybe_unused]] llvm::Value *llvmPrivateVar = llvmPrivateVarAlloc;
4290 if ((privateVarOrErr.get() != llvmPrivateVarAlloc) &&
4291 !mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
4292 builder.CreateStore(privateVarOrErr.get(), llvmPrivateVarAlloc);
4293 // Load it so we have the value pointed to by the GEP
4294 llvmPrivateVar = builder.CreateLoad(privateVarOrErr.get()->getType(),
4295 llvmPrivateVarAlloc);
4296 }
4297 assert(llvmPrivateVar->getType() ==
4298 moduleTranslation.convertType(blockArg.getType()));
4299
4300 // Mapping blockArg -> llvmPrivateVarAlloc is done inside the body
4301 // callback so that OpenMPIRBuilder doesn't try to pass each GEP address
4302 // through a stack allocated structure.
4303 }
4304
4305 if (failed(completePrivateVars(contextOp.getOperation(), builder,
4306 moduleTranslation, srcGEPs, destGEPs,
4307 privateVarsInfo.privatizers,
4308 contextOp.getPrivateNeedsBarrier())))
4309 return llvm::make_error<PreviouslyReportedError>();
4310
4311 return builder.saveIP();
4312 };
4313
4314 auto loopInfo = [&]() -> llvm::Expected<llvm::CanonicalLoopInfo *> {
4315 llvm::CanonicalLoopInfo *loopInfo = findCurrentLoopInfo(moduleTranslation);
4316 return loopInfo;
4317 };
4318
4319 llvm::Value *ifCond = nullptr;
4320 llvm::Value *grainsize = nullptr;
4321 int sched = 0; // default
4322 mlir::Value grainsizeVal = contextOp.getGrainsize();
4323 mlir::Value numTasksVal = contextOp.getNumTasks();
4324 if (Value ifVar = contextOp.getIfExpr())
4325 ifCond = moduleTranslation.lookupValue(ifVar);
4326 if (grainsizeVal) {
4327 grainsize = moduleTranslation.lookupValue(grainsizeVal);
4328 sched = 1; // grainsize
4329 } else if (numTasksVal) {
4330 grainsize = moduleTranslation.lookupValue(numTasksVal);
4331 sched = 2; // num_tasks
4332 }
4333
4334 llvm::OpenMPIRBuilder::TaskDupCallbackTy taskDupOrNull = nullptr;
4335 if (taskStructMgr.getStructPtr())
4336 taskDupOrNull = taskDupCB;
4337
4338 llvm::OpenMPIRBuilder &ompBuilder = *moduleTranslation.getOpenMPBuilder();
4339 SmallVector<llvm::UncondBrInst *> cancelTerminators;
4340 // The directive to match here is OMPD_taskgroup because it is the
4341 // taskgroup which is canceled. This is handled here because it is the
4342 // task's cleanup block which should be branched to. It doesn't depend upon
4343 // nogroup because even in that case the taskloop might still be inside an
4344 // explicit taskgroup.
4345 pushCancelFinalizationCB(cancelTerminators, builder, ompBuilder, contextOp,
4346 llvm::omp::Directive::OMPD_taskgroup);
4347
4348 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4349 bool effectiveNoGroup = contextOp.getNogroup() || implicitTaskgroup;
4350 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
4351 moduleTranslation.getOpenMPBuilder()->createTaskloop(
4352 ompLoc, allocaIP, deallocBlocks, bodyCB, loopInfo, lbVal, ubVal,
4353 stepVal, contextOp.getUntied(), ifCond, grainsize, effectiveNoGroup,
4354 sched, moduleTranslation.lookupValue(contextOp.getFinal()),
4355 contextOp.getMergeable(),
4356 moduleTranslation.lookupValue(contextOp.getPriority()),
4357 loopOp.getCollapseNumLoops(), taskDupOrNull,
4358 taskStructMgr.getStructPtr(),
4359 contextOp.getThreadset() == omp::ThreadsetPolicy::omp_pool);
4360
4361 if (failed(handleError(afterIP, opInst)))
4362 return failure();
4363
4364 popCancelFinalizationCB(cancelTerminators, ompBuilder,
4365 afterIP->getNodeParent());
4366
4367 builder.restoreIP(*afterIP);
4368
4369 // Close the implicit taskgroup we opened for task_reduction. The end call
4370 // must execute on the encountering thread, so use the outer-scope gtid.
4371 if (implicitTaskgroup) {
4372 llvm::OpenMPIRBuilder::LocationDescription endLoc(builder);
4373 uint32_t srcLocSize;
4374 llvm::Constant *srcLocStr =
4375 ompBuilder.getOrCreateSrcLocStr(endLoc, srcLocSize);
4376 llvm::Value *ident = ompBuilder.getOrCreateIdent(srcLocStr, srcLocSize);
4377 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the
4378 // gtid call lands at our insertion point.
4379 ompBuilder.updateToLocation(endLoc);
4380 llvm::Value *outerGtid = ompBuilder.getOrCreateThreadID(ident);
4381 llvm::FunctionCallee endTgFn = ompBuilder.getOrCreateRuntimeFunction(
4382 *moduleTranslation.getLLVMModule(),
4383 llvm::omp::OMPRTL___kmpc_end_taskgroup);
4384 builder.CreateCall(endTgFn, {ident, outerGtid});
4385 }
4386 return success();
4387}
4388
4389/// Build an outlined init helper for a task_reduction declare_reduction op.
4390/// Signature: void(ptr %priv, ptr %orig). For non-byref reductions, the init
4391/// region's mold argument is mapped following the same rule as the regular
4392/// reduction path (`mapInitializationArgs`): a non-pointer mold loads the
4393/// value from %orig, while a pointer-typed mold receives %orig directly. The
4394/// yielded value is stored into %priv.
4395static llvm::Function *
4396emitTaskReductionInitFn(omp::DeclareReductionOp decl, StringRef baseName,
4397 LLVM::ModuleTranslation &moduleTranslation) {
4398 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4399 llvm::LLVMContext &ctx = llvmModule->getContext();
4400 llvm::Type *voidTy = llvm::Type::getVoidTy(ctx);
4401 llvm::Type *ptrTy = llvm::PointerType::getUnqual(ctx);
4402 llvm::FunctionType *fty =
4403 llvm::FunctionType::get(voidTy, {ptrTy, ptrTy}, false);
4404 llvm::Function *fn =
4405 llvm::Function::Create(fty, llvm::GlobalValue::InternalLinkage,
4406 baseName + ".red.init", llvmModule);
4407 fn->setDoesNotRecurse();
4408 fn->getArg(0)->setName("priv");
4409 fn->getArg(1)->setName("orig");
4410
4411 llvm::BasicBlock *entry = llvm::BasicBlock::Create(ctx, "entry", fn);
4412 llvm::IRBuilder<> b(entry);
4413
4414 // Map the initializer's mold argument the same way the regular reduction
4415 // path does in `mapInitializationArgs`: only load the original value when a
4416 // non-pointer mold is expected. For a pointer-typed mold the storage pointer
4417 // (%orig) is passed through directly, so a mold-yielding initializer lowers
4418 // to `store ptr %orig, ptr %priv` rather than emitting a spurious load.
4419 Value moldArg = decl.getInitializerMoldArg();
4420 llvm::Value *origVal = fn->getArg(1);
4421 if (!isa<LLVM::LLVMPointerType>(moldArg.getType()))
4422 origVal = b.CreateLoad(moduleTranslation.convertType(moldArg.getType()),
4423 fn->getArg(1), "omp.orig");
4424 moduleTranslation.mapValue(moldArg, origVal);
4426 if (failed(inlineConvertOmpRegions(decl.getInitializerRegion(),
4427 "omp.taskred.init", b, moduleTranslation,
4428 &phis))) {
4429 fn->eraseFromParent();
4430 return nullptr;
4431 }
4432 assert(phis.size() == 1 &&
4433 "expected one value yielded from reduction initializer");
4434 b.CreateStore(phis[0], fn->getArg(0));
4435 b.CreateRetVoid();
4436
4437 moduleTranslation.forgetMapping(decl.getInitializerRegion());
4438 return fn;
4439}
4440
4441/// Build an outlined combiner helper for a task_reduction declare_reduction op.
4442/// Signature: void(ptr %lhs, ptr %rhs). For non-byref reductions, the values
4443/// at *%lhs and *%rhs are loaded, fed into the combiner region, and the
4444/// yielded scalar is stored back into *%lhs.
4445static llvm::Function *
4446emitTaskReductionCombFn(omp::DeclareReductionOp decl, StringRef baseName,
4447 LLVM::ModuleTranslation &moduleTranslation) {
4448 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4449 llvm::LLVMContext &ctx = llvmModule->getContext();
4450 llvm::Type *voidTy = llvm::Type::getVoidTy(ctx);
4451 llvm::Type *ptrTy = llvm::PointerType::getUnqual(ctx);
4452 llvm::FunctionType *fty =
4453 llvm::FunctionType::get(voidTy, {ptrTy, ptrTy}, false);
4454 llvm::Function *fn =
4455 llvm::Function::Create(fty, llvm::GlobalValue::InternalLinkage,
4456 baseName + ".red.comb", llvmModule);
4457 fn->setDoesNotRecurse();
4458 fn->getArg(0)->setName("lhs");
4459 fn->getArg(1)->setName("rhs");
4460
4461 llvm::BasicBlock *entry = llvm::BasicBlock::Create(ctx, "entry", fn);
4462 llvm::IRBuilder<> b(entry);
4463
4464 llvm::Type *elemTy = moduleTranslation.convertType(decl.getType());
4465 Block &combBlock = decl.getReductionRegion().front();
4466 assert(combBlock.getNumArguments() == 2 &&
4467 "expected two arguments in declare_reduction combiner");
4468 llvm::Value *lhsVal = b.CreateLoad(elemTy, fn->getArg(0), "omp.lhs");
4469 llvm::Value *rhsVal = b.CreateLoad(elemTy, fn->getArg(1), "omp.rhs");
4470 moduleTranslation.mapValue(combBlock.getArgument(0), lhsVal);
4471 moduleTranslation.mapValue(combBlock.getArgument(1), rhsVal);
4472
4474 if (failed(inlineConvertOmpRegions(decl.getReductionRegion(),
4475 "omp.taskred.comb", b, moduleTranslation,
4476 &phis))) {
4477 fn->eraseFromParent();
4478 return nullptr;
4479 }
4480 assert(phis.size() == 1 &&
4481 "expected one value yielded from reduction combiner");
4482 b.CreateStore(phis[0], fn->getArg(0));
4483 b.CreateRetVoid();
4484
4485 moduleTranslation.forgetMapping(decl.getReductionRegion());
4486 return fn;
4487}
4488
4489/// Emit the per-taskgroup task_reduction descriptor array and the
4490/// `__kmpc_taskred_init` runtime call. \p origPtrs holds the LLVM values for
4491/// the original (shared) variables, one per declaration in \p redDecls.
4492/// `builder` must be set to the point at which the descriptor stores and the
4493/// init call should be emitted; the descriptor array itself is allocated at
4494/// \p allocaIP. \p helperNamePrefix is used to disambiguate the generated
4495/// init/combiner helper symbol names between taskgroup and taskloop callers.
4496///
4497/// When \p isModifier is false, emits `__kmpc_taskred_init` and returns the
4498/// `ptr` value it produces (the taskgroup reduction handle). When \p isModifier
4499/// is true, emits `__kmpc_taskred_modifier_init` instead to open a
4500/// task-reduction scope for a parallel or worksharing construct, passing
4501/// \p isWorksharing as the runtime `is_ws` argument. Returns null on failure.
4502///
4503/// Only the non-byref form is handled here. Byref reductions have already
4504/// been rejected by `checkImplementationStatus`.
4505static llvm::Value *emitTaskReductionInitCall(
4507 ArrayRef<llvm::Value *> origPtrs, StringRef helperNamePrefix,
4508 llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder::InsertPointTy allocaIP,
4509 LLVM::ModuleTranslation &moduleTranslation, bool isModifier,
4510 bool isWorksharing) {
4511 assert(redDecls.size() == origPtrs.size() &&
4512 "expected one orig pointer per reduction decl");
4513 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4514 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4515 llvm::LLVMContext &ctx = llvmModule->getContext();
4516 const llvm::DataLayout &dl = llvmModule->getDataLayout();
4517
4518 llvm::Type *ptrTy = llvm::PointerType::getUnqual(ctx);
4519 llvm::Type *i32Ty = llvm::Type::getInt32Ty(ctx);
4520 llvm::Type *sizeTy =
4521 llvm::Type::getIntNTy(ctx, dl.getPointerSizeInBits(/*AddrSpace=*/0));
4522
4523 // Identified `kmp_taskred_input_t` struct, matching the layout used by
4524 // Clang's CGOpenMPRuntime::emitTaskReductionInit.
4525 llvm::StructType *redInputTy =
4526 llvm::StructType::getTypeByName(ctx, "kmp_taskred_input_t");
4527 if (!redInputTy)
4528 redInputTy = llvm::StructType::create(
4529 ctx, {ptrTy, ptrTy, sizeTy, ptrTy, ptrTy, ptrTy, i32Ty},
4530 "kmp_taskred_input_t");
4531
4532 unsigned n = redDecls.size();
4533 llvm::ArrayType *arrTy = llvm::ArrayType::get(redInputTy, n);
4534
4535 // Allocate the descriptor array in the enclosing function's alloca block.
4536 llvm::AllocaInst *arrAlloca;
4537 {
4538 llvm::IRBuilderBase::InsertPointGuard guard(builder);
4539 builder.restoreIP(allocaIP);
4540 arrAlloca =
4541 builder.CreateAlloca(arrTy, /*ArraySize=*/nullptr, ".taskred.input");
4542 }
4543
4544 // Fill each descriptor entry at the current builder insertion point.
4545 llvm::Value *zero = builder.getInt32(0);
4546 for (unsigned i = 0; i < n; ++i) {
4547 omp::DeclareReductionOp decl = redDecls[i];
4548 llvm::Value *orig = origPtrs[i];
4549 if (auto *origPtrTy = llvm::dyn_cast<llvm::PointerType>(orig->getType());
4550 origPtrTy && origPtrTy->getAddressSpace() != 0)
4551 orig = builder.CreateAddrSpaceCast(orig, ptrTy);
4552 llvm::Type *elemTy = moduleTranslation.convertType(decl.getType());
4553 uint64_t size = dl.getTypeAllocSize(elemTy).getFixedValue();
4554
4555 std::string baseName =
4556 (llvm::Twine(helperNamePrefix) + decl.getSymName()).str();
4557 llvm::Function *initFn =
4558 emitTaskReductionInitFn(decl, baseName, moduleTranslation);
4559 llvm::Function *combFn =
4560 emitTaskReductionCombFn(decl, baseName, moduleTranslation);
4561 if (!initFn || !combFn)
4562 return nullptr;
4563 llvm::Value *elemPtr = builder.CreateInBoundsGEP(
4564 arrTy, arrAlloca, {zero, builder.getInt32(i)}, ".taskred.elem");
4565 auto storeField = [&](unsigned fieldIdx, llvm::Value *val) {
4566 llvm::Value *fieldPtr =
4567 builder.CreateStructGEP(redInputTy, elemPtr, fieldIdx);
4568 builder.CreateStore(val, fieldPtr);
4569 };
4570 storeField(0, orig); // reduce_shar
4571 storeField(1, orig); // reduce_orig
4572 storeField(2, llvm::ConstantInt::get(sizeTy, size)); // reduce_size
4573 storeField(3, initFn); // reduce_init
4574 storeField(4, llvm::ConstantPointerNull::get(ptrTy)); // reduce_fini
4575 storeField(5, combFn); // reduce_comb
4576 storeField(6, llvm::ConstantInt::get(i32Ty, 0)); // flags
4577 }
4578
4579 // Emit the runtime call that registers the task reduction data.
4580 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4581 uint32_t srcLocSize;
4582 llvm::Constant *srcLocStr =
4583 ompBuilder->getOrCreateSrcLocStr(ompLoc, srcLocSize);
4584 llvm::Value *ident = ompBuilder->getOrCreateIdent(srcLocStr, srcLocSize);
4585 ompBuilder->updateToLocation(ompLoc);
4586 llvm::Value *gtid = ompBuilder->getOrCreateThreadID(ident);
4587 if (isModifier) {
4588 // __kmpc_taskred_modifier_init(loc, gtid, is_ws, num, &arr) opens a
4589 // task-reduction scope for the enclosing parallel/worksharing region.
4590 llvm::FunctionCallee modInit = ompBuilder->getOrCreateRuntimeFunction(
4591 *llvmModule, llvm::omp::OMPRTL___kmpc_taskred_modifier_init);
4592 return builder.CreateCall(modInit,
4593 {ident, gtid,
4594 builder.getInt32(isWorksharing ? 1 : 0),
4595 builder.getInt32(n), arrAlloca},
4596 ".taskred.desc");
4597 }
4598 // __kmpc_taskred_init(gtid, num, &arr).
4599 llvm::FunctionCallee taskredInit = ompBuilder->getOrCreateRuntimeFunction(
4600 *llvmModule, llvm::omp::OMPRTL___kmpc_taskred_init);
4601 return builder.CreateCall(taskredInit, {gtid, builder.getInt32(n), arrAlloca},
4602 ".taskred.desc");
4603}
4604
4605/// Emits `__kmpc_task_reduction_modifier_fini(loc, gtid, is_ws)` at the current
4606/// builder insertion point, closing the task-reduction scope opened by the
4607/// `task` reduction modifier on a parallel or worksharing construct.
4608static void
4609emitTaskReductionModifierFini(bool isWorksharing, llvm::IRBuilderBase &builder,
4610 LLVM::ModuleTranslation &moduleTranslation) {
4611 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4612 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4613 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4614 uint32_t srcLocSize;
4615 llvm::Constant *srcLocStr =
4616 ompBuilder->getOrCreateSrcLocStr(ompLoc, srcLocSize);
4617 llvm::Value *ident = ompBuilder->getOrCreateIdent(srcLocStr, srcLocSize);
4618 ompBuilder->updateToLocation(ompLoc);
4619 llvm::Value *gtid = ompBuilder->getOrCreateThreadID(ident);
4620 llvm::FunctionCallee fini = ompBuilder->getOrCreateRuntimeFunction(
4621 *llvmModule, llvm::omp::OMPRTL___kmpc_task_reduction_modifier_fini);
4622 builder.CreateCall(fini,
4623 {ident, gtid, builder.getInt32(isWorksharing ? 1 : 0)});
4624}
4625
4626/// Converts an OpenMP taskgroup construct into LLVM IR using OpenMPIRBuilder.
4627static LogicalResult
4628convertOmpTaskgroupOp(omp::TaskgroupOp tgOp, llvm::IRBuilderBase &builder,
4629 LLVM::ModuleTranslation &moduleTranslation) {
4630 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
4631 if (failed(checkImplementationStatus(*tgOp)))
4632 return failure();
4633
4634 // Resolve and validate task_reduction declarations up front. We only handle
4635 // declare_reduction ops shaped like a non-byref scalar reduction in this
4636 // first cut; richer shapes (two-argument initializer, cleanup region,
4637 // missing combiner) require additional infrastructure.
4639 if (auto syms = tgOp.getTaskReductionSyms()) {
4640 redDecls.reserve(syms->size());
4641 for (auto sym : syms->getAsRange<SymbolRefAttr>()) {
4643 tgOp, sym);
4644 if (!decl)
4645 return tgOp.emitError()
4646 << "failed to resolve task_reduction declare_reduction symbol "
4647 << sym.getRootReference() << " in omp.taskgroup";
4648 if (decl.getInitializerRegion().front().getNumArguments() != 1)
4649 return tgOp.emitError("not yet implemented: task_reduction with "
4650 "two-argument initializer in omp.taskgroup");
4651 if (!decl.getCleanupRegion().empty())
4652 return tgOp.emitError("not yet implemented: task_reduction with "
4653 "cleanup region in omp.taskgroup");
4654 if (decl.getReductionRegion().empty())
4655 return tgOp.emitError("task_reduction declare_reduction is missing a "
4656 "combiner region");
4657 redDecls.push_back(decl);
4658 }
4659 }
4660
4661 auto bodyCB =
4662 [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
4663 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
4664 builder.restoreIP(codegenIP);
4665
4666 if (!redDecls.empty()) {
4668 origPtrs.reserve(redDecls.size());
4669 for (Value v : tgOp.getTaskReductionVars())
4670 origPtrs.push_back(moduleTranslation.lookupValue(v));
4671 if (!emitTaskReductionInitCall(redDecls, origPtrs, "__omp_taskred_",
4672 builder, allocaIP, moduleTranslation))
4673 return llvm::createStringError(
4674 llvm::inconvertibleErrorCode(),
4675 "failed to emit task_reduction initialization for omp.taskgroup");
4676 }
4677
4678 // Inside the taskgroup body, each task_reduction block argument refers to
4679 // the same shared/original storage that the runtime now knows about via
4680 // the descriptor array. Inner tasks that declare in_reduction look up
4681 // per-task private copies through the runtime; the taskgroup body itself
4682 // uses the original variable.
4683 for (auto [i, blockArg] :
4684 llvm::enumerate(tgOp.getRegion().getArguments())) {
4685 llvm::Value *orig =
4686 moduleTranslation.lookupValue(tgOp.getTaskReductionVars()[i]);
4687 moduleTranslation.mapValue(blockArg, orig);
4688 }
4689
4690 return convertOmpOpRegions(tgOp.getRegion(), "omp.taskgroup.region",
4691 builder, moduleTranslation)
4692 .takeError();
4693 };
4694
4696 InsertPointTy allocaIP =
4697 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
4698 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4699 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
4700 moduleTranslation.getOpenMPBuilder()->createTaskgroup(
4701 ompLoc, allocaIP, deallocBlocks, bodyCB);
4702
4703 if (failed(handleError(afterIP, *tgOp)))
4704 return failure();
4705
4706 builder.restoreIP(*afterIP);
4707 return success();
4708}
4709
4710static LogicalResult
4711convertOmpInteropInitOp(omp::InteropInitOp initOp, llvm::IRBuilderBase &builder,
4712 LLVM::ModuleTranslation &moduleTranslation) {
4713 if (!initOp.getDependVars().empty() || initOp.getDependKinds() ||
4714 !initOp.getDependIterated().empty() || initOp.getDependIteratedKinds())
4715 return initOp.emitError()
4716 << "not yet implemented: Unhandled clause depend in "
4717 << omp::InteropInitOp::getOperationName() << " operation";
4718
4719 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4720 llvm::Value *interopVar =
4721 moduleTranslation.lookupValue(initOp.getInteropVar());
4722 llvm::Value *device = initOp.getDevice()
4723 ? moduleTranslation.lookupValue(initOp.getDevice())
4724 : nullptr;
4725
4726 // TODO: Handle depend clauses when supported.
4727 llvm::Value *numDeps = llvm::ConstantInt::get(builder.getInt32Ty(), 0);
4728 llvm::Value *depArray = llvm::ConstantPointerNull::get(builder.getPtrTy());
4729 bool hasNowait = initOp.getNowait();
4730
4731 // A single `init` clause may list both `target` and `targetsync`, but the
4732 // runtime init call takes a single interop-type. Collapse the set to one
4733 // value, matching Clang: if `target` is present use Target, otherwise
4734 // TargetSync. The offload runtime object model supports only one type per
4735 // object; representing both would require a runtime change.
4736 bool hasTarget = false, hasTargetSync = false;
4737 for (mlir::Attribute typeAttr : initOp.getInteropTypes()) {
4738 switch (cast<omp::InteropTypeAttr>(typeAttr).getValue()) {
4739 case omp::InteropType::target:
4740 hasTarget = true;
4741 break;
4742 case omp::InteropType::targetsync:
4743 hasTargetSync = true;
4744 break;
4745 }
4746 }
4747 llvm::omp::OMPInteropType interopType =
4748 (!hasTarget && hasTargetSync) ? llvm::omp::OMPInteropType::TargetSync
4749 : llvm::omp::OMPInteropType::Target;
4750 ompBuilder->createOMPInteropInit(builder, interopVar, interopType, device,
4751 numDeps, depArray, hasNowait);
4752 return success();
4753}
4754
4755static LogicalResult
4756convertOmpInteropDestroyOp(omp::InteropDestroyOp destroyOp,
4757 llvm::IRBuilderBase &builder,
4758 LLVM::ModuleTranslation &moduleTranslation) {
4759 if (!destroyOp.getDependVars().empty() || destroyOp.getDependKinds() ||
4760 !destroyOp.getDependIterated().empty() ||
4761 destroyOp.getDependIteratedKinds())
4762 return destroyOp.emitError()
4763 << "not yet implemented: Unhandled clause depend in "
4764 << omp::InteropDestroyOp::getOperationName() << " operation";
4765
4766 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4767 llvm::Value *interopVar =
4768 moduleTranslation.lookupValue(destroyOp.getInteropVar());
4769 llvm::Value *device =
4770 destroyOp.getDevice()
4771 ? moduleTranslation.lookupValue(destroyOp.getDevice())
4772 : nullptr;
4773
4774 llvm::Value *numDeps = llvm::ConstantInt::get(builder.getInt32Ty(), 0);
4775 llvm::Value *depArray = llvm::ConstantPointerNull::get(builder.getPtrTy());
4776 bool hasNowait = destroyOp.getNowait();
4777
4778 ompBuilder->createOMPInteropDestroy(builder, interopVar, device, numDeps,
4779 depArray, hasNowait);
4780 return success();
4781}
4782
4783static LogicalResult
4784convertOmpInteropUseOp(omp::InteropUseOp useOp, llvm::IRBuilderBase &builder,
4785 LLVM::ModuleTranslation &moduleTranslation) {
4786 if (!useOp.getDependVars().empty() || useOp.getDependKinds() ||
4787 !useOp.getDependIterated().empty() || useOp.getDependIteratedKinds())
4788 return useOp.emitError()
4789 << "not yet implemented: Unhandled clause depend in "
4790 << omp::InteropUseOp::getOperationName() << " operation";
4791
4792 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4793 llvm::Value *interopVar =
4794 moduleTranslation.lookupValue(useOp.getInteropVar());
4795 llvm::Value *device = useOp.getDevice()
4796 ? moduleTranslation.lookupValue(useOp.getDevice())
4797 : nullptr;
4798
4799 llvm::Value *numDeps = llvm::ConstantInt::get(builder.getInt32Ty(), 0);
4800 llvm::Value *depArray = llvm::ConstantPointerNull::get(builder.getPtrTy());
4801 bool hasNowait = useOp.getNowait();
4802
4803 ompBuilder->createOMPInteropUse(builder, interopVar, device, numDeps,
4804 depArray, hasNowait);
4805 return success();
4806}
4807
4808static LogicalResult
4809convertOmpTaskwaitOp(omp::TaskwaitOp twOp, llvm::IRBuilderBase &builder,
4810 LLVM::ModuleTranslation &moduleTranslation) {
4811 if (failed(checkImplementationStatus(*twOp)))
4812 return failure();
4813
4814 llvm::OpenMPIRBuilder::DependenciesInfo dds;
4815 if (failed(buildDependData(
4816 twOp.getDependVars(), twOp.getDependKinds(), twOp.getDependIterated(),
4817 twOp.getDependIteratedKinds(), builder, moduleTranslation, dds))) {
4818 return failure();
4819 }
4820
4821 moduleTranslation.getOpenMPBuilder()->createTaskwait(builder, dds,
4822 twOp.getNowait());
4823 if (dds.DepArray) {
4824 builder.CreateFree(dds.DepArray);
4825 }
4826
4827 return success();
4828}
4829
4830/// Converts an OpenMP workshare loop into LLVM IR using OpenMPIRBuilder.
4831static LogicalResult
4832convertOmpWsloop(Operation &opInst, llvm::IRBuilderBase &builder,
4833 LLVM::ModuleTranslation &moduleTranslation) {
4834 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4835 auto wsloopOp = cast<omp::WsloopOp>(opInst);
4836 if (failed(checkImplementationStatus(opInst)))
4837 return failure();
4838
4839 auto loopOp = cast<omp::LoopNestOp>(wsloopOp.getWrappedLoop());
4840 llvm::ArrayRef<bool> isByRef = getIsByRef(wsloopOp.getReductionByref());
4841 assert(isByRef.size() == wsloopOp.getNumReductionVars());
4842
4843 // Static is the default.
4844 auto schedule =
4845 wsloopOp.getScheduleKind().value_or(omp::ClauseScheduleKind::Static);
4846
4847 // Find the loop configuration.
4848 llvm::Value *step = moduleTranslation.lookupValue(loopOp.getLoopSteps()[0]);
4849 llvm::Type *ivType = step->getType();
4850 llvm::Value *chunk = nullptr;
4851 if (wsloopOp.getScheduleChunk()) {
4852 llvm::Value *chunkVar =
4853 moduleTranslation.lookupValue(wsloopOp.getScheduleChunk());
4854 chunk = builder.CreateSExtOrTrunc(chunkVar, ivType);
4855 }
4856
4857 omp::DistributeOp distributeOp = nullptr;
4858 llvm::Value *distScheduleChunk = nullptr;
4859 bool hasDistSchedule = false;
4860 if (llvm::isa_and_present<omp::DistributeOp>(opInst.getParentOp())) {
4861 distributeOp = cast<omp::DistributeOp>(opInst.getParentOp());
4862 hasDistSchedule = distributeOp.getDistScheduleStatic();
4863 if (distributeOp.getDistScheduleChunkSize()) {
4864 llvm::Value *chunkVar = moduleTranslation.lookupValue(
4865 distributeOp.getDistScheduleChunkSize());
4866 distScheduleChunk = builder.CreateSExtOrTrunc(chunkVar, ivType);
4867 }
4868 }
4869
4870 PrivateVarsInfo privateVarsInfo(wsloopOp);
4871 privateVarsInfo.addLinearVars(wsloopOp);
4872
4874 collectReductionDecls(wsloopOp, reductionDecls);
4875
4876 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
4877 findAllocInsertPoints(builder, moduleTranslation);
4878
4879 llvm::BasicBlock *allocaBlock = allocaIP.getNodeParent();
4880 SmallVector<llvm::Value *> privateReductionVariables(
4881 wsloopOp.getNumReductionVars());
4882
4884 wsloopOp, builder, moduleTranslation, privateVarsInfo, allocaIP);
4885 if (handleError(afterAllocas, opInst).failed())
4886 return failure();
4887
4888 DenseMap<Value, llvm::Value *> reductionVariableMap;
4889
4890 MutableArrayRef<BlockArgument> reductionArgs =
4891 cast<omp::BlockArgOpenMPOpInterface>(opInst).getReductionBlockArgs();
4892
4893 SmallVector<DeferredStore> deferredStores;
4894
4895 if (failed(allocReductionVars(wsloopOp, reductionArgs, builder,
4896 moduleTranslation, allocaIP, reductionDecls,
4897 privateReductionVariables, reductionVariableMap,
4898 deferredStores, isByRef)))
4899 return failure();
4900
4901 if (handleError(initPrivateVars(builder, moduleTranslation, privateVarsInfo),
4902 opInst)
4903 .failed())
4904 return failure();
4905
4906 if (failed(completePrivateVars(
4907 wsloopOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
4908 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
4909 wsloopOp.getPrivateNeedsBarrier())))
4910 return failure();
4911
4912 assert(afterAllocas.get()->getSinglePredecessor());
4913 if (failed(initReductionVars(wsloopOp, reductionArgs, builder,
4914 moduleTranslation,
4915 afterAllocas.get()->getSinglePredecessor(),
4916 reductionDecls, privateReductionVariables,
4917 reductionVariableMap, isByRef, deferredStores)))
4918 return failure();
4919
4920 // For `reduction(task, ...)` open a task-reduction scope for the worksharing
4921 // loop. Participating explicit tasks accumulate into the per-thread private
4922 // copies, which the worksharing reduction then combines across threads.
4923 bool isTaskReductionMod =
4924 wsloopOp.getReductionMod() == omp::ReductionModifier::task &&
4925 wsloopOp.getNumReductionVars() > 0;
4926 if (isTaskReductionMod &&
4927 !emitTaskReductionInitCall(reductionDecls, privateReductionVariables,
4928 "__omp_taskred_mod_", builder, allocaIP,
4929 moduleTranslation, /*isModifier=*/true,
4930 /*isWorksharing=*/true))
4931 return wsloopOp.emitError(
4932 "failed to emit task reduction modifier initialization");
4933
4934 // TODO: Handle doacross loops when the ordered clause has a parameter.
4935 bool isOrdered = wsloopOp.getOrdered().has_value();
4936 std::optional<omp::ScheduleModifier> scheduleMod = wsloopOp.getScheduleMod();
4937 bool isSimd = wsloopOp.getScheduleSimd();
4938 bool loopNeedsBarrier = !wsloopOp.getNowait();
4939
4940 // The only legal way for the direct parent to be omp.distribute is that this
4941 // represents 'distribute parallel do'. Otherwise, this is a regular
4942 // worksharing loop.
4943 llvm::omp::WorksharingLoopType workshareLoopType =
4944 llvm::isa_and_present<omp::DistributeOp>(opInst.getParentOp())
4945 ? llvm::omp::WorksharingLoopType::DistributeForStaticLoop
4946 : llvm::omp::WorksharingLoopType::ForStaticLoop;
4947
4948 SmallVector<llvm::UncondBrInst *> cancelTerminators;
4949 pushCancelFinalizationCB(cancelTerminators, builder, *ompBuilder, wsloopOp,
4950 llvm::omp::Directive::OMPD_for);
4951
4952 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4953
4954 // Initialize linear variables and linear step
4955 LinearClauseProcessor linearClauseProcessor;
4956
4957 if (!wsloopOp.getLinearVars().empty()) {
4958 auto linearVarTypes = wsloopOp.getLinearVarTypes().value();
4959 for (mlir::Attribute linearVarType : linearVarTypes)
4960 linearClauseProcessor.registerType(moduleTranslation, linearVarType);
4961
4962 for (auto [idx, linearVar] : llvm::enumerate(wsloopOp.getLinearVars())) {
4963 size_t privIdx = privateVarsInfo.numPrivateVars + idx;
4964 linearClauseProcessor.createLinearVar(
4965 builder, allocaIP, privateVarsInfo.privatizers[privIdx],
4966 moduleTranslation.lookupValue(linearVar),
4967 privateVarsInfo.llvmVars[privIdx], idx);
4968 }
4969 for (mlir::Value linearStep : wsloopOp.getLinearStepVars())
4970 linearClauseProcessor.initLinearStep(moduleTranslation, linearStep);
4971 }
4972
4974 wsloopOp.getRegion(), "omp.wsloop.region", builder, moduleTranslation);
4975
4976 if (failed(handleError(regionBlock, opInst)))
4977 return failure();
4978
4979 llvm::CanonicalLoopInfo *loopInfo = findCurrentLoopInfo(moduleTranslation);
4980
4981 // Emit Initialization and Update IR for linear variables
4982 if (!wsloopOp.getLinearVars().empty()) {
4983 if (failed(handleError(
4984 linearClauseProcessor.initLinearVar(builder, moduleTranslation,
4985 loopInfo->getPreheader()),
4986 opInst)))
4987 return failure();
4988 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterBarrierIP =
4989 moduleTranslation.getOpenMPBuilder()->createBarrier(
4990 builder, llvm::omp::OMPD_barrier);
4991 if (failed(handleError(afterBarrierIP, *loopOp)))
4992 return failure();
4993 builder.restoreIP(*afterBarrierIP);
4994 if (failed(handleError(linearClauseProcessor.updateLinearVar(
4995 builder, moduleTranslation, loopInfo->getBody(),
4996 loopInfo->getIndVar()),
4997 opInst)))
4998 return failure();
4999 linearClauseProcessor.splitLinearFiniBB(builder, loopInfo->getExit());
5000 }
5001
5002 builder.SetInsertPoint((*regionBlock)->begin());
5003
5004 // Check if we can generate no-loop kernel
5005 bool noLoopMode = false;
5006 omp::TargetOp targetOp = wsloopOp->getParentOfType<mlir::omp::TargetOp>();
5007 if (targetOp &&
5008 targetOp.getKernelType() == omp::TargetExecMode::spmd_no_loop) {
5009 Operation *targetCapturedOp =
5010 cast<omp::ComposableOpInterface>(*targetOp).findCapturedOp();
5011 // We need this check because, without it, noLoopMode would be set to true
5012 // for every omp.wsloop nested inside a no-loop SPMD target region, even if
5013 // that loop is not the top-level SPMD one.
5014 if (loopOp == targetCapturedOp)
5015 noLoopMode = true;
5016 }
5017
5018 // Nested regions can move the instruction referenced by allocaIP.
5019 allocaIP = allocaBlock->getFirstInsertionPt();
5020 llvm::OpenMPIRBuilder::InsertPointOrErrorTy wsloopIP =
5021 ompBuilder->applyWorkshareLoop(
5022 ompLoc.DL, loopInfo, allocaIP, loopNeedsBarrier,
5023 convertToScheduleKind(schedule), chunk, isSimd,
5024 scheduleMod == omp::ScheduleModifier::monotonic,
5025 scheduleMod == omp::ScheduleModifier::nonmonotonic, isOrdered,
5026 workshareLoopType, noLoopMode, hasDistSchedule, distScheduleChunk,
5027 !wsloopOp.getLinearVars().empty());
5028
5029 if (failed(handleError(wsloopIP, opInst)))
5030 return failure();
5031
5032 // Save the continuation block before linear var finalization, which may
5033 // invalidate the wsloopIP iterator.
5034 llvm::BasicBlock *wsloopContinuationBB = wsloopIP->getNodeParent();
5035
5036 // Emit finalization and in-place rewrites for linear vars.
5037 if (!wsloopOp.getLinearVars().empty()) {
5038 llvm::OpenMPIRBuilder::InsertPointTy oldIP = builder.saveIP();
5039 assert(loopInfo->getLastIter() &&
5040 "`lastiter` in CanonicalLoopInfo is nullptr");
5041 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterBarrierIP =
5042 linearClauseProcessor.finalizeLinearVar(builder, moduleTranslation,
5043 loopInfo->getLastIter());
5044 if (failed(handleError(afterBarrierIP, *loopOp)))
5045 return failure();
5046
5047 builder.restoreIP(oldIP);
5048 }
5049
5050 // Set the correct branch target for task cancellation
5051 popCancelFinalizationCB(cancelTerminators, *ompBuilder, wsloopContinuationBB);
5052
5053 // Close the task-reduction scope before the worksharing reduction combine.
5054 if (isTaskReductionMod)
5055 emitTaskReductionModifierFini(/*isWorksharing=*/true, builder,
5056 moduleTranslation);
5057
5058 // Process the reductions if required.
5059 if (failed(createReductionsAndCleanup(
5060 wsloopOp, builder, moduleTranslation, allocaIP, reductionDecls,
5061 privateReductionVariables, isByRef, wsloopOp.getNowait(),
5062 /*isTeamsReduction=*/false)))
5063 return failure();
5064
5065 return cleanupPrivateVars(wsloopOp, builder, moduleTranslation,
5066 wsloopOp.getLoc(), privateVarsInfo);
5067}
5068
5069/// Converts the OpenMP parallel operation to LLVM IR.
5070static LogicalResult
5071convertOmpParallel(omp::ParallelOp opInst, llvm::IRBuilderBase &builder,
5072 LLVM::ModuleTranslation &moduleTranslation) {
5073 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
5074 ArrayRef<bool> isByRef = getIsByRef(opInst.getReductionByref());
5075 assert(isByRef.size() == opInst.getNumReductionVars());
5076 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5077 bool isCancellable = constructIsCancellable(opInst);
5078
5079 if (failed(checkImplementationStatus(*opInst)))
5080 return failure();
5081
5082 PrivateVarsInfo privateVarsInfo(opInst);
5083 if (failed(convertAllocatorVars(*opInst, opInst.getAllocatorVars(), builder,
5084 moduleTranslation, privateVarsInfo)))
5085 return failure();
5086
5087 // Collect reduction declarations
5089 collectReductionDecls(opInst, reductionDecls);
5090 SmallVector<llvm::Value *> privateReductionVariables(
5091 opInst.getNumReductionVars());
5092 SmallVector<DeferredStore> deferredStores;
5093 // Only open a task-reduction scope when the `task` modifier is present and
5094 // there are reduction variables to combine; otherwise the matching fini in
5095 // the reduction-combine path (guarded by getNumReductionVars() > 0) would be
5096 // skipped, leaving the modifier init unbalanced.
5097 bool isTaskReductionMod =
5098 opInst.getReductionMod() == omp::ReductionModifier::task &&
5099 opInst.getNumReductionVars() > 0;
5100
5101 auto bodyGenCB =
5102 [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
5103 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
5105 opInst, builder, moduleTranslation, privateVarsInfo, allocaIP);
5106 if (handleError(afterAllocas, *opInst).failed())
5107 return llvm::make_error<PreviouslyReportedError>();
5108
5109 // Allocate reduction vars
5110 DenseMap<Value, llvm::Value *> reductionVariableMap;
5111
5112 MutableArrayRef<BlockArgument> reductionArgs =
5113 cast<omp::BlockArgOpenMPOpInterface>(*opInst).getReductionBlockArgs();
5114
5115 allocaIP = allocaIP.getNodeParent()->getTerminator()->getIterator();
5116
5117 if (failed(allocReductionVars(
5118 opInst, reductionArgs, builder, moduleTranslation, allocaIP,
5119 reductionDecls, privateReductionVariables, reductionVariableMap,
5120 deferredStores, isByRef)))
5121 return llvm::make_error<PreviouslyReportedError>();
5122
5123 assert(afterAllocas.get()->getSinglePredecessor());
5124 builder.restoreIP(codeGenIP);
5125
5126 if (handleError(
5127 initPrivateVars(builder, moduleTranslation, privateVarsInfo),
5128 *opInst)
5129 .failed())
5130 return llvm::make_error<PreviouslyReportedError>();
5131
5132 if (failed(completePrivateVars(
5133 opInst, builder, moduleTranslation, privateVarsInfo.mlirVars,
5134 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
5135 opInst.getPrivateNeedsBarrier())))
5136 return llvm::make_error<PreviouslyReportedError>();
5137
5138 if (failed(
5139 initReductionVars(opInst, reductionArgs, builder, moduleTranslation,
5140 afterAllocas.get()->getSinglePredecessor(),
5141 reductionDecls, privateReductionVariables,
5142 reductionVariableMap, isByRef, deferredStores)))
5143 return llvm::make_error<PreviouslyReportedError>();
5144
5145 // For `reduction(task, ...)` open a task-reduction scope so participating
5146 // explicit tasks accumulate into the per-thread private copies; the
5147 // parallel reduction then combines those copies across the team.
5148 if (isTaskReductionMod &&
5149 !emitTaskReductionInitCall(reductionDecls, privateReductionVariables,
5150 "__omp_taskred_mod_", builder, allocaIP,
5151 moduleTranslation, /*isModifier=*/true,
5152 /*isWorksharing=*/false))
5153 return llvm::createStringError(
5154 "failed to emit task reduction modifier initialization");
5155
5156 // Save the alloca insertion point on ModuleTranslation stack for use in
5157 // nested regions.
5159 moduleTranslation, allocaIP.getNodeParent(), deallocBlocks);
5160
5161 // ParallelOp has only one region associated with it.
5163 opInst.getRegion(), "omp.par.region", builder, moduleTranslation);
5164 if (!regionBlock)
5165 return regionBlock.takeError();
5166
5167 // Process the reductions if required.
5168 if (opInst.getNumReductionVars() > 0) {
5169 // Collect reduction info
5171 SmallVector<OwningAtomicReductionGen> owningAtomicReductionGens;
5173 owningReductionGenRefDataPtrGens;
5175 collectReductionInfo(opInst, builder, moduleTranslation, reductionDecls,
5176 owningReductionGens, owningAtomicReductionGens,
5177 owningReductionGenRefDataPtrGens,
5178 privateReductionVariables, reductionInfos, isByRef);
5179
5180 // Move to region cont block
5181 builder.SetInsertPoint((*regionBlock)->getTerminator());
5182
5183 // Close the task-reduction scope before the per-thread reduction
5184 // contributions are combined across the team.
5185 if (isTaskReductionMod)
5186 emitTaskReductionModifierFini(/*isWorksharing=*/false, builder,
5187 moduleTranslation);
5188
5189 // Generate reductions from info
5190 llvm::UnreachableInst *tempTerminator = builder.CreateUnreachable();
5191 builder.SetInsertPoint(tempTerminator);
5192
5193 llvm::OpenMPIRBuilder::InsertPointOrErrorTy contInsertPoint =
5194 ompBuilder->createReductions(builder, allocaIP, reductionInfos,
5195 isByRef,
5196 /*IsNoWait=*/false,
5197 /*IsTeamsReduction=*/false);
5198 if (!contInsertPoint)
5199 return contInsertPoint.takeError();
5200
5201 if (!contInsertPoint->isValid())
5202 return llvm::make_error<PreviouslyReportedError>();
5203
5204 tempTerminator->eraseFromParent();
5205 builder.restoreIP(*contInsertPoint);
5206 }
5207
5208 return llvm::Error::success();
5209 };
5210
5211 auto privCB = [](InsertPointTy allocaIP, InsertPointTy codeGenIP,
5212 llvm::Value &, llvm::Value &val, llvm::Value *&replVal) {
5213 // tell OpenMPIRBuilder not to do anything. We handled Privatisation in
5214 // bodyGenCB.
5215 replVal = &val;
5216 return codeGenIP;
5217 };
5218
5219 // TODO: Perform finalization actions for variables. This has to be
5220 // called for variables which have destructors/finalizers.
5221 auto finiCB = [&](InsertPointTy codeGenIP) -> llvm::Error {
5222 InsertPointTy oldIP = builder.saveIP();
5223 builder.restoreIP(codeGenIP);
5224
5225 // if the reduction has a cleanup region, inline it here to finalize the
5226 // reduction variables
5227 SmallVector<Region *> reductionCleanupRegions;
5228 llvm::transform(reductionDecls, std::back_inserter(reductionCleanupRegions),
5229 [](omp::DeclareReductionOp reductionDecl) {
5230 return &reductionDecl.getCleanupRegion();
5231 });
5232 if (failed(inlineOmpRegionCleanup(
5233 reductionCleanupRegions, privateReductionVariables,
5234 moduleTranslation, builder, "omp.reduction.cleanup")))
5235 return llvm::createStringError(
5236 "failed to inline `cleanup` region of `omp.declare_reduction`");
5237
5238 if (failed(cleanupPrivateVars(opInst, builder, moduleTranslation,
5239 opInst.getLoc(), privateVarsInfo)))
5240 return llvm::make_error<PreviouslyReportedError>();
5241
5242 // If we could be performing cancellation, add the cancellation barrier on
5243 // the way out of the outlined region.
5244 if (isCancellable) {
5245 auto IPOrErr = ompBuilder->createBarrier(
5246 llvm::OpenMPIRBuilder::LocationDescription(builder),
5247 llvm::omp::Directive::OMPD_unknown,
5248 /* ForceSimpleCall */ false,
5249 /* CheckCancelFlag */ false);
5250 if (!IPOrErr)
5251 return IPOrErr.takeError();
5252 }
5253
5254 builder.restoreIP(oldIP);
5255 return llvm::Error::success();
5256 };
5257
5258 llvm::Value *ifCond = nullptr;
5259 if (auto ifVar = opInst.getIfExpr())
5260 ifCond = moduleTranslation.lookupValue(ifVar);
5261 llvm::Value *numThreads = nullptr;
5262 if (!opInst.getNumThreadsVars().empty())
5263 numThreads = moduleTranslation.lookupValue(opInst.getNumThreads(0));
5264 auto pbKind = llvm::omp::OMP_PROC_BIND_default;
5265 if (auto bind = opInst.getProcBindKind())
5266 pbKind = getProcBindKind(*bind);
5267
5269 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
5270 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
5271 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5272
5273 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
5274 ompBuilder->createParallel(ompLoc, allocaIP, deallocBlocks, bodyGenCB,
5275 privCB, finiCB, ifCond, numThreads, pbKind,
5276 isCancellable);
5277
5278 if (failed(handleError(afterIP, *opInst)))
5279 return failure();
5280
5281 builder.restoreIP(*afterIP);
5282 return success();
5283}
5284
5285/// Convert Order attribute to llvm::omp::OrderKind.
5286static llvm::omp::OrderKind
5287convertOrderKind(std::optional<omp::ClauseOrderKind> o) {
5288 if (!o)
5289 return llvm::omp::OrderKind::OMP_ORDER_unknown;
5290 switch (*o) {
5291 case omp::ClauseOrderKind::Concurrent:
5292 return llvm::omp::OrderKind::OMP_ORDER_concurrent;
5293 }
5294 llvm_unreachable("Unknown ClauseOrderKind kind");
5295}
5296
5297/// Converts an OpenMP simd loop into LLVM IR using OpenMPIRBuilder.
5298static LogicalResult
5299convertOmpSimd(Operation &opInst, llvm::IRBuilderBase &builder,
5300 LLVM::ModuleTranslation &moduleTranslation) {
5301 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5302 auto simdOp = cast<omp::SimdOp>(opInst);
5303
5304 if (failed(checkImplementationStatus(opInst)))
5305 return failure();
5306
5307 PrivateVarsInfo privateVarsInfo(simdOp);
5308 privateVarsInfo.addLinearVars(simdOp);
5309
5310 MutableArrayRef<BlockArgument> reductionArgs =
5311 cast<omp::BlockArgOpenMPOpInterface>(opInst).getReductionBlockArgs();
5312 DenseMap<Value, llvm::Value *> reductionVariableMap;
5313 SmallVector<llvm::Value *> privateReductionVariables(
5314 simdOp.getNumReductionVars());
5315 SmallVector<DeferredStore> deferredStores;
5317 collectReductionDecls(simdOp, reductionDecls);
5318 llvm::ArrayRef<bool> isByRef = getIsByRef(simdOp.getReductionByref());
5319 assert(isByRef.size() == simdOp.getNumReductionVars());
5320
5321 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
5322 findAllocInsertPoints(builder, moduleTranslation);
5323
5325 simdOp, builder, moduleTranslation, privateVarsInfo, allocaIP);
5326 if (handleError(afterAllocas, opInst).failed())
5327 return failure();
5328
5329 if (failed(allocReductionVars(simdOp, reductionArgs, builder,
5330 moduleTranslation, allocaIP, reductionDecls,
5331 privateReductionVariables, reductionVariableMap,
5332 deferredStores, isByRef)))
5333 return failure();
5334
5335 if (handleError(initPrivateVars(builder, moduleTranslation, privateVarsInfo),
5336 opInst)
5337 .failed())
5338 return failure();
5339
5340 // No call to completePrivateVars because SIMD does not allow FIRSTPRIVATE and
5341 // is not a worksharing construct, so a privatization barrier here could
5342 // deadlock.
5343
5344 // Initialize linear variables and linear step
5345 LinearClauseProcessor linearClauseProcessor;
5346 if (linearClauseProcessor.initLinearIV(simdOp).failed())
5347 return failure();
5348
5349 if (!simdOp.getLinearVars().empty()) {
5350 auto linearVarTypes = simdOp.getLinearVarTypes().value();
5351 for (mlir::Attribute linearVarType : linearVarTypes)
5352 linearClauseProcessor.registerType(moduleTranslation, linearVarType);
5353 for (auto [idx, linearVar] : llvm::enumerate(simdOp.getLinearVars())) {
5354 size_t privIdx = privateVarsInfo.numPrivateVars + idx;
5355 linearClauseProcessor.createLinearVar(
5356 builder, allocaIP, privateVarsInfo.privatizers[privIdx],
5357 moduleTranslation.lookupValue(linearVar),
5358 privateVarsInfo.llvmVars[privIdx], idx);
5359 }
5360 for (mlir::Value linearStep : simdOp.getLinearStepVars())
5361 linearClauseProcessor.initLinearStep(moduleTranslation, linearStep);
5362 }
5363
5364 assert(afterAllocas.get()->getSinglePredecessor());
5365 if (failed(initReductionVars(simdOp, reductionArgs, builder,
5366 moduleTranslation,
5367 afterAllocas.get()->getSinglePredecessor(),
5368 reductionDecls, privateReductionVariables,
5369 reductionVariableMap, isByRef, deferredStores)))
5370 return failure();
5371
5372 llvm::ConstantInt *simdlen = nullptr;
5373 if (std::optional<uint64_t> simdlenVar = simdOp.getSimdlen())
5374 simdlen = builder.getInt64(simdlenVar.value());
5375
5376 llvm::ConstantInt *safelen = nullptr;
5377 if (std::optional<uint64_t> safelenVar = simdOp.getSafelen())
5378 safelen = builder.getInt64(safelenVar.value());
5379
5381 llvm::omp::OrderKind order = convertOrderKind(simdOp.getOrder());
5382
5383 llvm::BasicBlock *sourceBlock = builder.GetInsertBlock();
5384 std::optional<ArrayAttr> alignmentValues = simdOp.getAlignments();
5385 mlir::OperandRange operands = simdOp.getAlignedVars();
5386 for (size_t i = 0; i < operands.size(); ++i) {
5387 llvm::Value *alignment = nullptr;
5388 llvm::Value *llvmVal = moduleTranslation.lookupValue(operands[i]);
5389 llvm::Type *ty = llvmVal->getType();
5390
5391 auto intAttr = cast<IntegerAttr>((*alignmentValues)[i]);
5392 alignment = builder.getInt64(intAttr.getInt());
5393 assert(ty->isPointerTy() && "Invalid type for aligned variable");
5394 assert(alignment && "Invalid alignment value");
5395
5396 // Check if the alignment value is not a power of 2. If so, skip emitting
5397 // alignment.
5398 if (!intAttr.getValue().isPowerOf2())
5399 continue;
5400
5401 auto curInsert = builder.saveIP();
5402 builder.SetInsertPoint(sourceBlock);
5403 llvmVal = builder.CreateLoad(ty, llvmVal);
5404 builder.restoreIP(curInsert);
5405 alignedVars[llvmVal] = alignment;
5406 }
5407
5409 simdOp.getRegion(), "omp.simd.region", builder, moduleTranslation);
5410
5411 if (failed(handleError(regionBlock, opInst)))
5412 return failure();
5413
5414 llvm::CanonicalLoopInfo *loopInfo = findCurrentLoopInfo(moduleTranslation);
5415 // Emit Initialization for linear variables
5416 if (simdOp.getLinearVars().size()) {
5417 if (failed(handleError(
5418 linearClauseProcessor.initLinearVar(builder, moduleTranslation,
5419 loopInfo->getPreheader()),
5420 opInst)))
5421 return failure();
5422
5423 if (failed(handleError(linearClauseProcessor.updateLinearVar(
5424 builder, moduleTranslation, loopInfo->getBody(),
5425 loopInfo->getIndVar()),
5426 opInst)))
5427 return failure();
5428 }
5429 builder.SetInsertPoint((*regionBlock)->begin());
5430
5431 ompBuilder->applySimd(loopInfo, alignedVars,
5432 simdOp.getIfExpr()
5433 ? moduleTranslation.lookupValue(simdOp.getIfExpr())
5434 : nullptr,
5435 order, simdlen, safelen);
5436
5437 if (failed(handleError(
5438 linearClauseProcessor.updateLinearIV(builder, moduleTranslation),
5439 opInst)))
5440 return failure();
5441 if (failed(handleError(linearClauseProcessor.emitStoresForLinearVar(
5442 builder, moduleTranslation),
5443 opInst)))
5444 return failure();
5445
5446 // We now need to reduce the per-simd-lane reduction variable into the
5447 // original variable. This works a bit differently to other reductions (e.g.
5448 // wsloop) because we don't need to call into the OpenMP runtime to handle
5449 // threads: everything happened in this one thread.
5450 for (auto [i, tuple] : llvm::enumerate(
5451 llvm::zip(reductionDecls, isByRef, simdOp.getReductionVars(),
5452 privateReductionVariables))) {
5453 auto [decl, byRef, reductionVar, privateReductionVar] = tuple;
5454
5455 OwningReductionGen gen = makeReductionGen(decl, builder, moduleTranslation);
5456 llvm::Value *originalVariable = moduleTranslation.lookupValue(reductionVar);
5457 llvm::Type *reductionType = moduleTranslation.convertType(decl.getType());
5458
5459 // We have one less load for by-ref case because that load is now inside of
5460 // the reduction region.
5461 llvm::Value *redValue = originalVariable;
5462 if (!byRef)
5463 redValue =
5464 builder.CreateLoad(reductionType, redValue, "red.value." + Twine(i));
5465 llvm::Value *privateRedValue = builder.CreateLoad(
5466 reductionType, privateReductionVar, "red.private.value." + Twine(i));
5467 llvm::Value *reduced;
5468
5469 auto res = gen(builder.saveIP(), redValue, privateRedValue, reduced);
5470 if (failed(handleError(res, opInst)))
5471 return failure();
5472 builder.restoreIP(res.get());
5473
5474 // For by-ref case, the store is inside of the reduction region.
5475 if (!byRef)
5476 builder.CreateStore(reduced, originalVariable);
5477 }
5478
5479 // After the construct, deallocate private reduction variables.
5480 SmallVector<Region *> reductionRegions;
5481 llvm::transform(reductionDecls, std::back_inserter(reductionRegions),
5482 [](omp::DeclareReductionOp reductionDecl) {
5483 return &reductionDecl.getCleanupRegion();
5484 });
5485 if (failed(inlineOmpRegionCleanup(reductionRegions, privateReductionVariables,
5486 moduleTranslation, builder,
5487 "omp.reduction.cleanup")))
5488 return failure();
5489
5490 return cleanupPrivateVars(simdOp, builder, moduleTranslation, simdOp.getLoc(),
5491 privateVarsInfo);
5492}
5493
5494/// Converts an OpenMP loop nest into LLVM IR using OpenMPIRBuilder.
5495static LogicalResult
5496convertOmpLoopNest(Operation &opInst, llvm::IRBuilderBase &builder,
5497 LLVM::ModuleTranslation &moduleTranslation) {
5498 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5499 auto loopOp = cast<omp::LoopNestOp>(opInst);
5500
5501 if (failed(checkImplementationStatus(opInst)))
5502 return failure();
5503
5504 // Set up the source location value for OpenMP runtime.
5505 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5506
5507 // Generator of the canonical loop body.
5510 auto bodyGen = [&](llvm::OpenMPIRBuilder::InsertPointTy ip,
5511 llvm::Value *iv) -> llvm::Error {
5512 // Make sure further conversions know about the induction variable.
5513 moduleTranslation.mapValue(
5514 loopOp.getRegion().front().getArgument(loopInfos.size()), iv);
5515
5516 // Capture the body insertion point for use in nested loops. BodyIP of the
5517 // CanonicalLoopInfo always points to the beginning of the entry block of
5518 // the body.
5519 bodyInsertPoints.push_back(ip);
5520
5521 if (loopInfos.size() != loopOp.getNumLoops() - 1)
5522 return llvm::Error::success();
5523
5524 // Convert the body of the loop.
5525 builder.restoreIP(ip);
5527 loopOp.getRegion(), "omp.loop_nest.region", builder, moduleTranslation);
5528 if (!regionBlock)
5529 return regionBlock.takeError();
5530
5531 builder.SetInsertPoint((*regionBlock)->begin());
5532 return llvm::Error::success();
5533 };
5534
5535 // Delegate actual loop construction to the OpenMP IRBuilder.
5536 // TODO: this currently assumes omp.loop_nest is semantically similar to SCF
5537 // loop, i.e. it has a positive step, uses signed integer semantics.
5538 // Reconsider this code when the nested loop operation clearly supports more
5539 // cases.
5540 for (unsigned i = 0, e = loopOp.getNumLoops(); i < e; ++i) {
5541 llvm::Value *lowerBound =
5542 moduleTranslation.lookupValue(loopOp.getLoopLowerBounds()[i]);
5543 llvm::Value *upperBound =
5544 moduleTranslation.lookupValue(loopOp.getLoopUpperBounds()[i]);
5545 llvm::Value *step = moduleTranslation.lookupValue(loopOp.getLoopSteps()[i]);
5546
5547 // Make sure loop trip count are emitted in the preheader of the outermost
5548 // loop at the latest so that they are all available for the new collapsed
5549 // loop will be created below.
5550 llvm::OpenMPIRBuilder::LocationDescription loc = ompLoc;
5551 llvm::OpenMPIRBuilder::InsertPointTy computeIP = ompLoc.IP;
5552 if (i != 0) {
5553 loc = llvm::OpenMPIRBuilder::LocationDescription(bodyInsertPoints.back(),
5554 ompLoc.DL);
5555 computeIP = loopInfos.front()->getPreheaderIP();
5556 }
5557
5559 ompBuilder->createCanonicalLoop(
5560 loc, bodyGen, lowerBound, upperBound, step,
5561 /*IsSigned=*/true, loopOp.getLoopInclusive(), computeIP);
5562
5563 if (failed(handleError(loopResult, *loopOp)))
5564 return failure();
5565
5566 loopInfos.push_back(*loopResult);
5567 }
5568
5569 llvm::OpenMPIRBuilder::InsertPointTy afterIP =
5570 loopInfos.front()->getAfterIP();
5571
5572 // Do tiling.
5573 if (const auto &tiles = loopOp.getTileSizes()) {
5574 llvm::Type *ivType = loopInfos.front()->getIndVarType();
5576
5577 for (auto tile : tiles.value()) {
5578 llvm::Value *tileVal = llvm::ConstantInt::get(ivType, tile);
5579 tileSizes.push_back(tileVal);
5580 }
5581
5582 std::vector<llvm::CanonicalLoopInfo *> newLoops =
5583 ompBuilder->tileLoops(ompLoc.DL, loopInfos, tileSizes);
5584
5585 // Update afterIP to get the correct insertion point after
5586 // tiling.
5587 llvm::BasicBlock *afterBB = newLoops.front()->getAfter();
5588 llvm::BasicBlock *afterAfterBB = afterBB->getSingleSuccessor();
5589 afterIP = afterAfterBB->begin();
5590
5591 // Update the loop infos.
5592 loopInfos.clear();
5593 for (const auto &newLoop : newLoops)
5594 loopInfos.push_back(newLoop);
5595 } // Tiling done.
5596
5597 // Do collapse.
5598 const auto &numCollapse = loopOp.getCollapseNumLoops();
5600 loopInfos.begin(), loopInfos.begin() + (numCollapse));
5601
5602 auto newTopLoopInfo =
5603 ompBuilder->collapseLoops(ompLoc.DL, collapseLoopInfos, {});
5604
5605 assert(newTopLoopInfo && "New top loop information is missing");
5606 moduleTranslation.stackWalk<OpenMPLoopInfoStackFrame>(
5607 [&](OpenMPLoopInfoStackFrame &frame) {
5608 frame.loopInfo = newTopLoopInfo;
5609 return WalkResult::interrupt();
5610 });
5611
5612 // Continue building IR after the loop. Note that the LoopInfo returned by
5613 // `collapseLoops` points inside the outermost loop and is intended for
5614 // potential further loop transformations. Use the insertion point stored
5615 // before collapsing loops instead.
5616 builder.restoreIP(afterIP);
5617 return success();
5618}
5619
5620/// Convert an omp.canonical_loop to LLVM-IR
5621static LogicalResult
5622convertOmpCanonicalLoopOp(omp::CanonicalLoopOp op, llvm::IRBuilderBase &builder,
5623 LLVM::ModuleTranslation &moduleTranslation) {
5624 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5625
5626 llvm::OpenMPIRBuilder::LocationDescription loopLoc(builder);
5627 Value loopIV = op.getInductionVar();
5628 Value loopTC = op.getTripCount();
5629
5630 llvm::Value *llvmTC = moduleTranslation.lookupValue(loopTC);
5631
5633 ompBuilder->createCanonicalLoop(
5634 loopLoc,
5635 [&](llvm::OpenMPIRBuilder::InsertPointTy ip, llvm::Value *llvmIV) {
5636 // Register the mapping of MLIR induction variable to LLVM-IR
5637 // induction variable
5638 moduleTranslation.mapValue(loopIV, llvmIV);
5639
5640 builder.restoreIP(ip);
5642 convertOmpOpRegions(op.getRegion(), "omp.loop.region", builder,
5643 moduleTranslation);
5644
5645 return bodyGenStatus.takeError();
5646 },
5647 llvmTC, "omp.loop");
5648 if (!llvmOrError)
5649 return op.emitError(llvm::toString(llvmOrError.takeError()));
5650
5651 llvm::CanonicalLoopInfo *llvmCLI = *llvmOrError;
5652 llvm::IRBuilderBase::InsertPoint afterIP = llvmCLI->getAfterIP();
5653 builder.restoreIP(afterIP);
5654
5655 // Register the mapping of MLIR loop to LLVM-IR OpenMPIRBuilder loop
5656 if (Value cli = op.getCli())
5657 moduleTranslation.mapOmpLoop(cli, llvmCLI);
5658
5659 return success();
5660}
5661
5662/// Apply a `#pragma omp unroll` / "!$omp unroll" transformation using the
5663/// OpenMPIRBuilder.
5664static LogicalResult
5665applyUnrollHeuristic(omp::UnrollHeuristicOp op, llvm::IRBuilderBase &builder,
5666 LLVM::ModuleTranslation &moduleTranslation) {
5667 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5668
5669 Value applyee = op.getApplyee();
5670 assert(applyee && "Loop to apply unrolling on required");
5671
5672 llvm::CanonicalLoopInfo *consBuilderCLI =
5673 moduleTranslation.lookupOMPLoop(applyee);
5674 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5675 ompBuilder->unrollLoopHeuristic(loc.DL, consBuilderCLI);
5676
5677 moduleTranslation.invalidateOmpLoop(applyee);
5678 return success();
5679}
5680
5681/// Apply a `#pragma omp unroll full` / `!$omp unroll full` transformation
5682/// using the OpenMPIRBuilder.
5683static LogicalResult
5684applyUnrollFull(omp::UnrollFullOp op, llvm::IRBuilderBase &builder,
5685 LLVM::ModuleTranslation &moduleTranslation) {
5686 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5687
5688 Value applyee = op.getApplyee();
5689 assert(applyee && "Loop to apply unrolling on required");
5690
5691 llvm::CanonicalLoopInfo *consBuilderCLI =
5692 moduleTranslation.lookupOMPLoop(applyee);
5693 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5694 ompBuilder->unrollLoopFull(loc.DL, consBuilderCLI);
5695
5696 moduleTranslation.invalidateOmpLoop(applyee);
5697 return success();
5698}
5699
5700/// Apply a `#pragma omp unroll partial` / `!$omp unroll partial`
5701/// transformation using the OpenMPIRBuilder.
5702static LogicalResult
5703applyUnrollPartial(omp::UnrollPartialOp op, llvm::IRBuilderBase &builder,
5704 LLVM::ModuleTranslation &moduleTranslation) {
5705 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5706
5707 Value applyee = op.getApplyee();
5708 assert(applyee && "Loop to apply unrolling on required");
5709
5710 llvm::CanonicalLoopInfo *consBuilderCLI =
5711 moduleTranslation.lookupOMPLoop(applyee);
5712 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5713
5714 // No generatee is supported yet, so the unrolled loop's CanonicalLoopInfo is
5715 // not requested and unrolling is deferred to LLVM's LoopUnroll pass.
5716 int32_t factor = static_cast<int32_t>(op.getUnrollFactor());
5717 ompBuilder->unrollLoopPartial(loc.DL, consBuilderCLI, factor,
5718 /*UnrolledCLI=*/nullptr);
5719
5720 moduleTranslation.invalidateOmpLoop(applyee);
5721 return success();
5722}
5723
5724/// Apply a `#pragma omp tile` / `!$omp tile` transformation using the
5725/// OpenMPIRBuilder.
5726static LogicalResult applyTile(omp::TileOp op, llvm::IRBuilderBase &builder,
5727 LLVM::ModuleTranslation &moduleTranslation) {
5728 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5729 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5730
5732 SmallVector<llvm::Value *> translatedSizes;
5733
5734 for (Value size : op.getSizes()) {
5735 llvm::Value *translatedSize = moduleTranslation.lookupValue(size);
5736 assert(translatedSize &&
5737 "sizes clause arguments must already be translated");
5738 translatedSizes.push_back(translatedSize);
5739 }
5740
5741 for (Value applyee : op.getApplyees()) {
5742 llvm::CanonicalLoopInfo *consBuilderCLI =
5743 moduleTranslation.lookupOMPLoop(applyee);
5744 assert(applyee && "Canonical loop must already been translated");
5745 translatedLoops.push_back(consBuilderCLI);
5746 }
5747
5748 auto generatedLoops =
5749 ompBuilder->tileLoops(loc.DL, translatedLoops, translatedSizes);
5750 if (!op.getGeneratees().empty()) {
5751 for (auto [mlirLoop, genLoop] :
5752 zip_equal(op.getGeneratees(), generatedLoops))
5753 moduleTranslation.mapOmpLoop(mlirLoop, genLoop);
5754 }
5755
5756 // CLIs can only be consumed once
5757 for (Value applyee : op.getApplyees())
5758 moduleTranslation.invalidateOmpLoop(applyee);
5759
5760 return success();
5761}
5762
5763/// Apply a `#pragma omp fuse` / `!$omp fuse` transformation using the
5764/// OpenMPIRBuilder.
5765static LogicalResult applyFuse(omp::FuseOp op, llvm::IRBuilderBase &builder,
5766 LLVM::ModuleTranslation &moduleTranslation) {
5767 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5768 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5769
5770 // Select what CLIs are going to be fused
5771 SmallVector<llvm::CanonicalLoopInfo *> beforeFuse, toFuse, afterFuse;
5772 for (size_t i = 0; i < op.getApplyees().size(); i++) {
5773 Value applyee = op.getApplyees()[i];
5774 llvm::CanonicalLoopInfo *consBuilderCLI =
5775 moduleTranslation.lookupOMPLoop(applyee);
5776 assert(applyee && "Canonical loop must already been translated");
5777 if (op.getFirst().has_value() && i < op.getFirst().value() - 1)
5778 beforeFuse.push_back(consBuilderCLI);
5779 else if (op.getCount().has_value() &&
5780 i >= op.getFirst().value() + op.getCount().value() - 1)
5781 afterFuse.push_back(consBuilderCLI);
5782 else
5783 toFuse.push_back(consBuilderCLI);
5784 }
5785 assert(
5786 (op.getGeneratees().empty() ||
5787 beforeFuse.size() + afterFuse.size() + 1 == op.getGeneratees().size()) &&
5788 "Wrong number of generatees");
5789
5790 // do the fuse
5791 auto generatedLoop = ompBuilder->fuseLoops(loc.DL, toFuse);
5792 if (!op.getGeneratees().empty()) {
5793 size_t i = 0;
5794 for (; i < beforeFuse.size(); i++)
5795 moduleTranslation.mapOmpLoop(op.getGeneratees()[i], beforeFuse[i]);
5796 moduleTranslation.mapOmpLoop(op.getGeneratees()[i++], generatedLoop);
5797 for (; i < afterFuse.size(); i++)
5798 moduleTranslation.mapOmpLoop(op.getGeneratees()[i], afterFuse[i]);
5799 }
5800
5801 // CLIs can only be consumed once
5802 for (Value applyee : op.getApplyees())
5803 moduleTranslation.invalidateOmpLoop(applyee);
5804
5805 return success();
5806}
5807
5808/// Convert an Atomic Ordering attribute to llvm::AtomicOrdering.
5809static llvm::AtomicOrdering
5810convertAtomicOrdering(std::optional<omp::ClauseMemoryOrderKind> ao) {
5811 if (!ao)
5812 return llvm::AtomicOrdering::Monotonic; // Default Memory Ordering
5813
5814 switch (*ao) {
5815 case omp::ClauseMemoryOrderKind::Seq_cst:
5816 return llvm::AtomicOrdering::SequentiallyConsistent;
5817 case omp::ClauseMemoryOrderKind::Acq_rel:
5818 return llvm::AtomicOrdering::AcquireRelease;
5819 case omp::ClauseMemoryOrderKind::Acquire:
5820 return llvm::AtomicOrdering::Acquire;
5821 case omp::ClauseMemoryOrderKind::Release:
5822 return llvm::AtomicOrdering::Release;
5823 case omp::ClauseMemoryOrderKind::Relaxed:
5824 return llvm::AtomicOrdering::Monotonic;
5825 }
5826 llvm_unreachable("Unknown ClauseMemoryOrderKind kind");
5827}
5828
5829/// Compute the cmpxchg failure ordering for an atomic compare op: use the
5830/// `fail` clause ordering when present (the verifier guarantees it is a valid
5831/// cmpxchg failure ordering), otherwise the strongest failure ordering derived
5832/// from the success ordering (which matches the OpenMPIRBuilder default).
5833static llvm::AtomicOrdering
5834getAtomicCompareFailureOrdering(omp::AtomicCompareOp atomicCompareOp,
5835 llvm::AtomicOrdering atomicOrdering) {
5836 if (atomicCompareOp.getFailMemoryOrder())
5837 return convertAtomicOrdering(atomicCompareOp.getFailMemoryOrder());
5838 return llvm::AtomicCmpXchgInst::getStrongestFailureOrdering(atomicOrdering);
5839}
5840
5841/// Convert omp.atomic.read operation to LLVM IR.
5842static LogicalResult
5843convertOmpAtomicRead(Operation &opInst, llvm::IRBuilderBase &builder,
5844 LLVM::ModuleTranslation &moduleTranslation) {
5845 auto readOp = cast<omp::AtomicReadOp>(opInst);
5846 if (failed(checkImplementationStatus(opInst)))
5847 return failure();
5848
5849 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5850 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
5851 findAllocInsertPoints(builder, moduleTranslation);
5852
5853 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5854
5855 llvm::AtomicOrdering AO = convertAtomicOrdering(readOp.getMemoryOrder());
5856 llvm::Value *x = moduleTranslation.lookupValue(readOp.getX());
5857 llvm::Value *v = moduleTranslation.lookupValue(readOp.getV());
5858
5859 llvm::Type *elementType =
5860 moduleTranslation.convertType(readOp.getElementType());
5861
5862 llvm::OpenMPIRBuilder::AtomicOpValue V = {v, elementType, false, false};
5863 llvm::OpenMPIRBuilder::AtomicOpValue X = {x, elementType, false, false};
5864 builder.restoreIP(ompBuilder->createAtomicRead(ompLoc, X, V, AO, allocaIP));
5865 return success();
5866}
5867
5868/// Converts an omp.atomic.write operation to LLVM IR.
5869static LogicalResult
5870convertOmpAtomicWrite(Operation &opInst, llvm::IRBuilderBase &builder,
5871 LLVM::ModuleTranslation &moduleTranslation) {
5872 auto writeOp = cast<omp::AtomicWriteOp>(opInst);
5873 if (failed(checkImplementationStatus(opInst)))
5874 return failure();
5875
5876 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5877 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
5878 findAllocInsertPoints(builder, moduleTranslation);
5879
5880 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5881 llvm::AtomicOrdering ao = convertAtomicOrdering(writeOp.getMemoryOrder());
5882 llvm::Value *expr = moduleTranslation.lookupValue(writeOp.getExpr());
5883 llvm::Value *dest = moduleTranslation.lookupValue(writeOp.getX());
5884 llvm::Type *ty = moduleTranslation.convertType(writeOp.getExpr().getType());
5885 llvm::OpenMPIRBuilder::AtomicOpValue x = {dest, ty, /*isSigned=*/false,
5886 /*isVolatile=*/false};
5887 builder.restoreIP(
5888 ompBuilder->createAtomicWrite(ompLoc, x, expr, ao, allocaIP));
5889 return success();
5890}
5891
5892/// Converts an LLVM dialect binary operation to the corresponding enum value
5893/// for `atomicrmw` supported binary operation.
5894static llvm::AtomicRMWInst::BinOp convertBinOpToAtomic(Operation &op) {
5896 .Case([&](LLVM::AddOp) { return llvm::AtomicRMWInst::BinOp::Add; })
5897 .Case([&](LLVM::SubOp) { return llvm::AtomicRMWInst::BinOp::Sub; })
5898 .Case([&](LLVM::AndOp) { return llvm::AtomicRMWInst::BinOp::And; })
5899 .Case([&](LLVM::OrOp) { return llvm::AtomicRMWInst::BinOp::Or; })
5900 .Case([&](LLVM::XOrOp) { return llvm::AtomicRMWInst::BinOp::Xor; })
5901 .Case([&](LLVM::SMaxOp) { return llvm::AtomicRMWInst::BinOp::Max; })
5902 .Case([&](LLVM::SMinOp) { return llvm::AtomicRMWInst::BinOp::Min; })
5903 .Case([&](LLVM::UMaxOp) { return llvm::AtomicRMWInst::BinOp::UMax; })
5904 .Case([&](LLVM::UMinOp) { return llvm::AtomicRMWInst::BinOp::UMin; })
5905 .Case([&](LLVM::FAddOp) { return llvm::AtomicRMWInst::BinOp::FAdd; })
5906 .Case([&](LLVM::FSubOp) { return llvm::AtomicRMWInst::BinOp::FSub; })
5907 .Case([&](LLVM::MaxNumOp) { return llvm::AtomicRMWInst::BinOp::FMax; })
5908 .Case([&](LLVM::MinNumOp) { return llvm::AtomicRMWInst::BinOp::FMin; })
5909 .Case(
5910 [&](LLVM::MaximumOp) { return llvm::AtomicRMWInst::BinOp::FMaximum; })
5911 .Case(
5912 [&](LLVM::MinimumOp) { return llvm::AtomicRMWInst::BinOp::FMinimum; })
5913 .Default(llvm::AtomicRMWInst::BinOp::BAD_BINOP);
5914}
5915
5916static void extractAtomicControlFlags(omp::AtomicUpdateOp atomicUpdateOp,
5917 bool &isIgnoreDenormalMode,
5918 bool &isFineGrainedMemory,
5919 bool &isRemoteMemory) {
5920 isIgnoreDenormalMode = false;
5921 isFineGrainedMemory = false;
5922 isRemoteMemory = false;
5923 if (atomicUpdateOp && atomicUpdateOp.getAtomicControlAttr()) {
5924 mlir::omp::AtomicControlAttr atomicControlAttr =
5925 atomicUpdateOp.getAtomicControlAttr();
5926 isIgnoreDenormalMode = atomicControlAttr.getIgnoreDenormalMode();
5927 isFineGrainedMemory = atomicControlAttr.getFineGrainedMemory();
5928 isRemoteMemory = atomicControlAttr.getRemoteMemory();
5929 }
5930}
5931
5932/// Converts an OpenMP atomic update operation using OpenMPIRBuilder.
5933static LogicalResult
5934convertOmpAtomicUpdate(omp::AtomicUpdateOp &opInst,
5935 llvm::IRBuilderBase &builder,
5936 LLVM::ModuleTranslation &moduleTranslation) {
5937 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5938 if (failed(checkImplementationStatus(*opInst)))
5939 return failure();
5940
5941 // Convert values and types.
5942 auto &innerOpList = opInst.getRegion().front().getOperations();
5943 bool isXBinopExpr{false};
5944 llvm::AtomicRMWInst::BinOp binop;
5945 mlir::Value mlirExpr;
5946 llvm::Value *llvmExpr = nullptr;
5947 llvm::Value *llvmX = nullptr;
5948 llvm::Type *llvmXElementType = nullptr;
5949 if (innerOpList.size() == 2) {
5950 // The two operations here are the update and the terminator.
5951 // Since we can identify the update operation, there is a possibility
5952 // that we can generate the atomicrmw instruction.
5953 mlir::Operation &innerOp = *opInst.getRegion().front().begin();
5954 if (!llvm::is_contained(innerOp.getOperands(),
5955 opInst.getRegion().getArgument(0))) {
5956 return opInst.emitError("no atomic update operation with region argument"
5957 " as operand found inside atomic.update region");
5958 }
5959 binop = convertBinOpToAtomic(innerOp);
5960 isXBinopExpr = innerOp.getOperand(0) == opInst.getRegion().getArgument(0);
5961 mlirExpr = (isXBinopExpr ? innerOp.getOperand(1) : innerOp.getOperand(0));
5962 llvmExpr = moduleTranslation.lookupValue(mlirExpr);
5963 } else {
5964 // Since the update region includes more than one operation
5965 // we will resort to generating a cmpxchg loop.
5966 binop = llvm::AtomicRMWInst::BinOp::BAD_BINOP;
5967 }
5968 llvmX = moduleTranslation.lookupValue(opInst.getX());
5969 llvmXElementType = moduleTranslation.convertType(
5970 opInst.getRegion().getArgument(0).getType());
5971 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {llvmX, llvmXElementType,
5972 /*isSigned=*/false,
5973 /*isVolatile=*/false};
5974
5975 llvm::AtomicOrdering atomicOrdering =
5976 convertAtomicOrdering(opInst.getMemoryOrder());
5977
5978 // Generate update code.
5979 auto updateFn =
5980 [&opInst, &moduleTranslation](
5981 llvm::Value *atomicx,
5982 llvm::IRBuilder<> &builder) -> llvm::Expected<llvm::Value *> {
5983 Block &bb = *opInst.getRegion().begin();
5984 moduleTranslation.mapValue(*opInst.getRegion().args_begin(), atomicx);
5985 moduleTranslation.mapBlock(&bb, builder.GetInsertBlock());
5986 if (failed(moduleTranslation.convertBlock(bb, true, builder)))
5987 return llvm::make_error<PreviouslyReportedError>();
5988
5989 omp::YieldOp yieldop = dyn_cast<omp::YieldOp>(bb.getTerminator());
5990 assert(yieldop && yieldop.getResults().size() == 1 &&
5991 "terminator must be omp.yield op and it must have exactly one "
5992 "argument");
5993 return moduleTranslation.lookupValue(yieldop.getResults()[0]);
5994 };
5995
5996 bool isIgnoreDenormalMode;
5997 bool isFineGrainedMemory;
5998 bool isRemoteMemory;
5999 extractAtomicControlFlags(opInst, isIgnoreDenormalMode, isFineGrainedMemory,
6000 isRemoteMemory);
6001 // Handle ambiguous alloca, if any.
6002 auto allocaIP = findAllocInsertPoints(builder, moduleTranslation);
6003 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6004 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
6005 ompBuilder->createAtomicUpdate(ompLoc, allocaIP, llvmAtomicX, llvmExpr,
6006 atomicOrdering, binop, updateFn,
6007 isXBinopExpr, isIgnoreDenormalMode,
6008 isFineGrainedMemory, isRemoteMemory);
6009
6010 if (failed(handleError(afterIP, *opInst)))
6011 return failure();
6012
6013 builder.restoreIP(*afterIP);
6014 return success();
6015}
6016
6017/// Helper to extract the OMPAtomicCompareOp from an integer comparison
6018/// predicate. Returns std::nullopt for unsupported predicates.
6019static std::optional<llvm::omp::OMPAtomicCompareOp>
6020convertICmpPredicateToAtomicCompareOp(LLVM::ICmpPredicate predicate) {
6021 switch (predicate) {
6022 case LLVM::ICmpPredicate::eq:
6023 return llvm::omp::OMPAtomicCompareOp::EQ;
6024 case LLVM::ICmpPredicate::slt:
6025 case LLVM::ICmpPredicate::ult:
6026 return llvm::omp::OMPAtomicCompareOp::MIN;
6027 case LLVM::ICmpPredicate::sgt:
6028 case LLVM::ICmpPredicate::ugt:
6029 return llvm::omp::OMPAtomicCompareOp::MAX;
6030 default:
6031 return std::nullopt;
6032 }
6033}
6034
6035/// Helper to extract the OMPAtomicCompareOp from a floating-point comparison
6036/// predicate. Returns std::nullopt for unsupported predicates.
6037static std::optional<llvm::omp::OMPAtomicCompareOp>
6038convertFCmpPredicateToAtomicCompareOp(LLVM::FCmpPredicate predicate) {
6039 switch (predicate) {
6040 case LLVM::FCmpPredicate::oeq:
6041 case LLVM::FCmpPredicate::ueq:
6042 return llvm::omp::OMPAtomicCompareOp::EQ;
6043 case LLVM::FCmpPredicate::olt:
6044 case LLVM::FCmpPredicate::ult:
6045 return llvm::omp::OMPAtomicCompareOp::MIN;
6046 case LLVM::FCmpPredicate::ogt:
6047 case LLVM::FCmpPredicate::ugt:
6048 return llvm::omp::OMPAtomicCompareOp::MAX;
6049 default:
6050 return std::nullopt;
6051 }
6052}
6053
6054/// Result of matching the decomposed complex equality pattern inside an atomic
6055/// compare region.
6057 bool isComplex = false;
6058 bool isNE = false; // `or` of the field compares => NE (unsupported).
6059 mlir::Value eAggregate; // The complex expected value (`e`).
6060 bool isXBinopExpr = false; // True if x is the first fcmp operand.
6061};
6062
6063/// Detect a decomposed complex equality comparison in an atomic compare region:
6064/// %re_x = llvm.extractvalue %xval[0]
6065/// %re_e = llvm.extractvalue %eStruct[0]
6066/// %cmp_re = llvm.fcmp "oeq" %re_x, %re_e
6067/// %im_x = llvm.extractvalue %xval[1]
6068/// %im_e = llvm.extractvalue %eStruct[1]
6069/// %cmp_im = llvm.fcmp "oeq" %im_x, %im_e
6070/// %cmp = llvm.and %cmp_re, %cmp_im (llvm.or would be NE)
6071/// It is recognised by an and/or whose operands are both fcmps operating on
6072/// extractvalues, one chain rooted at the block argument (x) and the other at
6073/// the expected complex value (e).
6076 auto traceToAggregate = [](mlir::Value v) -> mlir::Value {
6077 if (auto extractOp = v.getDefiningOp<LLVM::ExtractValueOp>())
6078 return extractOp.getContainer();
6079 return nullptr;
6080 };
6081 for (Operation &op : block.getOperations()) {
6082 if (!isa<LLVM::AndOp, LLVM::OrOp>(op))
6083 continue;
6084 auto lhsFcmp = op.getOperand(0).getDefiningOp<LLVM::FCmpOp>();
6085 auto rhsFcmp = op.getOperand(1).getDefiningOp<LLVM::FCmpOp>();
6086 if (!lhsFcmp || !rhsFcmp)
6087 continue;
6088 mlir::Value lhsAgg0 = traceToAggregate(lhsFcmp.getOperand(0));
6089 mlir::Value lhsAgg1 = traceToAggregate(lhsFcmp.getOperand(1));
6090 bool lhsXIsOp0 = (lhsAgg0 == block.getArgument(0));
6091 bool lhsXIsOp1 = (lhsAgg1 == block.getArgument(0));
6092 if (!lhsXIsOp0 && !lhsXIsOp1)
6093 continue;
6094 mlir::Value eAggregate = lhsXIsOp0 ? lhsAgg1 : lhsAgg0;
6095 if (!eAggregate)
6096 continue;
6097 result.isComplex = true;
6098 result.isNE = isa<LLVM::OrOp>(op);
6099 result.eAggregate = eAggregate;
6100 result.isXBinopExpr = lhsXIsOp0;
6101 break;
6102 }
6103 return result;
6104}
6105
6106/// Emit an IEEE-754-correct `cmpxchg` for a complex (struct-typed) atomic
6107/// compare with `fcmp oeq`. The old value of X is returned (as the complex
6108/// struct type) in \p oldComplex and the success flag (i1) in \p cmpOk.
6109/// \p failOrdering is the memory ordering used when the compare-exchange does
6110/// not store; it must be a valid cmpxchg failure ordering.
6111static void emitComplexAtomicCmpXchg(llvm::IRBuilderBase &builder,
6112 llvm::Value *llvmX, llvm::Type *complexTy,
6113 llvm::Value *eVal, llvm::Value *dVal,
6114 llvm::AtomicOrdering atomicOrdering,
6115 llvm::AtomicOrdering failOrdering,
6116 bool isWeak, llvm::Value *&oldComplex,
6117 llvm::Value *&cmpOk) {
6118 const llvm::DataLayout &DL = builder.getDataLayout();
6119 unsigned totalBits = DL.getTypeStoreSizeInBits(complexTy).getFixedValue();
6120 llvm::IntegerType *intTy =
6121 llvm::IntegerType::get(builder.getContext(), totalBits);
6122 llvm::Align complexAlign = DL.getABITypeAlign(complexTy);
6123 llvm::Align intAlign = DL.getABITypeAlign(intTy);
6124 llvm::Align maxAlign = std::max(complexAlign, intAlign);
6125
6126 // Spill D to obtain its integer bit pattern for the swap value.
6127 llvm::AllocaInst *dAlloca =
6128 builder.CreateAlloca(complexTy, nullptr, "cmplx.d");
6129 dAlloca->setAlignment(maxAlign);
6130 builder.CreateAlignedStore(dVal, dAlloca, maxAlign);
6131 llvm::Value *dInt =
6132 builder.CreateAlignedLoad(intTy, dAlloca, maxAlign, "cmplx.d.int");
6133
6134 // Load X atomically and reinterpret as complex. Use the failure ordering: on
6135 // a failed component comparison we branch around the cmpxchg, so this load is
6136 // the only memory op on that path.
6137 llvm::LoadInst *xCurr =
6138 builder.CreateAlignedLoad(intTy, llvmX, maxAlign, "cmplx.x.load");
6139 xCurr->setAtomic(failOrdering);
6140 llvm::AllocaInst *xAlloca =
6141 builder.CreateAlloca(complexTy, nullptr, "cmplx.x");
6142 xAlloca->setAlignment(maxAlign);
6143 builder.CreateAlignedStore(xCurr, xAlloca, maxAlign);
6144 llvm::Value *xStruct =
6145 builder.CreateAlignedLoad(complexTy, xAlloca, maxAlign, "cmplx.x.val");
6146
6147 // Component-wise IEEE-754 equality: `fcmp oeq` yields false for NaN (so a
6148 // NaN component correctly makes the compare fail) and true for +0.0 vs -0.0
6149 // (so a zero-sign difference does not spuriously fail the compare).
6150 llvm::Value *reX = builder.CreateExtractValue(xStruct, 0);
6151 llvm::Value *imX = builder.CreateExtractValue(xStruct, 1);
6152 llvm::Value *reE = builder.CreateExtractValue(eVal, 0);
6153 llvm::Value *imE = builder.CreateExtractValue(eVal, 1);
6154 llvm::Value *reEq = builder.CreateFCmpOEQ(reX, reE, "cmplx.re.eq");
6155 llvm::Value *imEq = builder.CreateFCmpOEQ(imX, imE, "cmplx.im.eq");
6156 llvm::Value *fpEqual = builder.CreateAnd(reEq, imEq, "cmplx.eq");
6157
6158 // When the components compare equal, attempt the swap using X's own loaded
6159 // bit pattern as the comparand; otherwise the compare fails and X is left
6160 // unchanged (the captured old value is the value just loaded).
6161 llvm::BasicBlock *curBB = builder.GetInsertBlock();
6162 llvm::Function *fn = curBB->getParent();
6163 llvm::BasicBlock *swapBB =
6164 llvm::BasicBlock::Create(builder.getContext(), "cmplx.atomic.swap", fn);
6165 llvm::BasicBlock *exitBB =
6166 llvm::BasicBlock::Create(builder.getContext(), "cmplx.atomic.exit", fn);
6167 builder.CreateCondBr(fpEqual, swapBB, exitBB);
6168
6169 builder.SetInsertPoint(swapBB);
6170 llvm::AtomicCmpXchgInst *cmpXchg = builder.CreateAtomicCmpXchg(
6171 llvmX, xCurr, dInt, maxAlign, atomicOrdering, failOrdering);
6172 cmpXchg->setWeak(isWeak);
6173 llvm::Value *oldSwap = builder.CreateExtractValue(cmpXchg, 0);
6174 llvm::Value *okSwap = builder.CreateExtractValue(cmpXchg, 1);
6175 builder.CreateBr(exitBB);
6176
6177 // Merge the swap and no-swap paths.
6178 builder.SetInsertPoint(exitBB);
6179 llvm::PHINode *oldIntPHI = builder.CreatePHI(intTy, 2, "cmplx.old.int");
6180 oldIntPHI->addIncoming(oldSwap, swapBB);
6181 oldIntPHI->addIncoming(xCurr, curBB);
6182 llvm::PHINode *okPHI = builder.CreatePHI(builder.getInt1Ty(), 2, "cmplx.ok");
6183 okPHI->addIncoming(okSwap, swapBB);
6184 okPHI->addIncoming(builder.getFalse(), curBB);
6185
6186 // Reinterpret the old integer value as the complex struct via memory.
6187 llvm::AllocaInst *oldAlloca =
6188 builder.CreateAlloca(complexTy, nullptr, "cmplx.old");
6189 oldAlloca->setAlignment(maxAlign);
6190 builder.CreateAlignedStore(oldIntPHI, oldAlloca, maxAlign);
6191 oldComplex = builder.CreateAlignedLoad(complexTy, oldAlloca, maxAlign,
6192 "cmplx.old.val");
6193 cmpOk = okPHI;
6194}
6195
6196/// Holds the extracted comparison pattern information from an atomic compare
6197/// region.
6199 llvm::omp::OMPAtomicCompareOp compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
6200 llvm::Value *eVal = nullptr;
6201 llvm::Value *dVal = nullptr;
6202 bool isXBinopExpr = false;
6203 bool isSigned = false;
6204};
6205/// Extract comparison predicate, expected value (e), desired value (d), and
6206/// related flags from an atomic compare region block by scanning for
6207/// icmp/fcmp/select/min/max operations.
6208static LogicalResult extractAtomicComparePattern(
6209 Block &block,
6210 llvm::function_ref<llvm::Value *(mlir::Value)> materializeValue,
6211 omp::AtomicCompareOp atomicCompareOp, AtomicComparePatternInfo &info) {
6212 // Complex equality is a decomposed per-field pattern (extractvalue + fcmp +
6213 // and) rather than a single scalar compare. Detect it first so the scalar
6214 // icmp/fcmp handling below does not mistake a real/imaginary field for the
6215 // whole expected value.
6217 cplx.isComplex) {
6218 if (cplx.isNE)
6219 return atomicCompareOp.emitError(
6220 "unsupported comparison predicate (NE) for complex atomic compare");
6221 info.compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
6222 info.isXBinopExpr = cplx.isXBinopExpr;
6223 info.eVal = materializeValue(cplx.eAggregate);
6224 for (Operation &op : block.getOperations()) {
6225 if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
6226 info.dVal = materializeValue(selectOp.getTrueValue());
6227 break;
6228 }
6229 }
6230 return success();
6231 }
6232
6233 for (Operation &op : block.getOperations()) {
6234 // Pre-filter: skip icmps that don't involve the block argument
6235 // (e.g., truthiness extractions from logical-to-integer conversion).
6236 if (auto icmpOp = dyn_cast<LLVM::ICmpOp>(op);
6237 icmpOp && icmpOp.getOperand(0) != block.getArgument(0) &&
6238 icmpOp.getOperand(1) != block.getArgument(0))
6239 continue;
6240
6241 LogicalResult result =
6243 .Case<LLVM::ICmpOp>([&](LLVM::ICmpOp icmpOp) -> LogicalResult {
6244 auto maybeOp =
6245 convertICmpPredicateToAtomicCompareOp(icmpOp.getPredicate());
6246 if (!maybeOp)
6247 return atomicCompareOp.emitError(
6248 "unsupported comparison predicate in atomic compare");
6249 info.compareOp = *maybeOp;
6250 LLVM::ICmpPredicate pred = icmpOp.getPredicate();
6251 info.isSigned = (pred == LLVM::ICmpPredicate::slt ||
6252 pred == LLVM::ICmpPredicate::sgt ||
6253 pred == LLVM::ICmpPredicate::sle ||
6254 pred == LLVM::ICmpPredicate::sge);
6255 info.isXBinopExpr =
6256 (icmpOp.getOperand(0) == block.getArgument(0));
6257 mlir::Value eOperand = info.isXBinopExpr ? icmpOp.getOperand(1)
6258 : icmpOp.getOperand(0);
6259 info.eVal = materializeValue(eOperand);
6260 return success();
6261 })
6262 .Case<LLVM::FCmpOp>([&](LLVM::FCmpOp fcmpOp) -> LogicalResult {
6263 auto maybeOp =
6264 convertFCmpPredicateToAtomicCompareOp(fcmpOp.getPredicate());
6265 if (!maybeOp)
6266 return atomicCompareOp.emitError(
6267 "unsupported comparison predicate in atomic compare");
6268 info.compareOp = *maybeOp;
6269 info.isXBinopExpr =
6270 (fcmpOp.getOperand(0) == block.getArgument(0));
6271 mlir::Value eOperand = info.isXBinopExpr ? fcmpOp.getOperand(1)
6272 : fcmpOp.getOperand(0);
6273 info.eVal = materializeValue(eOperand);
6274 return success();
6275 })
6276 .Case<LLVM::SelectOp>([&](LLVM::SelectOp selectOp) {
6277 if (!info.dVal)
6278 info.dVal = materializeValue(selectOp.getTrueValue());
6279 return success();
6280 })
6281 .Case<mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
6282 mlir::arith::MaxUIOp, mlir::arith::MinUIOp,
6283 mlir::arith::MaximumFOp, mlir::arith::MinimumFOp,
6284 LLVM::SMaxOp, LLVM::SMinOp, LLVM::UMaxOp, LLVM::UMinOp,
6285 LLVM::MaxNumOp, LLVM::MinNumOp>([&](Operation *) {
6286 // Canonicalized min/max ops (arith or LLVM intrinsic form).
6287 // max(x,e) came from slt/ult/olt -> OMPAtomicCompareOp::MIN
6288 // min(x,e) came from sgt/ugt/ogt -> OMPAtomicCompareOp::MAX
6289 // (OMPIRBuilder inverts: MIN->atomicrmw max, MAX->atomicrmw min)
6290 bool isMax = isa<mlir::arith::MaxSIOp, mlir::arith::MaxUIOp,
6291 mlir::arith::MaximumFOp, LLVM::SMaxOp,
6292 LLVM::UMaxOp, LLVM::MaxNumOp>(op);
6293 info.compareOp = isMax ? llvm::omp::OMPAtomicCompareOp::MIN
6294 : llvm::omp::OMPAtomicCompareOp::MAX;
6295 info.isSigned = isa<mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
6296 LLVM::SMaxOp, LLVM::SMinOp>(op);
6297 info.isXBinopExpr = (op.getOperand(0) == block.getArgument(0));
6298 mlir::Value eOperand =
6299 info.isXBinopExpr ? op.getOperand(1) : op.getOperand(0);
6300 info.eVal = materializeValue(eOperand);
6301 info.dVal = info.eVal;
6302 return success();
6303 })
6304 .Default([](Operation *) { return success(); });
6305
6306 if (failed(result))
6307 return result;
6308 }
6309 return success();
6310}
6311
6312static LogicalResult
6313convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
6314 llvm::IRBuilderBase &builder,
6315 LLVM::ModuleTranslation &moduleTranslation) {
6316 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
6317 if (failed(checkImplementationStatus(*atomicCaptureOp)))
6318 return failure();
6319
6320 omp::AtomicUpdateOp atomicUpdateOp = atomicCaptureOp.getAtomicUpdateOp();
6321 omp::AtomicWriteOp atomicWriteOp = atomicCaptureOp.getAtomicWriteOp();
6322 omp::AtomicCompareOp atomicCompareOp = atomicCaptureOp.getAtomicCompareOp();
6323
6324 // If the capture contains an atomic.compare, delegate to
6325 // createAtomicCompare with the capture variable (V) set.
6326 if (atomicCompareOp) {
6327 omp::AtomicReadOp atomicReadOp = atomicCaptureOp.getAtomicReadOp();
6328 assert(atomicReadOp && "expected atomic.read in capture+compare");
6329
6330 Region &region = atomicCompareOp.getRegion();
6331 Block &block = region.front();
6332
6333 llvm::Type *llvmXElementType =
6334 moduleTranslation.convertType(block.getArgument(0).getType());
6335 llvm::Value *llvmX = moduleTranslation.lookupValue(atomicCompareOp.getX());
6336 llvm::Value *llvmV = moduleTranslation.lookupValue(atomicReadOp.getV());
6337
6338 bool isSigned = false;
6339 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {
6340 llvmX, llvmXElementType, isSigned, /*IsVolatile=*/false};
6341 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicV = {
6342 llvmV, llvmXElementType, /*isSigned=*/false, /*IsVolatile=*/false};
6343 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicR = {nullptr, nullptr, false,
6344 false};
6345
6346 llvm::AtomicOrdering atomicOrdering =
6347 convertAtomicOrdering(atomicCaptureOp.getMemoryOrder());
6348
6349 // Pre-translate non-pattern operations inside the compare region.
6350 auto isAtomicComparePatternOp = [](Operation &op) {
6351 return llvm::isa<LLVM::ICmpOp, LLVM::FCmpOp, LLVM::SelectOp, LLVM::AndOp,
6352 LLVM::OrOp, LLVM::SMaxOp, LLVM::SMinOp, LLVM::UMaxOp,
6353 LLVM::UMinOp, LLVM::MaxNumOp, LLVM::MinNumOp,
6354 mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
6355 mlir::arith::MaxUIOp, mlir::arith::MinUIOp,
6356 mlir::arith::MaximumFOp, mlir::arith::MinimumFOp>(op);
6357 };
6358 for (Operation &op : block.without_terminator()) {
6359 if (isAtomicComparePatternOp(op))
6360 continue;
6361 bool allOperandsMapped =
6362 llvm::all_of(op.getOperands(), [&](mlir::Value v) {
6363 return moduleTranslation.lookupValue(v) != nullptr;
6364 });
6365 if (!allOperandsMapped)
6366 continue;
6367 if (failed(moduleTranslation.convertOperation(op, builder)))
6368 return atomicCompareOp.emitError(
6369 "failed to translate operation inside atomic compare region");
6370 }
6371
6372 auto materializeValue = [&](mlir::Value val) -> llvm::Value * {
6373 if (llvm::Value *existing = moduleTranslation.lookupValue(val))
6374 return existing;
6375 if (auto loadOp = val.getDefiningOp<LLVM::LoadOp>()) {
6376 if (loadOp->getParentRegion() == &region) {
6377 llvm::Value *loadAddr =
6378 moduleTranslation.lookupValue(loadOp.getAddr());
6379 if (!loadAddr)
6380 return nullptr;
6381 llvm::Type *loadType =
6382 moduleTranslation.convertType(loadOp.getResult().getType());
6383 return builder.CreateLoad(loadType, loadAddr);
6384 }
6385 }
6386 return nullptr;
6387 };
6388
6389 // Extract comparison predicate, eVal, and dVal from the region.
6390 AtomicComparePatternInfo patternInfo;
6391 if (failed(extractAtomicComparePattern(block, materializeValue,
6392 atomicCompareOp, patternInfo)))
6393 return failure();
6394
6395 llvm::omp::OMPAtomicCompareOp compareOp = patternInfo.compareOp;
6396 llvm::Value *eVal = patternInfo.eVal;
6397 llvm::Value *dVal = patternInfo.dVal;
6398 bool isXBinopExpr = patternInfo.isXBinopExpr;
6399 isSigned = patternInfo.isSigned;
6400
6401 if (!eVal)
6402 return atomicCompareOp.emitError(
6403 "failed to extract expected value (e) from atomic compare region");
6404 if (!dVal) {
6405 auto yieldOp = cast<omp::YieldOp>(block.getTerminator());
6406 if (yieldOp.getResults().empty())
6407 return atomicCompareOp.emitError(
6408 "failed to extract desired value (d) from atomic compare region");
6409 dVal = materializeValue(yieldOp.getResults()[0]);
6410 }
6411
6412 llvmAtomicX.IsSigned = isSigned;
6413
6414 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6415 bool isReadFirst = isa<omp::AtomicReadOp>(atomicCaptureOp.getFirstOp());
6416 bool isPostfixCapture = !isReadFirst;
6417 bool isFailOnly = atomicCaptureOp.getFailOnly();
6418
6419 // Complex equality capture: x is struct-typed, which the OMPIRBuilder
6420 // cannot handle, so emit an IEEE-754-correct cmpxchg (as in the non-capture
6421 // complex path) and reconstruct the captured value from its result. The
6422 // helper compares components with `fcmp oeq` so `-0.0 == +0.0` and `NaN`
6423 // are handled as in the scalar float path. Complex only supports the ==
6424 // comparison.
6425 if (llvmXElementType->isStructTy()) {
6426 llvm::Value *oldComplex = nullptr;
6427 llvm::Value *cmpOk = nullptr;
6428 llvm::AtomicOrdering failOrdering =
6429 getAtomicCompareFailureOrdering(atomicCompareOp, atomicOrdering);
6430 emitComplexAtomicCmpXchg(builder, llvmX, llvmXElementType, eVal, dVal,
6431 atomicOrdering, failOrdering,
6432 atomicCompareOp.getWeak(), oldComplex, cmpOk);
6433
6434 if (isFailOnly) {
6435 // v is written only when the compare fails (cmpOk == false).
6436 llvm::Value *cmpFailed = builder.CreateNot(cmpOk);
6437 llvm::BasicBlock *curBB = builder.GetInsertBlock();
6438 llvm::Function *fn = curBB->getParent();
6439 llvm::BasicBlock *contBB = llvm::BasicBlock::Create(
6440 builder.getContext(), "omp.atomic.cont", fn);
6441 llvm::BasicBlock *exitBB = llvm::BasicBlock::Create(
6442 builder.getContext(), "omp.atomic.exit", fn);
6443 builder.CreateCondBr(cmpFailed, contBB, exitBB);
6444 builder.SetInsertPoint(contBB);
6445 builder.CreateStore(oldComplex, llvmAtomicV.Var,
6446 llvmAtomicV.IsVolatile);
6447 builder.CreateBr(exitBB);
6448 builder.SetInsertPoint(exitBB);
6449 } else if (isPostfixCapture) {
6450 // v gets the new value of x: d on success, old x otherwise.
6451 llvm::Value *newComplex = builder.CreateSelect(cmpOk, dVal, oldComplex);
6452 builder.CreateStore(newComplex, llvmAtomicV.Var,
6453 llvmAtomicV.IsVolatile);
6454 } else {
6455 // Prefix: v gets the old value of x.
6456 builder.CreateStore(oldComplex, llvmAtomicV.Var,
6457 llvmAtomicV.IsVolatile);
6458 }
6459
6460 // Emit flush after atomic compare if needed (release/acq_rel/seq_cst).
6461 if (atomicOrdering == llvm::AtomicOrdering::Release ||
6462 atomicOrdering == llvm::AtomicOrdering::AcquireRelease ||
6463 atomicOrdering == llvm::AtomicOrdering::SequentiallyConsistent) {
6464 llvm::OpenMPIRBuilder::LocationDescription flushLoc(builder);
6465 ompBuilder->createFlush(flushLoc);
6466 }
6467 return success();
6468 }
6469
6470 // Min/max (<, >) comparisons lower to an atomicrmw. The OMPIRBuilder has no
6471 // notion of a failed compare for an atomicrmw, so the fail-only capture
6472 // form (v written only when the compare fails) has no valid mapping and is
6473 // Min/max (<, >) comparisons lower to an atomicrmw. The OMPIRBuilder has no
6474 // notion of a failed compare for an atomicrmw (it asserts on IsFailOnly),
6475 // so for min/max the fail-only capture is reconstructed manually below.
6476 bool isMinMax = compareOp != llvm::omp::OMPAtomicCompareOp::EQ;
6477
6478 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicVForCall = llvmAtomicV;
6479 // Capture into V is reconstructed manually below for:
6480 // * postfix capture (v gets the new value of x): equality from the
6481 // cmpxchg result, min/max from the atomicrmw result;
6482 // * min/max fail-only capture (the OMPIRBuilder cannot express it).
6483 // Bypass V in the OMPIRBuilder for those cases so it does not also emit its
6484 // own (for min/max, incorrect or unsupported) capture store.
6485 bool minMaxManualCapture = isMinMax && (isPostfixCapture || isFailOnly);
6486 bool eqPostfixManualCapture = !isMinMax && isPostfixCapture && !isFailOnly;
6487 if (minMaxManualCapture || eqPostfixManualCapture)
6488 llvmAtomicVForCall = {nullptr, nullptr, false, false};
6489
6490 // The OMPIRBuilder only understands IsFailOnly for the equality (cmpxchg)
6491 // path; for min/max it would assert. Min/max fail-only is handled here.
6492 bool builderFailOnly = isFailOnly && !isMinMax;
6493
6494 // IsPostfixUpdate selects which value the OMPIRBuilder captures into V:
6495 // * min/max prefix and equality prefix: a direct store of the old value
6496 // (IsPostfixUpdate=true).
6497 // * equality fail-only: a conditional store (IsPostfixUpdate=false).
6498 // Manually-reconstructed captures bypass V above.
6499 bool isPostfixUpdate = !builderFailOnly;
6500
6501 bool isWeak = atomicCompareOp.getWeak();
6502 bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
6503 llvm::AtomicOrdering failureOrdering =
6504 getAtomicCompareFailureOrdering(atomicCompareOp, atomicOrdering);
6505 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
6506 ompBuilder->createAtomicCompare(
6507 ompLoc, llvmAtomicX, llvmAtomicVForCall, llvmAtomicR, eVal, dVal,
6508 atomicOrdering, compareOp, isXBinopExpr, isPostfixUpdate,
6509 builderFailOnly, failureOrdering, isWeak);
6510 ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
6511
6512 if (failed(handleError(afterIP, *atomicCaptureOp)))
6513 return failure();
6514
6515 builder.restoreIP(*afterIP);
6516
6517 // Min/max capture is reconstructed from the atomicrmw the OMPIRBuilder
6518 // emits (its result is the old value of x). V was bypassed above.
6519 // * postfix: v gets the new value min/max(old, e);
6520 // * fail-only: v gets the old value, but only when the compare failed
6521 // (i.e. the atomicrmw did not change x).
6522 // (Prefix min/max captures the old value directly through V, so nothing
6523 // extra is needed there.)
6524 if (isMinMax && (isPostfixCapture || isFailOnly)) {
6525 llvm::BasicBlock *curBB = builder.GetInsertBlock();
6526 llvm::AtomicRMWInst *rmw = nullptr;
6527 for (auto &inst : llvm::reverse(*curBB)) {
6528 if (auto *r = dyn_cast<llvm::AtomicRMWInst>(&inst)) {
6529 rmw = r;
6530 break;
6531 }
6532 }
6533 assert(rmw && "expected atomicrmw for min/max compare capture");
6534 llvm::Value *oldVal = rmw;
6535 llvm::Value *rhs = rmw->getValOperand();
6536
6537 if (isFailOnly) {
6538 // The compare "failed" (the else branch runs) exactly when the
6539 // atomicrmw did not change x. Recompute the original update condition
6540 // on the old value and negate it. v is stored only in that case.
6541 llvm::CmpInst::Predicate updatePred;
6542 switch (rmw->getOperation()) {
6543 case llvm::AtomicRMWInst::Min:
6544 updatePred = llvm::CmpInst::ICMP_SGT;
6545 break;
6546 case llvm::AtomicRMWInst::Max:
6547 updatePred = llvm::CmpInst::ICMP_SLT;
6548 break;
6549 case llvm::AtomicRMWInst::UMin:
6550 updatePred = llvm::CmpInst::ICMP_UGT;
6551 break;
6552 case llvm::AtomicRMWInst::UMax:
6553 updatePred = llvm::CmpInst::ICMP_ULT;
6554 break;
6555 case llvm::AtomicRMWInst::FMin:
6556 updatePred = llvm::CmpInst::FCMP_OGT;
6557 break;
6558 case llvm::AtomicRMWInst::FMax:
6559 updatePred = llvm::CmpInst::FCMP_OLT;
6560 break;
6561 default:
6562 llvm_unreachable(
6563 "unexpected atomicrmw op for min/max compare capture");
6564 }
6565 llvm::Value *updated = builder.CreateCmp(updatePred, oldVal, rhs);
6566 llvm::Value *failed = builder.CreateNot(updated);
6567 llvm::Function *fn = curBB->getParent();
6568 llvm::BasicBlock *contBB = llvm::BasicBlock::Create(
6569 builder.getContext(), "omp.atomic.cont", fn);
6570 llvm::BasicBlock *exitBB = llvm::BasicBlock::Create(
6571 builder.getContext(), "omp.atomic.exit", fn);
6572 builder.CreateCondBr(failed, contBB, exitBB);
6573 builder.SetInsertPoint(contBB);
6574 builder.CreateStore(oldVal, llvmAtomicV.Var, llvmAtomicV.IsVolatile);
6575 builder.CreateBr(exitBB);
6576 builder.SetInsertPoint(exitBB);
6577 } else {
6578 llvm::Intrinsic::ID id;
6579 switch (rmw->getOperation()) {
6580 case llvm::AtomicRMWInst::Min:
6581 id = llvm::Intrinsic::smin;
6582 break;
6583 case llvm::AtomicRMWInst::Max:
6584 id = llvm::Intrinsic::smax;
6585 break;
6586 case llvm::AtomicRMWInst::UMin:
6587 id = llvm::Intrinsic::umin;
6588 break;
6589 case llvm::AtomicRMWInst::UMax:
6590 id = llvm::Intrinsic::umax;
6591 break;
6592 case llvm::AtomicRMWInst::FMin:
6593 id = llvm::Intrinsic::minnum;
6594 break;
6595 case llvm::AtomicRMWInst::FMax:
6596 id = llvm::Intrinsic::maxnum;
6597 break;
6598 default:
6599 llvm_unreachable(
6600 "unexpected atomicrmw op for min/max compare capture");
6601 }
6602 llvm::Value *newVal = builder.CreateBinaryIntrinsic(id, oldVal, rhs);
6603 builder.CreateStore(newVal, llvmAtomicV.Var, llvmAtomicV.IsVolatile);
6604 }
6605 }
6606
6607 // Equality postfix: v = select(success, D, old) — reconstructs the new
6608 // value of x from the cmpxchg result.
6609 if (!isMinMax && isPostfixCapture && !isFailOnly) {
6610 llvm::BasicBlock *curBB = builder.GetInsertBlock();
6611 llvm::Value *oldVal = nullptr;
6612 llvm::Value *successVal = nullptr;
6613
6614 // Integer path (and non-HandleFPNegZero FP path): a single cmpxchg
6615 // lives in the current block.
6616 for (auto &inst : llvm::reverse(*curBB)) {
6617 if (isa<llvm::AtomicCmpXchgInst>(&inst)) {
6618 oldVal = builder.CreateExtractValue(&inst, /*Idxs=*/0);
6619 successVal = builder.CreateExtractValue(&inst, /*Idxs=*/1);
6620 break;
6621 }
6622 }
6623
6624 // FP HandleFPNegZero path: the OMPIRBuilder emits a multi-block
6625 // structure (NaN / ±0.0 handling) with cmpxchg in predecessor
6626 // blocks. Results are merged via PHI nodes in the current (exit)
6627 // block: an i1 PHI for success and a bitcast of an integer PHI
6628 // for the old FP value.
6629 if (!oldVal) {
6630 for (auto &inst : *curBB) {
6631 auto *phi = dyn_cast<llvm::PHINode>(&inst);
6632 if (!phi)
6633 break;
6634 if (phi->getType()->isIntegerTy(1))
6635 successVal = phi;
6636 }
6637 for (auto &inst : *curBB) {
6638 if (auto *bc = dyn_cast<llvm::BitCastInst>(&inst)) {
6639 oldVal = bc;
6640 break;
6641 }
6642 }
6643 }
6644
6645 assert(oldVal && "expected cmpxchg or PHI+bitcast for compare capture");
6646 assert(successVal && "expected success flag for compare capture");
6647 llvm::Value *newVal = builder.CreateSelect(successVal, dVal, oldVal);
6648 builder.CreateStore(newVal, llvmAtomicV.Var, llvmAtomicV.IsVolatile);
6649 }
6650
6651 return success();
6652 }
6653
6654 mlir::Value mlirExpr;
6655 bool isXBinopExpr = false, isPostfixUpdate = false;
6656 llvm::AtomicRMWInst::BinOp binop = llvm::AtomicRMWInst::BinOp::BAD_BINOP;
6657
6658 assert((atomicUpdateOp || atomicWriteOp) &&
6659 "internal op must be an atomic.update or atomic.write op");
6660
6661 if (atomicWriteOp) {
6662 isPostfixUpdate = true;
6663 mlirExpr = atomicWriteOp.getExpr();
6664 } else {
6665 isPostfixUpdate = atomicCaptureOp.getSecondOp() ==
6666 atomicCaptureOp.getAtomicUpdateOp().getOperation();
6667 auto &innerOpList = atomicUpdateOp.getRegion().front().getOperations();
6668 // Find the binary update operation that uses the region argument
6669 // and get the expression to update
6670 if (innerOpList.size() == 2) {
6671 mlir::Operation &innerOp = *atomicUpdateOp.getRegion().front().begin();
6672 if (!llvm::is_contained(innerOp.getOperands(),
6673 atomicUpdateOp.getRegion().getArgument(0))) {
6674 return atomicUpdateOp.emitError(
6675 "no atomic update operation with region argument"
6676 " as operand found inside atomic.update region");
6677 }
6678 binop = convertBinOpToAtomic(innerOp);
6679 isXBinopExpr =
6680 innerOp.getOperand(0) == atomicUpdateOp.getRegion().getArgument(0);
6681 mlirExpr = (isXBinopExpr ? innerOp.getOperand(1) : innerOp.getOperand(0));
6682 } else {
6683 binop = llvm::AtomicRMWInst::BinOp::BAD_BINOP;
6684 }
6685 }
6686
6687 llvm::Value *llvmExpr = moduleTranslation.lookupValue(mlirExpr);
6688 llvm::Value *llvmX =
6689 moduleTranslation.lookupValue(atomicCaptureOp.getAtomicReadOp().getX());
6690 llvm::Value *llvmV =
6691 moduleTranslation.lookupValue(atomicCaptureOp.getAtomicReadOp().getV());
6692 llvm::Type *llvmXElementType = moduleTranslation.convertType(
6693 atomicCaptureOp.getAtomicReadOp().getElementType());
6694 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {llvmX, llvmXElementType,
6695 /*isSigned=*/false,
6696 /*isVolatile=*/false};
6697 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicV = {llvmV, llvmXElementType,
6698 /*isSigned=*/false,
6699 /*isVolatile=*/false};
6700
6701 llvm::AtomicOrdering atomicOrdering =
6702 convertAtomicOrdering(atomicCaptureOp.getMemoryOrder());
6703
6704 auto updateFn =
6705 [&](llvm::Value *atomicx,
6706 llvm::IRBuilder<> &builder) -> llvm::Expected<llvm::Value *> {
6707 if (atomicWriteOp)
6708 return moduleTranslation.lookupValue(atomicWriteOp.getExpr());
6709 Block &bb = *atomicUpdateOp.getRegion().begin();
6710 moduleTranslation.mapValue(*atomicUpdateOp.getRegion().args_begin(),
6711 atomicx);
6712 moduleTranslation.mapBlock(&bb, builder.GetInsertBlock());
6713 if (failed(moduleTranslation.convertBlock(bb, true, builder)))
6714 return llvm::make_error<PreviouslyReportedError>();
6715
6716 omp::YieldOp yieldop = dyn_cast<omp::YieldOp>(bb.getTerminator());
6717 assert(yieldop && yieldop.getResults().size() == 1 &&
6718 "terminator must be omp.yield op and it must have exactly one "
6719 "argument");
6720 return moduleTranslation.lookupValue(yieldop.getResults()[0]);
6721 };
6722
6723 bool isIgnoreDenormalMode;
6724 bool isFineGrainedMemory;
6725 bool isRemoteMemory;
6726 extractAtomicControlFlags(atomicUpdateOp, isIgnoreDenormalMode,
6727 isFineGrainedMemory, isRemoteMemory);
6728 // Handle ambiguous alloca, if any.
6729 auto allocaIP = findAllocInsertPoints(builder, moduleTranslation);
6730 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6731 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
6732 ompBuilder->createAtomicCapture(
6733 ompLoc, allocaIP, llvmAtomicX, llvmAtomicV, llvmExpr, atomicOrdering,
6734 binop, updateFn, atomicUpdateOp, isPostfixUpdate, isXBinopExpr,
6735 isIgnoreDenormalMode, isFineGrainedMemory, isRemoteMemory);
6736
6737 if (failed(handleError(afterIP, *atomicCaptureOp)))
6738 return failure();
6739
6740 builder.restoreIP(*afterIP);
6741 return success();
6742}
6743
6744/// Converts an omp.atomic.compare operation to LLVM IR.
6745///
6746/// if (x == e) x = d
6747/// The region contains a comparison + select pattern:
6748/// ^bb0(%xval: T):
6749/// %cmp = llvm.icmp/fcmp <pred> %xval, %e : T
6750/// %sel = llvm.select %cmp, %d, %xval : i1, T
6751/// omp.yield(%sel : T)
6752///
6753/// From MLIR extract:
6754/// 1) comparison operator
6755/// 2) expected value (e)
6756/// 3) desired value (d)
6757/// These are passed to OpenMPIRBuilder::createAtomicCompare which generates
6758/// the actual cmpxchg / atomicrmw instruction.
6759///
6760static LogicalResult
6761convertOmpAtomicCompare(omp::AtomicCompareOp atomicCompareOp,
6762 llvm::IRBuilderBase &builder,
6763 LLVM::ModuleTranslation &moduleTranslation) {
6764 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
6765 if (failed(checkImplementationStatus(*atomicCompareOp)))
6766 return failure();
6767
6768 Region &region = atomicCompareOp.getRegion();
6769 Block &block = region.front();
6770
6771 // Determine element type from the region block argument
6772 llvm::Type *llvmXElementType =
6773 moduleTranslation.convertType(block.getArgument(0).getType());
6774 if (!llvmXElementType)
6775 return atomicCompareOp.emitError(
6776 "unable to determine element type for atomic compare");
6777
6778 llvm::Value *llvmX = moduleTranslation.lookupValue(atomicCompareOp.getX());
6779
6780 // IsSigned is determined from the comparison predicate in the region.
6781 // Signed ICmp predicates (slt/sgt) set this to true; unsigned (ult/ugt)
6782 // leave it false. For EQ and float comparisons, signedness is irrelevant.
6783 bool isSigned = false;
6784 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {llvmX, llvmXElementType,
6785 isSigned,
6786 /*IsVolatile=*/false};
6787
6788 llvm::AtomicOrdering atomicOrdering =
6789 convertAtomicOrdering(atomicCompareOp.getMemoryOrder());
6790
6791 auto isAtomicComparePatternOp = [](Operation &op) {
6792 return llvm::isa<LLVM::ICmpOp, LLVM::FCmpOp, LLVM::SelectOp, LLVM::AndOp,
6793 LLVM::OrOp, LLVM::SMaxOp, LLVM::SMinOp, LLVM::UMaxOp,
6794 LLVM::UMinOp, LLVM::MaxNumOp, LLVM::MinNumOp,
6795 mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
6796 mlir::arith::MaxUIOp, mlir::arith::MinUIOp,
6797 mlir::arith::MaximumFOp, mlir::arith::MinimumFOp>(op);
6798 };
6799
6800 // Pre-translate operations inside the region that compute e and d (e.g.,
6801 // GEP, loads for dereferencing Fortran pointers) but are not part of the
6802 // atomic compare-and-swap pattern (icmp/fcmp, select, and/or).
6803 //
6804 // 1) Validity: The OpenMP spec requires e and d to be evaluated before the
6805 // atomic operation, so emitting their computation here is correct.
6806 // 2) Memory effects: These ops only depend on values defined outside the
6807 // region. They cannot observe the block argument (%xval), which is the
6808 // value loaded atomically by cmpxchg and does not exist yet.
6809 // 3) Invariant enforcement: The `allOperandsMapped` check below skips any
6810 // op whose operands include the unmapped block argument, guaranteeing
6811 // only region-external-dependent ops are pre-translated.
6812 for (Operation &op : block.without_terminator()) {
6813 // Skip operations that form the atomic compare pattern — these are
6814 // not emitted as individual instructions but are analyzed below to
6815 // extract the comparison predicate, expected value (e), and desired
6816 // value (d) for generating a single cmpxchg/atomicrmw.
6817 if (isAtomicComparePatternOp(op))
6818 continue;
6819
6820 // Avoid translating ops that depend on the unmapped block argument.
6821 bool allOperandsMapped = llvm::all_of(op.getOperands(), [&](mlir::Value v) {
6822 return moduleTranslation.lookupValue(v) != nullptr;
6823 });
6824 if (!allOperandsMapped)
6825 continue;
6826
6827 if (failed(moduleTranslation.convertOperation(op, builder)))
6828 return atomicCompareOp.emitError(
6829 "failed to translate operation inside atomic compare region");
6830 }
6831
6832 // Look up a value that may have been pre-translated or defined outside the
6833 // region.
6834 auto materializeValue = [&](mlir::Value val) -> llvm::Value * {
6835 // Check if the value is already mapped (pre-translated or defined outside).
6836 if (llvm::Value *existing = moduleTranslation.lookupValue(val))
6837 return existing;
6838 // Fallback for a single LoadOp whose address is mapped but whose result
6839 // was not pre-translated.
6840 if (auto loadOp = val.getDefiningOp<LLVM::LoadOp>()) {
6841 if (loadOp->getParentRegion() == &region) {
6842 llvm::Value *loadAddr = moduleTranslation.lookupValue(loadOp.getAddr());
6843 if (!loadAddr)
6844 return nullptr;
6845 llvm::Type *loadType =
6846 moduleTranslation.convertType(loadOp.getResult().getType());
6847 return builder.CreateLoad(loadType, loadAddr);
6848 }
6849 }
6850 return nullptr;
6851 };
6852
6853 // Walk the region to extract comparison predicate, eVal, and dVal.
6854 // if (x == eVal) x = dVal
6855 llvm::omp::OMPAtomicCompareOp compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
6856 llvm::Value *eVal = nullptr;
6857 llvm::Value *dVal = nullptr;
6858 bool isXBinopExpr = false;
6859
6860 // Check for a decomposed complex comparison pattern (extractvalue + fcmp +
6861 // and/or of the real/imaginary fields).
6863 bool isComplexPattern = cplx.isComplex;
6864 if (isComplexPattern) {
6865 if (cplx.isNE)
6866 // OrOp corresponds to NE, which is not a valid atomic compare op.
6867 return atomicCompareOp.emitError(
6868 "unsupported comparison predicate (NE) for complex atomic compare");
6869 compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
6870 isXBinopExpr = cplx.isXBinopExpr;
6871 eVal = materializeValue(cplx.eAggregate);
6872 }
6873
6874 if (isComplexPattern) {
6875 // dVal from SelectOp or YieldOp.
6876 for (Operation &op : block.getOperations()) {
6877 if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
6878 dVal = materializeValue(selectOp.getTrueValue());
6879 break;
6880 }
6881 }
6882 if (!dVal) {
6883 auto yieldOp = cast<omp::YieldOp>(block.getTerminator());
6884 if (yieldOp.getResults().empty())
6885 return atomicCompareOp.emitError(
6886 "failed to extract desired value (d) from atomic compare region");
6887 dVal = materializeValue(yieldOp.getResults()[0]);
6888 }
6889
6890 llvm::Value *oldComplex = nullptr;
6891 llvm::Value *cmpOk = nullptr;
6892 llvm::AtomicOrdering failOrdering =
6893 getAtomicCompareFailureOrdering(atomicCompareOp, atomicOrdering);
6894 emitComplexAtomicCmpXchg(builder, llvmX, llvmXElementType, eVal, dVal,
6895 atomicOrdering, failOrdering,
6896 atomicCompareOp.getWeak(), oldComplex, cmpOk);
6897 (void)oldComplex;
6898 (void)cmpOk;
6899
6900 // Emit flush after atomic compare if needed (for release, acq_rel,
6901 // seq_cst orderings).
6902 if (atomicOrdering == llvm::AtomicOrdering::Release ||
6903 atomicOrdering == llvm::AtomicOrdering::AcquireRelease ||
6904 atomicOrdering == llvm::AtomicOrdering::SequentiallyConsistent) {
6905 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6906 ompBuilder->createFlush(ompLoc);
6907 }
6908 return success();
6909 } else {
6910 AtomicComparePatternInfo patternInfo;
6911 if (failed(extractAtomicComparePattern(block, materializeValue,
6912 atomicCompareOp, patternInfo)))
6913 return failure();
6914 compareOp = patternInfo.compareOp;
6915 eVal = patternInfo.eVal;
6916 dVal = patternInfo.dVal;
6917 isXBinopExpr = patternInfo.isXBinopExpr;
6918 isSigned = patternInfo.isSigned;
6919 }
6920
6921 if (!eVal)
6922 return atomicCompareOp.emitError(
6923 "failed to extract expected value (e) from atomic compare region");
6924 if (!dVal) {
6925 // Fall back to the yield operand.
6926 auto yieldOp = cast<omp::YieldOp>(block.getTerminator());
6927 if (yieldOp.getResults().empty())
6928 return atomicCompareOp.emitError(
6929 "failed to extract desired value (d) from atomic compare region");
6930 dVal = materializeValue(yieldOp.getResults()[0]);
6931 }
6932
6933 llvmAtomicX.IsSigned = isSigned;
6934
6935 llvm::OpenMPIRBuilder::AtomicOpValue vOpVal = {nullptr, nullptr, false,
6936 false};
6937 llvm::OpenMPIRBuilder::AtomicOpValue rOpVal = {nullptr, nullptr, false,
6938 false};
6939 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6940
6941 bool isWeak = atomicCompareOp.getWeak();
6942
6943 bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
6944 llvm::AtomicOrdering failureOrdering =
6945 getAtomicCompareFailureOrdering(atomicCompareOp, atomicOrdering);
6946 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
6947 ompBuilder->createAtomicCompare(
6948 ompLoc, llvmAtomicX, vOpVal, rOpVal, eVal, dVal, atomicOrdering,
6949 compareOp, isXBinopExpr, /*IsPostfixUpdate=*/false,
6950 /*IsFailOnly=*/false, failureOrdering, isWeak);
6951 ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
6952
6953 if (failed(handleError(afterIP, *atomicCompareOp)))
6954 return failure();
6955
6956 builder.restoreIP(*afterIP);
6957 return success();
6958}
6959
6960static llvm::omp::Directive convertCancellationConstructType(
6961 omp::ClauseCancellationConstructType directive) {
6962 switch (directive) {
6963 case omp::ClauseCancellationConstructType::Loop:
6964 return llvm::omp::Directive::OMPD_for;
6965 case omp::ClauseCancellationConstructType::Parallel:
6966 return llvm::omp::Directive::OMPD_parallel;
6967 case omp::ClauseCancellationConstructType::Sections:
6968 return llvm::omp::Directive::OMPD_sections;
6969 case omp::ClauseCancellationConstructType::Taskgroup:
6970 return llvm::omp::Directive::OMPD_taskgroup;
6971 }
6972 llvm_unreachable("Unhandled cancellation construct type");
6973}
6974
6975static LogicalResult
6976convertOmpCancel(omp::CancelOp op, llvm::IRBuilderBase &builder,
6977 LLVM::ModuleTranslation &moduleTranslation) {
6978 if (failed(checkImplementationStatus(*op.getOperation())))
6979 return failure();
6980
6981 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6982 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
6983
6984 llvm::Value *ifCond = nullptr;
6985 if (Value ifVar = op.getIfExpr())
6986 ifCond = moduleTranslation.lookupValue(ifVar);
6987
6988 llvm::omp::Directive cancelledDirective =
6989 convertCancellationConstructType(op.getCancelDirective());
6990
6991 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
6992 ompBuilder->createCancel(ompLoc, ifCond, cancelledDirective);
6993
6994 if (failed(handleError(afterIP, *op.getOperation())))
6995 return failure();
6996
6997 builder.restoreIP(afterIP.get());
6998
6999 return success();
7000}
7001
7002static LogicalResult
7003convertOmpCancellationPoint(omp::CancellationPointOp op,
7004 llvm::IRBuilderBase &builder,
7005 LLVM::ModuleTranslation &moduleTranslation) {
7006 if (failed(checkImplementationStatus(*op.getOperation())))
7007 return failure();
7008
7009 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
7010 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
7011
7012 llvm::omp::Directive cancelledDirective =
7013 convertCancellationConstructType(op.getCancelDirective());
7014
7015 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
7016 ompBuilder->createCancellationPoint(ompLoc, cancelledDirective);
7017
7018 if (failed(handleError(afterIP, *op.getOperation())))
7019 return failure();
7020
7021 builder.restoreIP(afterIP.get());
7022
7023 return success();
7024}
7025
7026/// Converts an OpenMP Threadprivate operation into LLVM IR using
7027/// OpenMPIRBuilder.
7028static LogicalResult
7029convertOmpThreadprivate(Operation &opInst, llvm::IRBuilderBase &builder,
7030 LLVM::ModuleTranslation &moduleTranslation) {
7031 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
7032 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
7033 auto threadprivateOp = cast<omp::ThreadprivateOp>(opInst);
7034
7035 if (failed(checkImplementationStatus(opInst)))
7036 return failure();
7037
7038 Value symAddr = threadprivateOp.getSymAddr();
7039 auto *symOp = symAddr.getDefiningOp();
7040
7041 if (auto asCast = dyn_cast<LLVM::AddrSpaceCastOp>(symOp))
7042 symOp = asCast.getOperand().getDefiningOp();
7043
7044 if (!isa<LLVM::AddressOfOp>(symOp))
7045 return opInst.emitError("Addressing symbol not found");
7046 LLVM::AddressOfOp addressOfOp = dyn_cast<LLVM::AddressOfOp>(symOp);
7047
7048 LLVM::GlobalOp global =
7049 addressOfOp.getGlobal(moduleTranslation.symbolTable());
7050 llvm::GlobalValue *globalValue = moduleTranslation.lookupGlobal(global);
7051 llvm::Type *type = globalValue->getValueType();
7052 llvm::TypeSize typeSize = builder.getDataLayout().getTypeStoreSize(type);
7053 llvm::ConstantInt *size = builder.getInt64(typeSize.getFixedValue());
7054 llvm::Value *callInst = ompBuilder->createCachedThreadPrivate(
7055 ompLoc, globalValue, size, global.getSymName() + ".cache");
7056 moduleTranslation.mapValue(opInst.getResult(0), callInst);
7057
7058 return success();
7059}
7060
7061static llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseKind
7062convertToDeviceClauseKind(mlir::omp::DeclareTargetDeviceType deviceClause) {
7063 switch (deviceClause) {
7064 case mlir::omp::DeclareTargetDeviceType::host:
7065 return llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseHost;
7066 break;
7067 case mlir::omp::DeclareTargetDeviceType::nohost:
7068 return llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseNoHost;
7069 break;
7070 case mlir::omp::DeclareTargetDeviceType::any:
7071 return llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseAny;
7072 break;
7073 }
7074 llvm_unreachable("unhandled device clause");
7075}
7076
7077static llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryKind
7079 mlir::omp::DeclareTargetCaptureClause captureClause) {
7080 switch (captureClause) {
7081 case mlir::omp::DeclareTargetCaptureClause::to:
7082 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryTo;
7083 case mlir::omp::DeclareTargetCaptureClause::link:
7084 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryLink;
7085 case mlir::omp::DeclareTargetCaptureClause::enter:
7086 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryEnter;
7087 case mlir::omp::DeclareTargetCaptureClause::none:
7088 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryNone;
7089 }
7090 llvm_unreachable("unhandled capture clause");
7091}
7092
7094 Operation *op = value.getDefiningOp();
7095 if (auto addrCast = dyn_cast_if_present<LLVM::AddrSpaceCastOp>(op))
7096 op = addrCast->getOperand(0).getDefiningOp();
7097 if (auto addressOfOp = dyn_cast_if_present<LLVM::AddressOfOp>(op)) {
7098 auto modOp = addressOfOp->getParentOfType<mlir::ModuleOp>();
7099 return modOp.lookupSymbol(addressOfOp.getGlobalName());
7100 }
7101 return nullptr;
7102}
7103
7105 while (Operation *op = value.getDefiningOp()) {
7106 if (auto addrCast = dyn_cast_if_present<LLVM::AddrSpaceCastOp>(op))
7107 value = addrCast.getOperand();
7108 // Traces through hlfir.declare, fir.declare to reach the base address and
7109 // use for type lookup.
7110 else if (op->getName().getIdentifier() &&
7111 (op->getName().getIdentifier().str() == "hlfir.declare" ||
7112 op->getName().getIdentifier().str() == "fir.declare")) {
7113 if (op->getNumOperands() > 0)
7114 value = op->getOperand(0);
7115 else
7116 break;
7117 } else {
7118 break;
7119 }
7120 }
7121 return value;
7122}
7123
7124// Determine the LLVM type whose storage size should be allocated for an
7125// OpenMP allocate directive list item. Opaque pointers lose element type, so
7126// trace through declare wrappers to the underlying global or stack allocation.
7127static llvm::Type *
7129 LLVM::ModuleTranslation &moduleTranslation) {
7130 llvm::Type *llvmVarTy = moduleTranslation.convertType(var.getType());
7131 if (!llvmVarTy->isPointerTy())
7132 return llvmVarTy;
7133
7134 if (Operation *globalOp = getGlobalOpFromValue(baseVar))
7135 if (auto gop = dyn_cast<LLVM::GlobalOp>(globalOp))
7136 return moduleTranslation.convertType(gop.getGlobalType());
7137
7138 if (auto allocaOp =
7139 dyn_cast_if_present<LLVM::AllocaOp>(baseVar.getDefiningOp()))
7140 return moduleTranslation.convertType(allocaOp.getElemType());
7141
7142 if (llvm::Value *baseLlvm = moduleTranslation.lookupValue(baseVar))
7143 if (auto *allocaInst = dyn_cast<llvm::AllocaInst>(baseLlvm))
7144 return allocaInst->getAllocatedType();
7145
7146 return llvmVarTy;
7147}
7148
7149// For dynamically-sized stack allocations, compute the allocation size from
7150// the alloca's element count at runtime.
7151static std::optional<llvm::Value *> getDynamicAllocatedSize(
7152 Value var, Value baseVar, LLVM::ModuleTranslation &moduleTranslation,
7153 llvm::IRBuilderBase &builder, const llvm::DataLayout &dataLayout) {
7154 if (auto allocaOp =
7155 dyn_cast_if_present<LLVM::AllocaOp>(baseVar.getDefiningOp())) {
7156 if (Value arraySize = allocaOp.getArraySize()) {
7157 llvm::Type *elemTy =
7158 moduleTranslation.convertType(allocaOp.getElemType());
7159 llvm::Value *numElems = moduleTranslation.lookupValue(arraySize);
7160 if (!numElems->getType()->isIntegerTy(64))
7161 numElems = builder.CreateZExt(numElems, builder.getInt64Ty());
7162 uint64_t elemSize = dataLayout.getTypeAllocSize(elemTy).getFixedValue();
7163 return builder.CreateMul(numElems, builder.getInt64(elemSize));
7164 }
7165 }
7166 if (llvm::Value *baseLlvm = moduleTranslation.lookupValue(baseVar)) {
7167 if (auto *allocaInst = dyn_cast<llvm::AllocaInst>(baseLlvm)) {
7168 if (allocaInst->isArrayAllocation() &&
7169 !llvm::isa<llvm::ArrayType>(allocaInst->getAllocatedType())) {
7170 uint64_t elemSize =
7171 dataLayout.getTypeAllocSize(allocaInst->getAllocatedType())
7172 .getFixedValue();
7173 return builder.CreateMul(allocaInst->getArraySize(),
7174 builder.getInt64(elemSize));
7175 }
7176 }
7177 }
7178 return std::nullopt;
7179}
7180
7181static llvm::SmallString<64>
7182getDeclareTargetRefPtrSuffix(LLVM::GlobalOp globalOp,
7183 llvm::OpenMPIRBuilder &ompBuilder,
7184 llvm::vfs::FileSystem &vfs) {
7185 llvm::SmallString<64> suffix;
7186 llvm::raw_svector_ostream os(suffix);
7187 if (globalOp.getVisibility() == mlir::SymbolTable::Visibility::Private) {
7188 auto loc = globalOp->getLoc()->findInstanceOf<FileLineColLoc>();
7189 auto fileInfoCallBack = [&loc]() {
7190 return std::pair<std::string, uint64_t>(
7191 llvm::StringRef(loc.getFilename()), loc.getLine());
7192 };
7193
7194 os << llvm::format(
7195 "_%x",
7196 ompBuilder.getTargetEntryUniqueInfo(fileInfoCallBack, vfs).FileID);
7197 }
7198 os << "_decl_tgt_ref_ptr";
7199
7200 return suffix;
7201}
7202
7203static bool isDeclareTargetLink(Value value) {
7204 if (auto declareTargetGlobal =
7205 dyn_cast_if_present<omp::DeclareTargetInterface>(
7206 getGlobalOpFromValue(value))) {
7207 omp::DeclareTargetAttr declareTargetAttr =
7208 declareTargetGlobal.getDeclareTarget();
7209 if (declareTargetAttr && declareTargetAttr.getCaptureClause() ==
7210 omp::DeclareTargetCaptureClause::link)
7211 return true;
7212 }
7213 return false;
7214}
7215
7216static bool isDeclareTargetTo(Value value) {
7217 if (auto declareTargetGlobal =
7218 dyn_cast_if_present<omp::DeclareTargetInterface>(
7219 getGlobalOpFromValue(value))) {
7220 omp::DeclareTargetAttr declareTargetAttr =
7221 declareTargetGlobal.getDeclareTarget();
7222 if (declareTargetAttr && (declareTargetAttr.getCaptureClause() ==
7223 omp::DeclareTargetCaptureClause::to ||
7224 declareTargetAttr.getCaptureClause() ==
7225 omp::DeclareTargetCaptureClause::enter))
7226 return true;
7227 }
7228 return false;
7229}
7230
7231// Returns the reference pointer generated by the lowering of the declare
7232// target operation in cases where the link clause is used or the to clause is
7233// used in USM mode.
7234static llvm::Value *
7236 LLVM::ModuleTranslation &moduleTranslation) {
7237 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
7238 if (auto gOp =
7239 dyn_cast_or_null<LLVM::GlobalOp>(getGlobalOpFromValue(value))) {
7240 // In this case, we must utilise the reference pointer generated by
7241 // the declare target operation, similar to Clang
7242 if (isDeclareTargetLink(value) ||
7243 (isDeclareTargetTo(value) &&
7244 ompBuilder->Config.hasRequiresUnifiedSharedMemory())) {
7246 gOp, *ompBuilder, moduleTranslation.getFileSystem());
7247
7248 if (gOp.getSymName().contains(suffix))
7249 return moduleTranslation.getLLVMModule()->getNamedValue(
7250 gOp.getSymName());
7251
7252 return moduleTranslation.getLLVMModule()->getNamedValue(
7253 (gOp.getSymName().str() + suffix.str()).str());
7254 }
7255 }
7256 return nullptr;
7257}
7258
7259namespace {
7260// Append customMappers information to existing MapInfosTy
7261struct MapInfosTy : llvm::OpenMPIRBuilder::MapInfosTy {
7262 SmallVector<Operation *, 4> Mappers;
7263
7264 /// Append arrays in \a CurInfo.
7265 void append(MapInfosTy &curInfo) {
7266 Mappers.append(curInfo.Mappers.begin(), curInfo.Mappers.end());
7267 llvm::OpenMPIRBuilder::MapInfosTy::append(curInfo);
7268 }
7269};
7270// A small helper structure to contain data gathered
7271// for map lowering and coalese it into one area and
7272// avoiding extra computations such as searches in the
7273// llvm module for lowered mapped variables or checking
7274// if something is declare target (and retrieving the
7275// value) more than neccessary.
7276struct MapInfoData : MapInfosTy {
7277 llvm::SmallVector<bool, 4> IsDeclareTarget;
7278 llvm::SmallVector<bool, 4> IsAMember;
7279 // Identify if mapping was added by mapClause or use_device clauses.
7280 llvm::SmallVector<bool, 4> IsAMapping;
7281 llvm::SmallVector<mlir::Operation *, 4> MapClause;
7282 llvm::SmallVector<llvm::Value *, 4> OriginalValue;
7283 // Stripped off array/pointer to get the underlying
7284 // element type
7285 llvm::SmallVector<llvm::Type *, 4> BaseType;
7286
7287 /// Append arrays in \a CurInfo.
7288 void append(MapInfoData &CurInfo) {
7289 IsDeclareTarget.append(CurInfo.IsDeclareTarget.begin(),
7290 CurInfo.IsDeclareTarget.end());
7291 MapClause.append(CurInfo.MapClause.begin(), CurInfo.MapClause.end());
7292 OriginalValue.append(CurInfo.OriginalValue.begin(),
7293 CurInfo.OriginalValue.end());
7294 BaseType.append(CurInfo.BaseType.begin(), CurInfo.BaseType.end());
7295 MapInfosTy::append(CurInfo);
7296 }
7297};
7298
7299enum class TargetDirectiveEnumTy : uint32_t {
7300 None = 0,
7301 Target = 1,
7302 TargetData = 2,
7303 TargetEnterData = 3,
7304 TargetExitData = 4,
7305 TargetUpdate = 5
7306};
7307
7308static TargetDirectiveEnumTy getTargetDirectiveEnumTyFromOp(Operation *op) {
7309 return llvm::TypeSwitch<Operation *, TargetDirectiveEnumTy>(op)
7310 .Case([](omp::TargetDataOp) { return TargetDirectiveEnumTy::TargetData; })
7311 .Case([](omp::TargetEnterDataOp) {
7312 return TargetDirectiveEnumTy::TargetEnterData;
7313 })
7314 .Case([&](omp::TargetExitDataOp) {
7315 return TargetDirectiveEnumTy::TargetExitData;
7316 })
7317 .Case([&](omp::TargetUpdateOp) {
7318 return TargetDirectiveEnumTy::TargetUpdate;
7319 })
7320 .Case([&](omp::TargetOp) { return TargetDirectiveEnumTy::Target; })
7321 .Default([&](Operation *op) { return TargetDirectiveEnumTy::None; });
7322}
7323
7324} // namespace
7325
7326static uint64_t getArrayElementSizeInBits(LLVM::LLVMArrayType arrTy,
7327 DataLayout &dl) {
7328 if (auto nestedArrTy = llvm::dyn_cast_if_present<LLVM::LLVMArrayType>(
7329 arrTy.getElementType()))
7330 return getArrayElementSizeInBits(nestedArrTy, dl);
7331 return dl.getTypeSizeInBits(arrTy.getElementType());
7332}
7333
7334// The intent is to verify if the mapped data being passed is a
7335// pointer -> pointee that requires special handling in certain cases,
7336// e.g. applying the OMP_MAP_PTR_AND_OBJ map type.
7337//
7338// There may be a better way to verify this, but unfortunately with
7339// opaque pointers we lose the ability to easily check if something is
7340// a pointer whilst maintaining access to the underlying type.
7341static bool checkIfPointerMap(omp::MapInfoOp mapOp) {
7342 // If we have a varPtrPtr field assigned then the underlying type is a pointer
7343 if (mapOp.getVarPtrPtr())
7344 return true;
7345
7346 // If the map data is declare target with a link clause, then it's represented
7347 // as a pointer when we lower it to LLVM-IR even if at the MLIR level it has
7348 // no relation to pointers.
7349 if (isDeclareTargetLink(mapOp.getVarPtr()))
7350 return true;
7351
7352 return false;
7353}
7354
7355// A privatizeable attach map is a pointer/descriptor that is privatized and
7356// passed directly as a kernel argument (target_param) rather than undergoing
7357// the standard attach/parent mapping. These are handled specially in a couple
7358// of places in map lowering.
7359static bool isPrivatizeableAttachMap(omp::ClauseMapFlags mapType) {
7360 return bitEnumContainsAll(mapType, omp::ClauseMapFlags::priv |
7361 omp::ClauseMapFlags::target_param |
7362 omp::ClauseMapFlags::attach);
7363}
7364
7365// This function calculates the size to be offloaded for a specified type, given
7366// its associated map clause (which can contain bounds information which affects
7367// the total size), this size is calculated based on the underlying element type
7368// e.g. given a 1-D array of ints, we will calculate the size from the integer
7369// type * number of elements in the array. This size can be used in other
7370// calculations but is ultimately used as an argument to the OpenMP runtimes
7371// kernel argument structure which is generated through the combinedInfo data
7372// structures.
7373// This function is somewhat equivalent to Clang's getExprTypeSize inside of
7374// CGOpenMPRuntime.cpp.
7375static llvm::Value *getSizeInBytes(DataLayout &dl, const mlir::Type &type,
7376 Operation *clauseOp,
7377 llvm::Value *basePointer,
7378 llvm::Type *baseType,
7379 llvm::IRBuilderBase &builder,
7380 LLVM::ModuleTranslation &moduleTranslation) {
7381 if (auto memberClause =
7382 mlir::dyn_cast_if_present<mlir::omp::MapInfoOp>(clauseOp)) {
7383 // This calculates the size to transfer based on bounds and the underlying
7384 // element type, provided bounds have been specified (Fortran
7385 // pointers/allocatables/target and arrays that have sections specified fall
7386 // into this as well)
7387 if (!memberClause.getBounds().empty()) {
7388 llvm::Value *elementCount = builder.getInt64(1);
7389 for (auto bounds : memberClause.getBounds()) {
7390 if (auto boundOp = mlir::dyn_cast_if_present<mlir::omp::MapBoundsOp>(
7391 bounds.getDefiningOp())) {
7392 // The below calculation for the size to be mapped calculated from the
7393 // map.info's bounds is: (elemCount * [UB - LB] + 1), later we
7394 // multiply by the underlying element types byte size to get the full
7395 // size to be offloaded based on the bounds
7396 elementCount = builder.CreateMul(
7397 elementCount,
7398 builder.CreateAdd(
7399 builder.CreateSub(
7400 moduleTranslation.lookupValue(boundOp.getUpperBound()),
7401 moduleTranslation.lookupValue(boundOp.getLowerBound())),
7402 builder.getInt64(1)));
7403 }
7404 }
7405
7406 // utilising getTypeSizeInBits instead of getTypeSize as getTypeSize gives
7407 // the size in inconsistent byte or bit format.
7408 uint64_t underlyingTypeSzInBits = dl.getTypeSizeInBits(type);
7409 if (auto arrTy = llvm::dyn_cast_if_present<LLVM::LLVMArrayType>(type))
7410 underlyingTypeSzInBits = getArrayElementSizeInBits(arrTy, dl);
7411
7412 // The size in bytes x number of elements, the sizeInBytes stored is
7413 // the underyling types size, e.g. if ptr<i32>, it'll be the i32's
7414 // size, so we do some on the fly runtime math to get the size in
7415 // bytes from the extent (ub - lb) * sizeInBytes. NOTE: This may need
7416 // some adjustment for members with more complex types.
7417 llvm::Value *sizeCalc = builder.CreateMul(
7418 elementCount, builder.getInt64(underlyingTypeSzInBits / 8),
7419 "element_count");
7420
7421 // This is a part of a "complicated" bit of size calculation logic that is
7422 // in place to handle a couple of scenarios, one specific to Fortran and
7423 // the other a more general OpenMP issue. The other piece of the
7424 // calculation can be found as the final size calculation within the
7425 // processIndividualMap function. Ideally we would move it here, but due
7426 // to the complexity of calculating the final base address of some
7427 // constructs (required for a nullary check), it's left as the final step.
7428 // So, in the below 2 cases, the nullary check is in processIndividualMap
7429 // and the size equality check is here. The cases this modifications help
7430 // cover are:
7431 //
7432 // 1) If an argument has a null base pointer, then the size must be set to
7433 // 0 to avoid the runtime exploding/complaining about an illegal
7434 // pointer map. The size returning non-zero is feasible in certain
7435 // cases if for example someone has specified there own bounds/range.
7436 // 2) We wish to support a very specific OpenMP Fortran edge-case where a
7437 // size zero array can be legally presence checked and found to be on
7438 // device when it has been mapped. In these rare occasions the
7439 // allocatable/pointer will have a size of 1 allocated for the
7440 // underlying data, but this wall not be represented within the size of
7441 // the descriptor, so we get a non-nullary pointer and a size of 0,
7442 // allowing us to specify a size of 1 in these cases registering it on
7443 // the device mapping table as present.
7444 //
7445 // The default fall through case is just returning the size calculation
7446 // above, if we are not nullary and the size we calculate is non-zero,
7447 // which is basically any pointer type that is allocated in someway
7448 // (providing you are not running on a rare system that allows malloc's of
7449 // size 0 with whatever caveats that may come with).
7450 //
7451 // Later in the nullary check in processIndividualMap it just devolves to
7452 // selecting a size of 0 if we are nullary, if we are not, we will return
7453 // either 1 or the calculated size, depending on the outcome of this
7454 // select.
7455 if (checkIfPointerMap(memberClause)) {
7456 return builder.CreateSelect(
7457 builder.CreateICmpEQ(sizeCalc, builder.getInt64(0)),
7458 builder.getInt64(1), sizeCalc);
7459 }
7460
7461 return sizeCalc;
7462 }
7463 }
7464
7465 return builder.getInt64(dl.getTypeSizeInBits(type) / 8);
7466}
7467
7468// Convert the MLIR map flag set to the runtime map flag set for embedding
7469// in LLVM-IR. This is important as the two bit-flag lists do not correspond
7470// 1-to-1 as there's flags the runtime doesn't care about and vice versa.
7471// Certain flags are discarded here such as RefPtee and co.
7472static llvm::omp::OpenMPOffloadMappingFlags
7473convertClauseMapFlags(omp::ClauseMapFlags mlirFlags) {
7474 const bool hasExplicitMap =
7475 (mlirFlags & ~omp::ClauseMapFlags::is_device_ptr) !=
7476 omp::ClauseMapFlags::none;
7477
7478 llvm::omp::OpenMPOffloadMappingFlags mapType =
7479 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE;
7480
7481 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::to))
7482 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TO;
7483
7484 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::from))
7485 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_FROM;
7486
7487 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::always))
7488 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ALWAYS;
7489
7490 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::del))
7491 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_DELETE;
7492
7493 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::return_param))
7494 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
7495
7496 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::priv))
7497 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_PRIVATE;
7498
7499 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::literal))
7500 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL;
7501
7502 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::implicit))
7503 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_IMPLICIT;
7504
7505 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::close))
7506 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_CLOSE;
7507
7508 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::present))
7509 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_PRESENT;
7510
7511 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::ompx_hold))
7512 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_OMPX_HOLD;
7513
7514 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::attach))
7515 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH;
7516
7517 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::target_param))
7518 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TARGET_PARAM;
7519
7520 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::is_device_ptr)) {
7521 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TARGET_PARAM;
7522 if (!hasExplicitMap)
7523 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL;
7524 }
7525
7526 return mapType;
7527}
7528
7529static StringRef getMapClauseName(omp::MapInfoOp mapOp) {
7530 if (StringAttr name = mapOp.getNameAttr())
7531 return name.getValue();
7532 return {};
7533}
7534
7536 MapInfoData &mapData, SmallVectorImpl<Value> &mapVars,
7537 LLVM::ModuleTranslation &moduleTranslation, DataLayout &dl,
7538 llvm::IRBuilderBase &builder, ArrayRef<Value> useDevPtrOperands = {},
7539 ArrayRef<Value> useDevAddrOperands = {},
7540 ArrayRef<Value> hasDevAddrOperands = {}) {
7541
7542 auto checkRefPtrOrPteeMapWithAttach = [](omp::ClauseMapFlags mapType) {
7543 bool hasRefType =
7544 bitEnumContainsAll(mapType, omp::ClauseMapFlags::ref_ptr) ||
7545 bitEnumContainsAll(mapType, omp::ClauseMapFlags::ref_ptee);
7546 return hasRefType &&
7547 bitEnumContainsAll(mapType, omp::ClauseMapFlags::attach);
7548 };
7549
7550 auto checkIsAMember = [](const auto &mapVars, auto mapOp) {
7551 // Check if this is a member mapping and correctly assign that it is, if
7552 // it is a member of a larger object.
7553 // TODO: Need better handling of members, and distinguishing of members
7554 // that are implicitly allocated on device vs explicitly passed in as
7555 // arguments.
7556 // TODO: May require some further additions to support nested record
7557 // types, i.e. member maps that can have member maps.
7558 for (Value mapValue : mapVars) {
7559 auto map = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
7560 for (auto member : map.getMembers())
7561 if (member == mapOp)
7562 return true;
7563 }
7564 return false;
7565 };
7566
7567 // Process MapOperands
7568 for (Value mapValue : mapVars) {
7569 auto mapOp = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
7570 bool isAttachStyleMap =
7571 checkRefPtrOrPteeMapWithAttach(mapOp.getMapType()) ||
7572 isPrivatizeableAttachMap(mapOp.getMapType());
7573 Value offloadPtr = (mapOp.getVarPtrPtr() && !isAttachStyleMap)
7574 ? mapOp.getVarPtrPtr()
7575 : mapOp.getVarPtr();
7576 mapData.OriginalValue.push_back(moduleTranslation.lookupValue(offloadPtr));
7577 mapData.Pointers.push_back(
7578 isAttachStyleMap ? moduleTranslation.lookupValue(mapOp.getVarPtrPtr())
7579 : mapData.OriginalValue.back());
7580
7581 if (llvm::Value *refPtr =
7582 getRefPtrIfDeclareTarget(offloadPtr, moduleTranslation)) {
7583 mapData.IsDeclareTarget.push_back(true);
7584 mapData.BasePointers.push_back(refPtr);
7585 } else if (isDeclareTargetTo(offloadPtr)) {
7586 mapData.IsDeclareTarget.push_back(true);
7587 mapData.BasePointers.push_back(mapData.OriginalValue.back());
7588 } else { // regular mapped variable
7589 mapData.IsDeclareTarget.push_back(false);
7590 mapData.BasePointers.push_back(mapData.OriginalValue.back());
7591 }
7592
7593 // In every situation we currently have if we have a varPtrPtr present
7594 // we wish to utilise it's type for the base type, main cases are
7595 // currently Fortran descriptor base address maps and attach maps.
7596 mapData.BaseType.push_back(moduleTranslation.convertType(
7597 mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtrType().value()
7598 : mapOp.getVarPtrType()));
7599
7600 // For the attach map cases, it's a little odd, as we effectively have to
7601 // utilise the base address (including all bounds offsets) for the pointer
7602 // field, the pointer address for the base address field, and the pointer
7603 // not the data (base addresses) size. So we end up with a mix of base
7604 // types and sizes we wish to insert here.
7605 mlir::Type sizeType = (isAttachStyleMap || !mapOp.getVarPtrPtr())
7606 ? mapOp.getVarPtrType()
7607 : mapOp.getVarPtrPtrType().value();
7608 mapData.Sizes.push_back(getSizeInBytes(
7609 dl, sizeType, isAttachStyleMap ? nullptr : mapOp,
7610 mapData.Pointers.back(), moduleTranslation.convertType(sizeType),
7611 builder, moduleTranslation));
7612 mapData.MapClause.push_back(mapOp.getOperation());
7613 mapData.Types.push_back(convertClauseMapFlags(mapOp.getMapType()));
7614 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7615 mapData.HasAttachPtr.push_back(false);
7616 mapData.Names.push_back(LLVM::createMappingInformation(
7617 mapOp.getLoc(), *moduleTranslation.getOpenMPBuilder(),
7618 getMapClauseName(mapOp)));
7619 mapData.DevicePointers.push_back(llvm::OpenMPIRBuilder::DeviceInfoTy::None);
7620 if (mapOp.getMapperId())
7621 mapData.Mappers.push_back(
7623 mapOp, mapOp.getMapperIdAttr()));
7624 else
7625 mapData.Mappers.push_back(nullptr);
7626 mapData.IsAMapping.push_back(true);
7627 mapData.IsAMember.push_back(checkIsAMember(mapVars, mapOp));
7628 }
7629
7630 auto findMapInfo = [&mapData](llvm::Value *val,
7631 llvm::OpenMPIRBuilder::DeviceInfoTy devInfoTy,
7632 size_t memberCount) {
7633 unsigned index = 0;
7634 bool found = false;
7635 for (llvm::Value *basePtr : mapData.OriginalValue) {
7636 auto mapOp = cast<omp::MapInfoOp>(mapData.MapClause[index]);
7637 // TODO: Currently we define an equivalent mapping as
7638 // the same base pointer and an equivalent member count, but
7639 // that is a loose definition. We may have to extend to check
7640 // for other fields (varPtrPtr/individual members being mapped).
7641 // Note: Attach maps are not the same as a normal data transfer
7642 // they specify to the runtime to perform an attach map and they
7643 // (at least at the moment) are never something we would aim to
7644 // return in a use_dev_* clause, so they are skipped in terms of
7645 // duplicate maps.
7646 bool isAttachMap =
7647 (mapData.Types[index] &
7648 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) ==
7649 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH;
7650 if (!isAttachMap && basePtr == val && mapData.IsAMapping[index] &&
7651 memberCount == mapOp.getMembers().size()) {
7652 found = true;
7653 mapData.Types[index] |=
7654 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
7655 mapData.DevicePointers[index] = devInfoTy;
7656 }
7657 index++;
7658 }
7659 return found;
7660 };
7661
7662 // Process useDevPtr(Addr)Operands
7663 auto addDevInfos = [&](const llvm::ArrayRef<Value> &useDevOperands,
7664 llvm::OpenMPIRBuilder::DeviceInfoTy devInfoTy) {
7665 for (Value mapValue : useDevOperands) {
7666 auto mapOp = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
7667 Value offloadPtr =
7668 mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtr() : mapOp.getVarPtr();
7669 llvm::Value *origValue = moduleTranslation.lookupValue(offloadPtr);
7670
7671 // Check if map info is already present for this entry.
7672 if (!findMapInfo(origValue, devInfoTy, mapOp.getMembers().size())) {
7673 mapData.OriginalValue.push_back(origValue);
7674 mapData.Pointers.push_back(mapData.OriginalValue.back());
7675 mapData.IsDeclareTarget.push_back(false);
7676 mapData.BasePointers.push_back(mapData.OriginalValue.back());
7677 mlir::Type baseTy = mapOp.getVarPtrPtr()
7678 ? mapOp.getVarPtrPtrType().value()
7679 : mapOp.getVarPtrType();
7680 mapData.BaseType.push_back(moduleTranslation.convertType(baseTy));
7681 mapData.Sizes.push_back(builder.getInt64(0));
7682 mapData.MapClause.push_back(mapOp.getOperation());
7683 mapData.Types.push_back(
7684 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM);
7685 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7686 mapData.HasAttachPtr.push_back(false);
7687 mapData.Names.push_back(LLVM::createMappingInformation(
7688 mapOp.getLoc(), *moduleTranslation.getOpenMPBuilder(),
7689 getMapClauseName(mapOp)));
7690 mapData.DevicePointers.push_back(devInfoTy);
7691 mapData.Mappers.push_back(nullptr);
7692 mapData.IsAMapping.push_back(false);
7693 mapData.IsAMember.push_back(checkIsAMember(useDevOperands, mapOp));
7694 }
7695 }
7696 };
7697
7698 addDevInfos(useDevAddrOperands, llvm::OpenMPIRBuilder::DeviceInfoTy::Address);
7699 addDevInfos(useDevPtrOperands, llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer);
7700
7701 for (Value mapValue : hasDevAddrOperands) {
7702 auto mapOp = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
7703 Value offloadPtr =
7704 mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtr() : mapOp.getVarPtr();
7705 llvm::Value *origValue = moduleTranslation.lookupValue(offloadPtr);
7706 auto mapType = convertClauseMapFlags(mapOp.getMapType());
7707 auto mapTypeAlways = llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ALWAYS;
7708 bool isDevicePtr =
7709 (mapOp.getMapType() & omp::ClauseMapFlags::is_device_ptr) !=
7710 omp::ClauseMapFlags::none;
7711
7712 mapData.OriginalValue.push_back(origValue);
7713 mapData.BasePointers.push_back(origValue);
7714 mapData.Pointers.push_back(origValue);
7715 mapData.IsDeclareTarget.push_back(false);
7716
7717 mlir::Type baseTy = mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtrType().value()
7718 : mapOp.getVarPtrType();
7719 mapData.BaseType.push_back(moduleTranslation.convertType(baseTy));
7720 mapData.Sizes.push_back(builder.getInt64(dl.getTypeSize(baseTy)));
7721
7722 mapData.MapClause.push_back(mapOp.getOperation());
7723 if (llvm::to_underlying(mapType & mapTypeAlways)) {
7724 // Descriptors are mapped with the ALWAYS flag, since they can get
7725 // rematerialized, so the address of the decriptor for a given object
7726 // may change from one place to another.
7727 mapData.Types.push_back(mapType);
7728 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7729 mapData.HasAttachPtr.push_back(false);
7730 // Technically it's possible for a non-descriptor mapping to have
7731 // both has-device-addr and ALWAYS, so lookup the mapper in case it
7732 // exists.
7733 if (mapOp.getMapperId()) {
7734 mapData.Mappers.push_back(
7736 mapOp, mapOp.getMapperIdAttr()));
7737 } else {
7738 mapData.Mappers.push_back(nullptr);
7739 }
7740 } else {
7741 // For is_device_ptr we need the map type to propagate so the runtime
7742 // can materialize the device-side copy of the pointer container.
7743 mapData.Types.push_back(
7744 isDevicePtr ? mapType
7745 : llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL);
7746 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7747 mapData.HasAttachPtr.push_back(false);
7748 mapData.Mappers.push_back(nullptr);
7749 }
7750 mapData.Names.push_back(LLVM::createMappingInformation(
7751 mapOp.getLoc(), *moduleTranslation.getOpenMPBuilder(),
7752 getMapClauseName(mapOp)));
7753 mapData.DevicePointers.push_back(
7754 isDevicePtr ? llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer
7755 : llvm::OpenMPIRBuilder::DeviceInfoTy::Address);
7756 mapData.IsAMapping.push_back(false);
7757 mapData.IsAMember.push_back(checkIsAMember(hasDevAddrOperands, mapOp));
7758 }
7759}
7760
7761static int getMapDataMemberIdx(MapInfoData &mapData, omp::MapInfoOp memberOp) {
7762 auto *res = llvm::find(mapData.MapClause, memberOp);
7763 assert(res != mapData.MapClause.end() &&
7764 "MapInfoOp for member not found in MapData, cannot return index");
7765 return std::distance(mapData.MapClause.begin(), res);
7766}
7767
7769 omp::MapInfoOp mapInfo, bool first = true) {
7770 ArrayAttr indexAttr = mapInfo.getMembersIndexAttr();
7771 llvm::SmallVector<size_t> occludedChildren;
7772 llvm::sort(
7773 indices.begin(), indices.end(), [&](const size_t a, const size_t b) {
7774 // Bail early if we are asked to look at the same index. If we do not
7775 // bail early, we can end up mistakenly adding indices to
7776 // occludedChildren. This can occur with some types of libc++ hardening.
7777 if (a == b)
7778 return false;
7779
7780 auto memberIndicesA = cast<ArrayAttr>(indexAttr[a]);
7781 auto memberIndicesB = cast<ArrayAttr>(indexAttr[b]);
7782
7783 for (auto it : llvm::zip(memberIndicesA, memberIndicesB)) {
7784 int64_t aIndex = mlir::cast<IntegerAttr>(std::get<0>(it)).getInt();
7785 int64_t bIndex = mlir::cast<IntegerAttr>(std::get<1>(it)).getInt();
7786
7787 if (aIndex == bIndex)
7788 continue;
7789
7790 if (aIndex < bIndex)
7791 return first;
7792
7793 if (aIndex > bIndex)
7794 return !first;
7795 }
7796
7797 // Iterated up until the end of the smallest member and
7798 // they were found to be equal up to that point, so select
7799 // the member with the lowest index count, so the "parent"
7800 bool memberAParent = memberIndicesA.size() < memberIndicesB.size();
7801 if (memberAParent)
7802 occludedChildren.push_back(b);
7803 else
7804 occludedChildren.push_back(a);
7805 return memberAParent;
7806 });
7807
7808 for (auto v : occludedChildren)
7809 indices.erase(std::remove(indices.begin(), indices.end(), v),
7810 indices.end());
7811}
7812
7813static omp::MapInfoOp getFirstOrLastMappedMemberPtr(omp::MapInfoOp mapInfo,
7814 bool first) {
7815 ArrayAttr indexAttr = mapInfo.getMembersIndexAttr();
7816 // Only 1 member has been mapped, we can return it.
7817 if (indexAttr.size() == 1)
7818 return cast<omp::MapInfoOp>(mapInfo.getMembers()[0].getDefiningOp());
7819 llvm::SmallVector<size_t> indices(indexAttr.size());
7820 std::iota(indices.begin(), indices.end(), 0);
7821 sortMapIndices(indices, mapInfo, first);
7822 return llvm::cast<omp::MapInfoOp>(
7823 mapInfo.getMembers()[indices.front()].getDefiningOp());
7824}
7825
7826/// This function calculates the array/pointer offset for map data provided
7827/// with bounds operations, e.g. when provided something like the following:
7828///
7829/// Fortran
7830/// map(tofrom: array(2:5, 3:2))
7831///
7832/// We must calculate the initial pointer offset to pass across, this function
7833/// performs this using bounds.
7834///
7835/// TODO/WARNING: This only supports Fortran's column major indexing currently
7836/// as is noted in the note below and comments in the function, we must extend
7837/// this function when we add a C++ frontend.
7838/// NOTE: which while specified in row-major order it currently needs to be
7839/// flipped for Fortran's column order array allocation and access (as
7840/// opposed to C++'s row-major, hence the backwards processing where order is
7841/// important). This is likely important to keep in mind for the future when
7842/// we incorporate a C++ frontend, both frontends will need to agree on the
7843/// ordering of generated bounds operations (one may have to flip them) to
7844/// make the below lowering frontend agnostic. The offload size
7845/// calcualtion may also have to be adjusted for C++.
7846static std::vector<llvm::Value *>
7848 llvm::IRBuilderBase &builder, bool isArrayTy,
7849 OperandRange bounds, bool &isByteOffset) {
7850 std::vector<llvm::Value *> idx;
7851 isByteOffset = false;
7852 // There's no bounds to calculate an offset from, we can safely
7853 // ignore and return no indices.
7854 if (bounds.empty())
7855 return idx;
7856
7857 // If we have an array type, then we have its type so can treat it as a
7858 // normal GEP instruction where the bounds operations are simply indexes
7859 // into the array. We currently do reverse order of the bounds, which
7860 // I believe leans more towards Fortran's column-major in memory.
7861 if (isArrayTy) {
7862 idx.push_back(builder.getInt64(0));
7863 for (int i = bounds.size() - 1; i >= 0; --i) {
7864 if (auto boundOp = dyn_cast_if_present<omp::MapBoundsOp>(
7865 bounds[i].getDefiningOp())) {
7866 idx.push_back(moduleTranslation.lookupValue(boundOp.getLowerBound()));
7867 }
7868 }
7869 } else {
7870 // Check if Fortran descriptors provide strides in bytes
7871 llvm::Value *offset = builder.getInt64(0);
7872 for (Value bound : bounds) {
7873 auto boundOp =
7874 dyn_cast_if_present<omp::MapBoundsOp>(bound.getDefiningOp());
7875 if (!boundOp || !boundOp.getStride())
7876 continue;
7877 // Fortran descriptors provide strides in bytes; when that's the case the
7878 // accumulated offset is a byte offset and must be consumed with a byte
7879 // (i8) GEP at the call site to avoid double-scaling by the element size.
7880 isByteOffset = boundOp.getStrideInBytes();
7881 }
7882 if (isByteOffset) {
7883 for (Value bound : bounds) {
7884 auto boundOp =
7885 dyn_cast_if_present<omp::MapBoundsOp>(bound.getDefiningOp());
7886 if (!boundOp || !boundOp.getStride())
7887 continue;
7888 // If we do not have an array type, but we have bounds, then we're
7889 // dealing with a pointer that's being treated like an array
7890 // and we have the underlying type e.g. an i32, or f64 etc,
7891 // e.g. a fortran descriptor base address (pointer pointing to
7892 // the actual data) and we know that the offset stride is expressed
7893 // in bytes, so we must calculate the offset to the first mapped
7894 // element as a single linear index into that data.
7895 //
7896 // Each bound carries the stride between consecutive elements along its
7897 // dimension, so the linear offset to the section's origin is:
7898 //
7899 // offset += lower_bound[d] * stride[d]
7900 llvm::Value *lb =
7901 moduleTranslation.lookupValue(boundOp.getLowerBound());
7902 llvm::Value *stride =
7903 moduleTranslation.lookupValue(boundOp.getStride());
7904 offset = builder.CreateAdd(offset, builder.CreateMul(lb, stride));
7905 }
7906 idx.push_back(offset);
7907 } else {
7908 for (int i = bounds.size() - 1; i >= 0; --i) {
7909 if (auto boundOp = dyn_cast_if_present<omp::MapBoundsOp>(
7910 bounds[i].getDefiningOp())) {
7911 // If the stride in bytes is unknown, so we must calculate the
7912 // offset using a single index which the following loop attempts to
7913 // compute using the standard column-major algorithm e.g for a 3D
7914 // array:
7915 //
7916 // ((((c_idx * b_len) + b_idx) * a_len) + a_idx)
7917 //
7918 // It is of note that it's doing column-major rather than row-major at
7919 // the moment, but having a way for the frontend to indicate which
7920 // major format to use or standardizing/canonicalizing the order of
7921 // the bounds to compute the offset may be useful in the future when
7922 // there's other frontends with different formats.
7923 if (i == ((int)bounds.size() - 1))
7924 idx.emplace_back(
7925 moduleTranslation.lookupValue(boundOp.getLowerBound()));
7926 else
7927 idx.back() = builder.CreateAdd(
7928 builder.CreateMul(idx.back(), moduleTranslation.lookupValue(
7929 boundOp.getExtent())),
7930 moduleTranslation.lookupValue(boundOp.getLowerBound()));
7931 }
7932 }
7933 }
7934 }
7935
7936 return idx;
7937}
7938
7940 llvm::transform(values, std::back_inserter(ints), [](Attribute value) {
7941 return cast<IntegerAttr>(value).getInt();
7942 });
7943}
7944
7945// Gathers members that are overlapping in the parent, excluding members that
7946// themselves overlap, keeping the top-most (closest to parents level) map.
7947static void
7949 omp::MapInfoOp parentOp) {
7950 // No members mapped, no overlaps.
7951 if (parentOp.getMembers().empty())
7952 return;
7953
7954 // Single member, we can insert and return early.
7955 if (parentOp.getMembers().size() == 1) {
7956 overlapMapDataIdxs.push_back(0);
7957 return;
7958 }
7959
7960 ArrayAttr indexAttr = parentOp.getMembersIndexAttr();
7961 size_t numMembers = indexAttr.size();
7962
7963 // Pre-convert all member indices to integer arrays for efficient comparison.
7964 llvm::SmallVector<llvm::SmallVector<int64_t>> memberIndices(numMembers);
7965 for (auto [i, indicesAttr] : llvm::enumerate(indexAttr))
7966 getAsIntegers(cast<ArrayAttr>(indicesAttr), memberIndices[i]);
7967
7968 // For each member, check if it's superseded by another (shorter prefix)
7969 // member. If member j's indices are a prefix of member i's indices, then
7970 // i is a child of j and should be skipped. e.g. if member [0] is mapped,
7971 // we skip members [0,1], [0,2], etc.
7972 llvm::SmallDenseSet<size_t> skipIndices;
7973 for (size_t i = 0; i < numMembers; ++i) {
7974 const auto &iIndices = memberIndices[i];
7975 for (size_t j = 0; j < numMembers; ++j) {
7976 if (i == j)
7977 continue;
7978 const auto &jIndices = memberIndices[j];
7979 // If j's indices are a strict prefix of i's indices, skip i
7980 if (jIndices.size() < iIndices.size() &&
7981 std::equal(jIndices.begin(), jIndices.end(), iIndices.begin())) {
7982 skipIndices.insert(i);
7983 break; // No need to check other potential parents
7984 }
7985 }
7986 }
7987
7988 // Collect indices of members that are not superseded by a parent.
7989 for (size_t i = 0; i < numMembers; ++i)
7990 if (!skipIndices.contains(i))
7991 overlapMapDataIdxs.push_back(i);
7992}
7993
7994/// This function handles the insertion of a single item of map data from
7995/// MapInfoData into the OMPIRBuilder's MapInfo list. Utilising this function
7996/// means the map being inserted can be treated as a non-parent map entity,
7997/// if the memberOfFlag is set then the map being inserted is treated as
7998/// a member map of a larger entity. The insertion into the MapInfo list of
7999/// the OMPIRBuilder can vary based on a number of factors, such as if it's
8000/// a ref_ptr or ref_ptee map, if it's a member of a record, what construct
8001/// the map belongs to and the various map type bit flags that are set for
8002/// the map.
8003static void
8004processIndividualMap(llvm::IRBuilderBase &builder,
8005 llvm::OpenMPIRBuilder &ompBuilder, MapInfoData &mapData,
8006 size_t mapDataIdx, MapInfosTy &combinedInfo,
8007 TargetDirectiveEnumTy targetDirective,
8008 llvm::omp::OpenMPOffloadMappingFlags memberOfFlag =
8009 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE,
8010 bool isTargetParam = true, int mapDataParentIdx = -1) {
8011 auto mapFlag = mapData.Types[mapDataIdx];
8012 auto mapInfoOp = llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIdx]);
8013
8014 bool isPtrTy = checkIfPointerMap(mapInfoOp);
8015 bool isAttachMap = ((convertClauseMapFlags(mapInfoOp.getMapType()) &
8016 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) ==
8017 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH);
8018
8019 // Declare target variables are not passed to the kernel, and for the moment
8020 // attach maps are not passed to the kernel. However, it is possible to create
8021 // attach maps that transfer data and thus can be kernel arguments, but our
8022 // existing frontend does not do this.
8023 if (isTargetParam &&
8024 (targetDirective == TargetDirectiveEnumTy::Target &&
8025 !mapData.IsDeclareTarget[mapDataIdx]) &&
8026 !isAttachMap)
8027 mapFlag |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TARGET_PARAM;
8028
8029 if (mapInfoOp.getMapCaptureType() == omp::VariableCaptureKind::ByCopy &&
8030 !isPtrTy)
8031 mapFlag |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL;
8032
8033 // If we have a pointer and it's part of a MEMBER_OF mapping we do not apply
8034 // MEMBER_OF, as the runtime currently has a work-around that utilises
8035 // MEMBER_OF to prevent reference updating in certain scenarios instead of
8036 // target_param. However, this causes a noticeable issue in cases where we
8037 // map some data (Fortran descriptor primarily at the moment), alter it on
8038 // the host, and then expect it to not be updated in a subsequent implicit map
8039 // (such as an implicit map on a target).
8040 if (memberOfFlag != llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE) {
8041 if (!isPtrTy && !isAttachMap)
8042 ompBuilder.setCorrectMemberOfFlag(mapFlag, memberOfFlag);
8043
8044 // The return parameter should be the over-riding parent in cases where we
8045 // have a return parameter that is echoed to all members, the main case of
8046 // this currently is with fortran descriptors. It may need more finessing
8047 // for C/C++ in the future or descriptors that are members of derived
8048 // types.
8049 mapFlag &= ~llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
8050 }
8051
8052 // We apply MAP_PTR_AND_OBJ when within a declare mapper object as it enforces
8053 // MEMBER_OF mappings on maps that are passed the initial nesting depth, which
8054 // includes pointed to data and attach members, both of which are technically
8055 // not part of the main object. This has the side effect of causing early
8056 // map-backs in certain cases where an implicit declare mapper has been
8057 // emitted for a target region. Applying MAP_PTR_AND_OBJ in these situations
8058 // circumvents this.
8059 if (isPtrTy && !isAttachMap && mapData.IsDeclareTarget[mapDataIdx])
8060 mapFlag |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_PTR_AND_OBJ;
8061
8062 // if we're provided a mapDataParentIdx, then the data being mapped is
8063 // part of a larger object (in a parent <-> member mapping) and in this
8064 // case our BasePointer should be the parent. Except in the edge case
8065 // where we are mapping pointee data, where we try staying close to
8066 // what Clang currently does and utilise the regular base pointer of the
8067 // data.
8068 bool isRefPtee =
8069 !bitEnumContainsAll(mapInfoOp.getMapType(),
8070 omp::ClauseMapFlags::ref_ptr) &&
8071 bitEnumContainsAll(mapInfoOp.getMapType(), omp::ClauseMapFlags::ref_ptee);
8072 bool isRefPtrPtee = bitEnumContainsAll(mapInfoOp.getMapType(),
8073 omp::ClauseMapFlags::ref_ptr |
8074 omp::ClauseMapFlags::ref_ptee);
8075
8076 if (!mapInfoOp->getParentOfType<omp::DeclareMapperOp>() &&
8077 mapDataParentIdx >= 0 && !(isRefPtee || (isRefPtrPtee && isPtrTy))) {
8078 combinedInfo.BasePointers.emplace_back(
8079 mapData.BasePointers[mapDataParentIdx]);
8080 } else {
8081 combinedInfo.BasePointers.emplace_back(mapData.BasePointers[mapDataIdx]);
8082 }
8083
8084 combinedInfo.Pointers.emplace_back(mapData.Pointers[mapDataIdx]);
8085 combinedInfo.DevicePointers.emplace_back(
8086 memberOfFlag != llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE
8087 ? llvm::OpenMPIRBuilder::DeviceInfoTy::None
8088 : mapData.DevicePointers[mapDataIdx]);
8089 combinedInfo.Mappers.emplace_back(mapData.Mappers[mapDataIdx]);
8090 combinedInfo.Names.emplace_back(mapData.Names[mapDataIdx]);
8091 combinedInfo.Types.emplace_back(mapFlag);
8092 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
8093 combinedInfo.HasAttachPtr.emplace_back(false);
8094 // A privatized attach map needs storage for the pointer or descriptor even
8095 // when its pointee is null. Its size describes that storage, not the pointee,
8096 // and is needed by the runtime for corresponding-pointer-initialization.
8097 combinedInfo.Sizes.emplace_back(
8098 isPtrTy && !isPrivatizeableAttachMap(mapInfoOp.getMapType())
8099 ? builder.CreateSelect(
8100 builder.CreateIsNull(mapData.Pointers[mapDataIdx]),
8101 builder.getInt64(0), mapData.Sizes[mapDataIdx])
8102 : mapData.Sizes[mapDataIdx]);
8103}
8104
8105// This creates two insertions into the MapInfosTy data structure for the
8106// "parent" of a set of members, (usually a container e.g.
8107// class/structure/derived type) when subsequent members have also been
8108// explicitly mapped on the same map clause. Certain types, such as Fortran
8109// descriptors are mapped like this as well, however, the members are
8110// implicit as far as a user is concerned, but we must explicitly map them
8111// internally.
8112//
8113// This function also returns the memberOfFlag for this particular parent,
8114// which is utilised in subsequent member mappings (by modifying there map type
8115// with it) to indicate that a member is part of this parent and should be
8116// treated by the runtime as such. Important to achieve the correct mapping.
8117//
8118// This function borrows a lot from Clang's emitCombinedEntry function
8119// inside of CGOpenMPRuntime.cpp
8121 LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder,
8122 llvm::OpenMPIRBuilder &ompBuilder, DataLayout &dl, MapInfosTy &combinedInfo,
8123 MapInfoData &mapData, uint64_t mapDataIndex,
8124 llvm::omp::OpenMPOffloadMappingFlags memberOfFlag,
8125 TargetDirectiveEnumTy targetDirective) {
8126 using MapFlags = llvm::omp::OpenMPOffloadMappingFlags;
8127 assert(!ompBuilder.Config.isTargetDevice() &&
8128 "function only supported for host device codegen");
8129 auto parentClause =
8130 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
8131 auto *parentMapper = mapData.Mappers[mapDataIndex];
8132
8133 // Map the first segment of the parent. If a user-defined mapper is attached,
8134 // include the parent's to/from-style bits (and common modifiers) in this
8135 // base entry so the mapper receives correct copy semantics via its 'type'
8136 // parameter. Also keep TARGET_PARAM when required for kernel arguments.
8137 MapFlags baseFlag = (targetDirective == TargetDirectiveEnumTy::Target &&
8138 !mapData.IsDeclareTarget[mapDataIndex])
8139 ? MapFlags::OMP_MAP_TARGET_PARAM
8140 : MapFlags::OMP_MAP_NONE;
8141
8142 if (parentMapper) {
8143 // Preserve relevant map-type bits from the parent clause. These include
8144 // the copy direction (TO/FROM), as well as commonly used modifiers that
8145 // should be visible to the mapper for correct behaviour.
8146 MapFlags parentFlags = mapData.Types[mapDataIndex];
8147 MapFlags preserve = MapFlags::OMP_MAP_TO | MapFlags::OMP_MAP_FROM |
8148 MapFlags::OMP_MAP_ALWAYS | MapFlags::OMP_MAP_CLOSE |
8149 MapFlags::OMP_MAP_PRESENT |
8150 MapFlags::OMP_MAP_OMPX_HOLD |
8151 MapFlags::OMP_MAP_IMPLICIT;
8152 baseFlag |= (parentFlags & preserve);
8153 } else {
8154 MapFlags parentFlags = mapData.Types[mapDataIndex];
8155 MapFlags preserve = MapFlags::OMP_MAP_TO | MapFlags::OMP_MAP_FROM |
8156 MapFlags::OMP_MAP_PRESENT |
8157 MapFlags::OMP_MAP_RETURN_PARAM |
8158 MapFlags::OMP_MAP_IMPLICIT;
8159 baseFlag |= (parentFlags & preserve);
8160 }
8161
8162 combinedInfo.Types.emplace_back(baseFlag);
8163 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
8164 combinedInfo.HasAttachPtr.emplace_back(false);
8165 combinedInfo.DevicePointers.emplace_back(
8166 mapData.DevicePointers[mapDataIndex]);
8167 // Only attach the mapper to the base entry when we are mapping the whole
8168 // parent. Combined/segment entries must not carry a mapper; otherwise the
8169 // mapper can be invoked with a partial size, which is undefined behaviour.
8170 combinedInfo.Mappers.emplace_back(
8171 parentMapper && !parentClause.getPartialMap() ? parentMapper : nullptr);
8172 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
8173 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder,
8174 getMapClauseName(cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]))));
8175 combinedInfo.BasePointers.emplace_back(mapData.BasePointers[mapDataIndex]);
8176
8177 // Calculate size of the parent object being mapped based on the
8178 // addresses at runtime, highAddr - lowAddr = size. This of course
8179 // doesn't factor in allocated data like pointers, hence the further
8180 // processing of members specified by users, or in the case of
8181 // Fortran pointers and allocatables, the mapping of the pointed to
8182 // data by the descriptor (which itself, is a structure containing
8183 // runtime information on the dynamically allocated data).
8184 llvm::Value *lowAddr, *highAddr;
8185 if (!parentClause.getPartialMap()) {
8186 lowAddr = builder.CreatePointerCast(mapData.Pointers[mapDataIndex],
8187 builder.getPtrTy());
8188 highAddr = builder.CreatePointerCast(
8189 builder.CreateConstGEP1_32(mapData.BaseType[mapDataIndex],
8190 mapData.Pointers[mapDataIndex], 1),
8191 builder.getPtrTy());
8192 combinedInfo.Pointers.emplace_back(mapData.Pointers[mapDataIndex]);
8193 } else {
8194 auto mapOp = dyn_cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
8195 int firstMemberIdx = getMapDataMemberIdx(
8196 mapData, getFirstOrLastMappedMemberPtr(mapOp, true));
8197 lowAddr = builder.CreatePointerCast(mapData.BasePointers[firstMemberIdx],
8198 builder.getPtrTy());
8199
8200 int lastMemberIdx = getMapDataMemberIdx(
8201 mapData, getFirstOrLastMappedMemberPtr(mapOp, false));
8202 auto lastMemberMapInfo =
8203 cast<omp::MapInfoOp>(mapData.MapClause[lastMemberIdx]);
8204
8205 // NOTE: Currently, for RefPtee the BaseType is set to the varPtrPtr field,
8206 // which is the pointer datas type and not the member within the structure
8207 // that it's part of, so we have to make sure we use the member type in this
8208 // case when calculating the parents size offsets.
8209 // TODO: May be good to extend MapInfoData to support tracking of both
8210 // VarPtr/VarPtrPtr BaseType's to better distinguish what's being used more
8211 // consistently.
8212 bool isRefPteeMap = bitEnumContainsAll(lastMemberMapInfo.getMapType(),
8213 omp::ClauseMapFlags::ref_ptee) &&
8214 !bitEnumContainsAll(lastMemberMapInfo.getMapType(),
8215 omp::ClauseMapFlags::ref_ptr);
8216 llvm::Type *castType = mapData.BaseType[lastMemberIdx];
8217 if (isRefPteeMap)
8218 castType =
8219 moduleTranslation.convertType(lastMemberMapInfo.getVarPtrType());
8220 highAddr = builder.CreatePointerCast(
8221 builder.CreateGEP(castType, mapData.BasePointers[lastMemberIdx],
8222 builder.getInt64(1)),
8223 builder.getPtrTy());
8224 combinedInfo.Pointers.emplace_back(mapData.BasePointers[firstMemberIdx]);
8225 }
8226
8227 llvm::Value *size = builder.CreateIntCast(
8228 builder.CreatePtrDiff(builder.getInt8Ty(), highAddr, lowAddr),
8229 builder.getInt64Ty(),
8230 /*isSigned=*/false);
8231 combinedInfo.Sizes.push_back(size);
8232
8233 // This creates the initial MEMBER_OF mapping that consists of
8234 // the parent/top level container (same as above effectively, except
8235 // with a fixed initial compile time size and separate maptype which
8236 // indicates the true mape type (tofrom etc.). This parent mapping is
8237 // only relevant if the structure in its totality is being mapped,
8238 // otherwise the above suffices.
8239 if (!parentClause.getPartialMap()) {
8240 // TODO: This will need to be expanded to include the whole host of logic
8241 // for the map flags that Clang currently supports (e.g. it should do some
8242 // further case specific flag modifications). For the moment, it handles
8243 // what we support as expected.
8244 MapFlags mapFlag = mapData.Types[mapDataIndex];
8245 bool hasMapClose = (MapFlags(mapFlag) & MapFlags::OMP_MAP_CLOSE) ==
8246 MapFlags::OMP_MAP_CLOSE;
8247 ompBuilder.setCorrectMemberOfFlag(mapFlag, memberOfFlag);
8248
8249 llvm::SmallVector<size_t> overlapIdxs;
8250 // Find all of the members that "overlap", i.e. occlude other members that
8251 // were mapped alongside the parent, e.g. member [0], occludes [0,1] and
8252 // [0,2], but not [1,0].
8253 getOverlappedMembers(overlapIdxs, parentClause);
8254
8255 // When we only have one overlap we skip the case that tries to segment the
8256 // mapping as best it can without creating holes, as the calculation is more
8257 // likely to have more overhead than anything we gain from mapping a smaller
8258 // chunk of data. This can be seen in cases where we are mapping Fortran
8259 // descriptors which are a special case of record type mapping.
8260 //
8261 // The cases for close and update are unique edge cases where the segmenting
8262 // does not play well with the runtime currently.
8263 if (targetDirective == TargetDirectiveEnumTy::TargetUpdate || hasMapClose ||
8264 overlapIdxs.size() == 1) {
8265 combinedInfo.Types.emplace_back(mapFlag);
8266 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
8267 combinedInfo.HasAttachPtr.emplace_back(false);
8268 combinedInfo.DevicePointers.emplace_back(
8269 mapData.DevicePointers[mapDataIndex]);
8270 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
8271 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder,
8273 cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]))));
8274 combinedInfo.BasePointers.emplace_back(
8275 mapData.BasePointers[mapDataIndex]);
8276 combinedInfo.Pointers.emplace_back(mapData.Pointers[mapDataIndex]);
8277 combinedInfo.Sizes.emplace_back(mapData.Sizes[mapDataIndex]);
8278 combinedInfo.Mappers.emplace_back(nullptr);
8279 } else {
8280 // We need to make sure the overlapped members are sorted in order of
8281 // lowest address to highest address.
8282 sortMapIndices(overlapIdxs, parentClause);
8283
8284 lowAddr = builder.CreatePointerCast(mapData.Pointers[mapDataIndex],
8285 builder.getPtrTy());
8286 highAddr = builder.CreatePointerCast(
8287 builder.CreateConstGEP1_32(mapData.BaseType[mapDataIndex],
8288 mapData.Pointers[mapDataIndex], 1),
8289 builder.getPtrTy());
8290
8291 // Currently, the return parameter should be the over-riding parent in
8292 // cases where we have a return parameter that is echoed to all members,
8293 // the main case of this currently is with fortran descriptors. It may
8294 // need more finessing for C/C++ in the future or descriptors that are
8295 // members of derived types.
8296 mapFlag &= ~llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
8297
8298 // TODO: We may want to skip arrays/array sections in this as Clang does.
8299 // It appears to be an optimisation rather than a necessity though,
8300 // but this requires further investigation. However, we would have to make
8301 // sure to not exclude maps with bounds that ARE pointers, as these are
8302 // processed as separate components, i.e. pointer + data.
8303 for (auto v : overlapIdxs) {
8304 auto mapDataOverlapIdx = getMapDataMemberIdx(
8305 mapData,
8306 cast<omp::MapInfoOp>(parentClause.getMembers()[v].getDefiningOp()));
8307 auto isPtrMap = checkIfPointerMap(
8308 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataOverlapIdx]));
8309 combinedInfo.Types.emplace_back(mapFlag);
8310 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
8311 combinedInfo.HasAttachPtr.emplace_back(false);
8312 combinedInfo.DevicePointers.emplace_back(
8313 llvm::OpenMPIRBuilder::DeviceInfoTy::None);
8314 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
8315 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder,
8317 cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]))));
8318 combinedInfo.BasePointers.emplace_back(
8319 mapData.BasePointers[mapDataIndex]);
8320 combinedInfo.Mappers.emplace_back(nullptr);
8321 combinedInfo.Pointers.emplace_back(lowAddr);
8322 auto sizeCalc = builder.CreateIntCast(
8323 builder.CreatePtrDiff(builder.getInt8Ty(),
8324 mapData.OriginalValue[mapDataOverlapIdx],
8325 lowAddr),
8326 builder.getInt64Ty(), /*isSigned=*/true);
8327 // In certain cases, we'll generate a size of 0 if we're not careful
8328 // (e.g. if lowAddr happens to be the first member), which isn't
8329 // correct, even if the runtimes is sometimes fine with it so, in these
8330 // scenarios we select the types size instead.
8331 llvm::DataLayout dataLayout = builder.getDataLayout();
8332 auto sizeSel = builder.CreateSelect(
8333 builder.CreateICmpNE(builder.getInt64(0), sizeCalc), sizeCalc,
8334 isPtrMap ? builder.getInt64(dataLayout.getPointerSize())
8335 : mapData.Sizes[mapDataOverlapIdx]);
8336 combinedInfo.Sizes.emplace_back(sizeSel);
8337 lowAddr = builder.CreateConstGEP1_32(
8338 isPtrMap ? builder.getPtrTy() : mapData.BaseType[mapDataOverlapIdx],
8339 mapData.BasePointers[mapDataOverlapIdx], 1);
8340 }
8341
8342 combinedInfo.Types.emplace_back(mapFlag);
8343 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
8344 combinedInfo.HasAttachPtr.emplace_back(false);
8345 combinedInfo.DevicePointers.emplace_back(
8346 llvm::OpenMPIRBuilder::DeviceInfoTy::None);
8347 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
8348 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder,
8350 cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]))));
8351 combinedInfo.BasePointers.emplace_back(
8352 mapData.BasePointers[mapDataIndex]);
8353 combinedInfo.Mappers.emplace_back(nullptr);
8354 combinedInfo.Pointers.emplace_back(lowAddr);
8355 combinedInfo.Sizes.emplace_back(builder.CreateIntCast(
8356 builder.CreatePtrDiff(builder.getInt8Ty(), highAddr, lowAddr),
8357 builder.getInt64Ty(), true));
8358 }
8359 }
8360}
8361
8363 llvm::IRBuilderBase &builder,
8364 llvm::OpenMPIRBuilder &ompBuilder,
8365 DataLayout &dl, MapInfosTy &combinedInfo,
8366 MapInfoData &mapData, uint64_t mapDataIndex,
8367 TargetDirectiveEnumTy targetDirective) {
8368 assert(!ompBuilder.Config.isTargetDevice() &&
8369 "function only supported for host device codegen");
8370
8371 auto parentClause =
8372 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
8373
8374 // If we have a partial map (no parent referenced in the map clauses of the
8375 // directive, only members) and only a single member, we do not need to bind
8376 // the map of the member to the parent, we can pass the member separately.
8377 if (parentClause.getMembers().size() == 1 && parentClause.getPartialMap()) {
8378 auto memberClause = llvm::cast<omp::MapInfoOp>(
8379 parentClause.getMembers()[0].getDefiningOp());
8380 int memberDataIdx = getMapDataMemberIdx(mapData, memberClause);
8381 // Note: Clang treats arrays with explicit bounds that fall into this
8382 // category as a parent with map case, however, it seems this isn't a
8383 // requirement, and processing them as an individual map is fine. So,
8384 // we will handle them as individual maps for the moment, as it's
8385 // difficult for us to check this as we always require bounds to be
8386 // specified currently and it's also marginally more optimal (single
8387 // map rather than two). The difference may come from the fact that
8388 // Clang maps array without bounds as pointers (which we do not
8389 // currently do), whereas we treat them as arrays in all cases
8390 // currently.
8392 builder, ompBuilder, mapData, memberDataIdx, combinedInfo,
8393 targetDirective,
8394 /*MemberOfFlag=*/llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE,
8395 /*isTargetParam=*/true, mapDataIndex);
8396 return;
8397 }
8398
8399 auto collectMapInfoIdxs =
8400 [&](llvm::SmallVectorImpl<int64_t> &mapsAndInfoIdx) {
8401 auto parentClause =
8402 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
8403 mapsAndInfoIdx.push_back(getMapDataMemberIdx(mapData, parentClause));
8404 for (auto member : parentClause.getMembers())
8405 mapsAndInfoIdx.push_back(getMapDataMemberIdx(
8406 mapData, llvm::cast<omp::MapInfoOp>(member.getDefiningOp())));
8407 };
8408
8409 llvm::SmallVector<int64_t> mapInfoIdx;
8410 collectMapInfoIdxs(mapInfoIdx);
8411
8412 llvm::omp::OpenMPOffloadMappingFlags memberOfFlag =
8413 ompBuilder.getMemberOfFlag(combinedInfo.Types.size());
8414
8415 // The first index is the parent map, the rest are its members. The parent
8416 // normally undergoes the standard parent-with-members mapping, contributing
8417 // the MEMBER_OF flag that binds each member to it. The one exception is a
8418 // privatizeable attach map (a privatized pointer/descriptor passed directly
8419 // as a kernel argument): here the parent is emitted as an individual map
8420 // instead, for the time being, as it's used only in pointer/allocatable to
8421 // array cases for the moment. This only ever applies to the parent, so it is
8422 // checked once here rather than inside the loop below.
8423 bool parentIsPrivatizeableAttach =
8424 isPrivatizeableAttachMap(parentClause.getMapType());
8425 for (auto [i, idx] : llvm::enumerate(mapInfoIdx)) {
8426 bool emitParentMap = i == 0 && !parentIsPrivatizeableAttach;
8427 if (emitParentMap) {
8428 mapParentWithMembers(moduleTranslation, builder, ompBuilder, dl,
8429 combinedInfo, mapData, idx, memberOfFlag,
8430 targetDirective);
8431 } else {
8433 builder, ompBuilder, mapData, idx, combinedInfo, targetDirective,
8434 parentIsPrivatizeableAttach
8435 ? llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE
8436 : memberOfFlag,
8437 /*isTargetParam=*/false, mapDataIndex);
8438 }
8439 }
8440}
8441
8442// This is a variation on Clang's GenerateOpenMPCapturedVars, which
8443// generates different operation (e.g. load/store) combinations for
8444// arguments to the kernel, based on map capture kinds which are then
8445// utilised in the combinedInfo in place of the original Map value.
8446static void
8447createAlteredByCaptureMap(MapInfoData &mapData,
8448 LLVM::ModuleTranslation &moduleTranslation,
8449 llvm::IRBuilderBase &builder) {
8450 assert(!moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
8451 "function only supported for host device codegen");
8452 for (size_t i = 0; i < mapData.MapClause.size(); ++i) {
8453 auto mapOp = cast<omp::MapInfoOp>(mapData.MapClause[i]);
8454 bool isAttachMap =
8455 ((convertClauseMapFlags(mapOp.getMapType()) &
8456 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) ==
8457 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH);
8458
8459 // If it's declare target, skip it, it's handled separately. However, if
8460 // it's declare target, and an attach map, we want to calculate the exact
8461 // address offset so that we attach correctly.
8462 if (!mapData.IsDeclareTarget[i] ||
8463 (mapData.IsDeclareTarget[i] && isAttachMap)) {
8464 omp::VariableCaptureKind captureKind = mapOp.getMapCaptureType();
8465 bool isPtrTy = checkIfPointerMap(mapOp);
8466
8467 // Currently handles array sectioning lowerbound case, but more
8468 // logic may be required in the future. Clang invokes EmitLValue,
8469 // which has specialised logic for special Clang types such as user
8470 // defines, so it is possible we will have to extend this for
8471 // structures or other complex types. As the general idea is that this
8472 // function mimics some of the logic from Clang that we require for
8473 // kernel argument passing from host -> device.
8474 switch (captureKind) {
8475 case omp::VariableCaptureKind::ByRef: {
8476 llvm::Value *newV = mapData.Pointers[i];
8477 bool isByteOffset = false;
8478 std::vector<llvm::Value *> offsetIdx = calculateBoundsOffset(
8479 moduleTranslation, builder, mapData.BaseType[i]->isArrayTy(),
8480 mapOp.getBounds(), isByteOffset);
8481 if (isPtrTy)
8482 newV = builder.CreateLoad(builder.getPtrTy(), newV);
8483
8484 if (!offsetIdx.empty())
8485 newV = builder.CreateInBoundsGEP(isByteOffset ? builder.getInt8Ty()
8486 : mapData.BaseType[i],
8487 newV, offsetIdx, "array_offset");
8488 mapData.Pointers[i] = newV;
8489 } break;
8490 case omp::VariableCaptureKind::ByCopy: {
8491 llvm::Type *type = mapData.BaseType[i];
8492 llvm::Value *newV;
8493 if (mapData.Pointers[i]->getType()->isPointerTy())
8494 newV = builder.CreateLoad(type, mapData.Pointers[i]);
8495 else
8496 newV = mapData.Pointers[i];
8497
8498 if (!isPtrTy) {
8499 auto curInsert = builder.saveIP();
8500 llvm::DebugLoc DbgLoc = builder.getCurrentDebugLocation();
8501 builder.restoreIP(findAllocInsertPoints(builder, moduleTranslation));
8502 auto *memTempAlloc =
8503 builder.CreateAlloca(builder.getPtrTy(), nullptr, ".casted");
8504 builder.SetCurrentDebugLocation(DbgLoc);
8505 builder.restoreIP(curInsert);
8506
8507 builder.CreateStore(newV, memTempAlloc);
8508 newV = builder.CreateLoad(builder.getPtrTy(), memTempAlloc);
8509 }
8510
8511 mapData.Pointers[i] = newV;
8512 mapData.BasePointers[i] = newV;
8513 } break;
8514 case omp::VariableCaptureKind::This:
8515 case omp::VariableCaptureKind::VLAType:
8516 mapData.MapClause[i]->emitOpError("Unhandled capture kind");
8517 break;
8518 }
8519 }
8520 }
8521}
8522
8523// Generate all map related information and fill the combinedInfo.
8524static void genMapInfos(llvm::IRBuilderBase &builder,
8525 LLVM::ModuleTranslation &moduleTranslation,
8526 DataLayout &dl, MapInfosTy &combinedInfo,
8527 MapInfoData &mapData,
8528 TargetDirectiveEnumTy targetDirective) {
8529 assert(!moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
8530 "function only supported for host device codegen");
8531 // We wish to modify some of the methods in which arguments are
8532 // passed based on their capture type by the target region, this can
8533 // involve generating new loads and stores, which changes the
8534 // MLIR value to LLVM value mapping, however, we only wish to do this
8535 // locally for the current function/target and also avoid altering
8536 // ModuleTranslation, so we remap the base pointer or pointer stored
8537 // in the map infos corresponding MapInfoData, which is later accessed
8538 // by genMapInfos and createTarget to help generate the kernel and
8539 // kernel arg structure. It primarily becomes relevant in cases like
8540 // bycopy, or byref range'd arrays. In the default case, we simply
8541 // pass thee pointer byref as both basePointer and pointer.
8542 createAlteredByCaptureMap(mapData, moduleTranslation, builder);
8543
8544 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8545
8546 // We operate under the assumption that all vectors that are
8547 // required in MapInfoData are of equal lengths (either filled with
8548 // default constructed data or appropiate information) so we can
8549 // utilise the size from any component of MapInfoData, if we can't
8550 // something is missing from the initial MapInfoData construction.
8551 for (size_t i = 0; i < mapData.MapClause.size(); ++i) {
8552 if (mapData.IsAMember[i])
8553 continue;
8554
8555 auto mapInfoOp = dyn_cast<omp::MapInfoOp>(mapData.MapClause[i]);
8556 if (!mapInfoOp.getMembers().empty()) {
8557 processMapWithMembersOf(moduleTranslation, builder, *ompBuilder, dl,
8558 combinedInfo, mapData, i, targetDirective);
8559 continue;
8560 }
8561
8562 processIndividualMap(builder, *ompBuilder, mapData, i, combinedInfo,
8563 targetDirective);
8564 }
8565}
8566
8567static llvm::Expected<llvm::Function *>
8568emitUserDefinedMapper(Operation *declMapperOp, llvm::IRBuilderBase &builder,
8569 LLVM::ModuleTranslation &moduleTranslation,
8570 llvm::StringRef mapperFuncName,
8571 TargetDirectiveEnumTy targetDirective);
8572
8573static llvm::Expected<llvm::Function *>
8574getOrCreateUserDefinedMapperFunc(Operation *op, llvm::IRBuilderBase &builder,
8575 LLVM::ModuleTranslation &moduleTranslation,
8576 TargetDirectiveEnumTy targetDirective) {
8577 assert(!moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
8578 "function only supported for host device codegen");
8579 auto declMapperOp = cast<omp::DeclareMapperOp>(op);
8580 std::string mapperFuncName =
8581 moduleTranslation.getOpenMPBuilder()->createPlatformSpecificName(
8582 {"omp_mapper", declMapperOp.getSymName()});
8583
8584 if (auto *lookupFunc = moduleTranslation.lookupFunction(mapperFuncName))
8585 return lookupFunc;
8586
8587 // Recursive types can cause re-entrant mapper emission. The mapper function
8588 // is created by OpenMPIRBuilder before the callbacks run, so it may already
8589 // exist in the LLVM module even though it is not yet registered in the
8590 // ModuleTranslation mapping table. Reuse and register it to break the
8591 // recursion.
8592 if (llvm::Function *existingFunc =
8593 moduleTranslation.getLLVMModule()->getFunction(mapperFuncName)) {
8594 moduleTranslation.mapFunction(mapperFuncName, existingFunc);
8595 return existingFunc;
8596 }
8597
8598 return emitUserDefinedMapper(declMapperOp, builder, moduleTranslation,
8599 mapperFuncName, targetDirective);
8600}
8601
8602static llvm::Expected<llvm::Function *>
8603emitUserDefinedMapper(Operation *op, llvm::IRBuilderBase &builder,
8604 LLVM::ModuleTranslation &moduleTranslation,
8605 llvm::StringRef mapperFuncName,
8606 TargetDirectiveEnumTy targetDirective) {
8607 assert(!moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
8608 "function only supported for host device codegen");
8609 auto declMapperOp = cast<omp::DeclareMapperOp>(op);
8610 auto declMapperInfoOp = declMapperOp.getDeclareMapperInfo();
8611 if (failed(checkImplementationStatus(*declMapperInfoOp)))
8612 return llvm::make_error<PreviouslyReportedError>();
8613
8614 DataLayout dl = DataLayout(declMapperOp->getParentOfType<ModuleOp>());
8615 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8616 llvm::Type *varType = moduleTranslation.convertType(declMapperOp.getType());
8617 SmallVector<Value> mapVars = declMapperInfoOp.getMapVars();
8618
8619 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
8620
8621 // Fill up the arrays with all the mapped variables.
8622 MapInfosTy combinedInfo;
8623 auto genMapInfoCB =
8624 [&](InsertPointTy codeGenIP, llvm::Value *ptrPHI,
8625 llvm::Value *unused2) -> llvm::OpenMPIRBuilder::MapInfosOrErrorTy {
8626 builder.restoreIP(codeGenIP);
8627 moduleTranslation.mapValue(declMapperOp.getSymVal(), ptrPHI);
8628 moduleTranslation.mapBlock(&declMapperOp.getRegion().front(),
8629 builder.GetInsertBlock());
8630 if (failed(moduleTranslation.convertBlock(declMapperOp.getRegion().front(),
8631 /*ignoreArguments=*/true,
8632 builder)))
8633 return llvm::make_error<PreviouslyReportedError>();
8634 MapInfoData mapData;
8635 collectMapDataFromMapOperands(mapData, mapVars, moduleTranslation, dl,
8636 builder);
8637 genMapInfos(builder, moduleTranslation, dl, combinedInfo, mapData,
8638 targetDirective);
8639
8640 // Drop the mapping that is no longer necessary so that the same region
8641 // can be processed multiple times.
8642 moduleTranslation.forgetMapping(declMapperOp.getRegion());
8643 return combinedInfo;
8644 };
8645
8646 auto customMapperCB = [&](unsigned i) -> llvm::Expected<llvm::Function *> {
8647 if (!combinedInfo.Mappers[i])
8648 return nullptr;
8649 return getOrCreateUserDefinedMapperFunc(combinedInfo.Mappers[i], builder,
8650 moduleTranslation, targetDirective);
8651 };
8652
8653 llvm::Expected<llvm::Function *> newFn = ompBuilder->emitUserDefinedMapper(
8654 genMapInfoCB, varType, mapperFuncName, customMapperCB,
8655 /*PreserveMemberOfFlags=*/true);
8656 if (!newFn)
8657 return newFn.takeError();
8658 if ([[maybe_unused]] llvm::Function *mappedFunc =
8659 moduleTranslation.lookupFunction(mapperFuncName)) {
8660 assert(mappedFunc == *newFn &&
8661 "mapper function mapping disagrees with emitted function");
8662 } else {
8663 moduleTranslation.mapFunction(mapperFuncName, *newFn);
8664 }
8665 return *newFn;
8666}
8667
8668static llvm::Value *getSourceLocIdentFromOp(llvm::IRBuilderBase &builder,
8669 llvm::OpenMPIRBuilder &ompBuilder,
8670 Operation *op) {
8671 auto fileLoc = op->getLoc()->findInstanceOf<FileLineColLoc>();
8672 if (!fileLoc)
8673 return nullptr;
8674 uint32_t strSize;
8675 llvm::Function *parentFn = builder.GetInsertBlock()->getParent();
8676 llvm::StringRef fnName = parentFn ? parentFn->getName() : "";
8677 llvm::Constant *srcStr = LLVM::createSourceLocStrFromLocation(
8678 fileLoc, ompBuilder, fnName, strSize);
8679 return ompBuilder.getOrCreateIdent(srcStr, strSize);
8680}
8681
8682static LogicalResult
8683convertOmpTargetData(Operation *op, llvm::IRBuilderBase &builder,
8684 LLVM::ModuleTranslation &moduleTranslation) {
8685 llvm::Value *ifCond = nullptr;
8686 llvm::Value *deviceID = builder.getInt64(llvm::omp::OMP_DEVICEID_UNDEF);
8687 SmallVector<Value> mapVars;
8688 SmallVector<Value> useDevicePtrVars;
8689 SmallVector<Value> useDeviceAddrVars;
8690 llvm::omp::RuntimeFunction RTLFn;
8691 DataLayout DL = DataLayout(op->getParentOfType<ModuleOp>());
8692 TargetDirectiveEnumTy targetDirective = getTargetDirectiveEnumTyFromOp(op);
8693
8694 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8695 llvm::OpenMPIRBuilder::TargetDataInfo info(
8696 /*RequiresDevicePointerInfo=*/true,
8697 /*SeparateBeginEndCalls=*/true);
8698
8699 if (ompBuilder->Config.isTargetDevice())
8700 return op->emitOpError() << "not allowed in a target device";
8701
8702 bool isOffloadEntry = !ompBuilder->Config.TargetTriples.empty();
8703
8704 auto getDeviceID = [&](mlir::Value dev) -> llvm::Value * {
8705 llvm::Value *v = moduleTranslation.lookupValue(dev);
8706 return builder.CreateIntCast(v, builder.getInt64Ty(), /*isSigned=*/true);
8707 };
8708
8709 LogicalResult result =
8711 .Case([&](omp::TargetDataOp dataOp) {
8712 if (failed(checkImplementationStatus(*dataOp)))
8713 return failure();
8714
8715 if (auto ifVar = dataOp.getIfExpr())
8716 ifCond = moduleTranslation.lookupValue(ifVar);
8717
8718 if (mlir::Value devId = dataOp.getDevice())
8719 deviceID = getDeviceID(devId);
8720
8721 mapVars = dataOp.getMapVars();
8722 useDevicePtrVars = dataOp.getUseDevicePtrVars();
8723 useDeviceAddrVars = dataOp.getUseDeviceAddrVars();
8724 return success();
8725 })
8726 .Case([&](omp::TargetEnterDataOp enterDataOp) -> LogicalResult {
8727 if (failed(checkImplementationStatus(*enterDataOp)))
8728 return failure();
8729
8730 if (auto ifVar = enterDataOp.getIfExpr())
8731 ifCond = moduleTranslation.lookupValue(ifVar);
8732
8733 if (mlir::Value devId = enterDataOp.getDevice())
8734 deviceID = getDeviceID(devId);
8735
8736 RTLFn =
8737 enterDataOp.getNowait()
8738 ? llvm::omp::OMPRTL___tgt_target_data_begin_nowait_mapper
8739 : llvm::omp::OMPRTL___tgt_target_data_begin_mapper;
8740 mapVars = enterDataOp.getMapVars();
8741 info.HasNoWait = enterDataOp.getNowait();
8742 return success();
8743 })
8744 .Case([&](omp::TargetExitDataOp exitDataOp) -> LogicalResult {
8745 if (failed(checkImplementationStatus(*exitDataOp)))
8746 return failure();
8747
8748 if (auto ifVar = exitDataOp.getIfExpr())
8749 ifCond = moduleTranslation.lookupValue(ifVar);
8750
8751 if (mlir::Value devId = exitDataOp.getDevice())
8752 deviceID = getDeviceID(devId);
8753
8754 RTLFn = exitDataOp.getNowait()
8755 ? llvm::omp::OMPRTL___tgt_target_data_end_nowait_mapper
8756 : llvm::omp::OMPRTL___tgt_target_data_end_mapper;
8757 mapVars = exitDataOp.getMapVars();
8758 info.HasNoWait = exitDataOp.getNowait();
8759 return success();
8760 })
8761 .Case([&](omp::TargetUpdateOp updateDataOp) -> LogicalResult {
8762 if (failed(checkImplementationStatus(*updateDataOp)))
8763 return failure();
8764
8765 if (auto ifVar = updateDataOp.getIfExpr())
8766 ifCond = moduleTranslation.lookupValue(ifVar);
8767
8768 if (mlir::Value devId = updateDataOp.getDevice())
8769 deviceID = getDeviceID(devId);
8770
8771 RTLFn =
8772 updateDataOp.getNowait()
8773 ? llvm::omp::OMPRTL___tgt_target_data_update_nowait_mapper
8774 : llvm::omp::OMPRTL___tgt_target_data_update_mapper;
8775 mapVars = updateDataOp.getMapVars();
8776 info.HasNoWait = updateDataOp.getNowait();
8777 return success();
8778 })
8779 .DefaultUnreachable("unexpected operation");
8780
8781 if (failed(result))
8782 return failure();
8783 // Pretend we have IF(false) if we're not doing offload.
8784 if (!isOffloadEntry)
8785 ifCond = builder.getFalse();
8786
8787 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
8788 MapInfoData mapData;
8789 collectMapDataFromMapOperands(mapData, mapVars, moduleTranslation, DL,
8790 builder, useDevicePtrVars, useDeviceAddrVars);
8791
8792 // Fill up the arrays with all the mapped variables.
8793 MapInfosTy combinedInfo;
8794 auto genMapInfoCB = [&](InsertPointTy codeGenIP) -> MapInfosTy & {
8795 builder.restoreIP(codeGenIP);
8796 genMapInfos(builder, moduleTranslation, DL, combinedInfo, mapData,
8797 targetDirective);
8798 return combinedInfo;
8799 };
8800
8801 // Define a lambda to apply mappings between use_device_addr and
8802 // use_device_ptr base pointers, and their associated block arguments.
8803 auto mapUseDevice =
8804 [&moduleTranslation](
8805 llvm::OpenMPIRBuilder::DeviceInfoTy type,
8807 llvm::SmallVectorImpl<Value> &useDeviceVars, MapInfoData &mapInfoData,
8808 llvm::function_ref<llvm::Value *(llvm::Value *)> mapper = nullptr) {
8809 for (auto [arg, useDevVar] :
8810 llvm::zip_equal(blockArgs, useDeviceVars)) {
8811
8812 auto getMapBasePtr = [](omp::MapInfoOp mapInfoOp) {
8813 return mapInfoOp.getVarPtrPtr() ? mapInfoOp.getVarPtrPtr()
8814 : mapInfoOp.getVarPtr();
8815 };
8816
8817 auto useDevMap = cast<omp::MapInfoOp>(useDevVar.getDefiningOp());
8818 for (auto [mapClause, devicePointer, basePointer] : llvm::zip_equal(
8819 mapInfoData.MapClause, mapInfoData.DevicePointers,
8820 mapInfoData.BasePointers)) {
8821 auto mapOp = cast<omp::MapInfoOp>(mapClause);
8822 if (getMapBasePtr(mapOp) != getMapBasePtr(useDevMap) ||
8823 devicePointer != type)
8824 continue;
8825
8826 if (llvm::Value *devPtrInfoMap =
8827 mapper ? mapper(basePointer) : basePointer) {
8828 moduleTranslation.mapValue(arg, devPtrInfoMap);
8829 break;
8830 }
8831 }
8832 }
8833 };
8834
8835 using BodyGenTy = llvm::OpenMPIRBuilder::BodyGenTy;
8836 auto bodyGenCB = [&](InsertPointTy codeGenIP, BodyGenTy bodyGenType)
8837 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
8838 // We must always restoreIP regardless of doing anything the caller
8839 // does not restore it, leading to incorrect (no) branch generation.
8840 builder.restoreIP(codeGenIP);
8841 assert(isa<omp::TargetDataOp>(op) &&
8842 "BodyGen requested for non TargetDataOp");
8843 auto blockArgIface = cast<omp::BlockArgOpenMPOpInterface>(op);
8844 Region &region = cast<omp::TargetDataOp>(op).getRegion();
8845 switch (bodyGenType) {
8846 case BodyGenTy::Priv:
8847 // Check if any device ptr/addr info is available
8848 if (!info.DevicePtrInfoMap.empty()) {
8849 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Address,
8850 blockArgIface.getUseDeviceAddrBlockArgs(),
8851 useDeviceAddrVars, mapData,
8852 [&](llvm::Value *basePointer) -> llvm::Value * {
8853 if (!info.DevicePtrInfoMap[basePointer].second)
8854 return nullptr;
8855 return builder.CreateLoad(
8856 builder.getPtrTy(),
8857 info.DevicePtrInfoMap[basePointer].second);
8858 });
8859 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer,
8860 blockArgIface.getUseDevicePtrBlockArgs(), useDevicePtrVars,
8861 mapData, [&](llvm::Value *basePointer) {
8862 return info.DevicePtrInfoMap[basePointer].second;
8863 });
8864
8865 if (failed(inlineConvertOmpRegions(region, "omp.data.region", builder,
8866 moduleTranslation)))
8867 return llvm::make_error<PreviouslyReportedError>();
8868 }
8869 break;
8870 case BodyGenTy::DupNoPriv:
8871 if (info.DevicePtrInfoMap.empty()) {
8872 // For host device we still need to do the mapping for codegen,
8873 // otherwise it may try to lookup a missing value.
8874 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Address,
8875 blockArgIface.getUseDeviceAddrBlockArgs(),
8876 useDeviceAddrVars, mapData);
8877 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer,
8878 blockArgIface.getUseDevicePtrBlockArgs(), useDevicePtrVars,
8879 mapData);
8880 }
8881 break;
8882 case BodyGenTy::NoPriv:
8883 // If device info is available then region has already been generated
8884 if (info.DevicePtrInfoMap.empty()) {
8885 if (failed(inlineConvertOmpRegions(region, "omp.data.region", builder,
8886 moduleTranslation)))
8887 return llvm::make_error<PreviouslyReportedError>();
8888 }
8889 break;
8890 }
8891 return builder.saveIP();
8892 };
8893
8894 auto customMapperCB =
8895 [&](unsigned int i) -> llvm::Expected<llvm::Function *> {
8896 if (!combinedInfo.Mappers[i])
8897 return nullptr;
8898 info.HasMapper = true;
8899 return getOrCreateUserDefinedMapperFunc(combinedInfo.Mappers[i], builder,
8900 moduleTranslation, targetDirective);
8901 };
8902
8903 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
8905 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
8906 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
8907
8908 // Pass the region's source location to the runtime, taken from the op's own
8909 // location; only offloading entries emit the mapper calls that consume it.
8910 // No need to also guard on !isTargetDevice here: this function bails out
8911 // earlier for the target device, so isOffloadEntry alone is sufficient.
8912 llvm::Value *srcLocOverride =
8913 isOffloadEntry ? getSourceLocIdentFromOp(builder, *ompBuilder, op)
8914 : nullptr;
8915
8916 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP = [&]() {
8917 if (isa<omp::TargetDataOp>(op))
8918 return ompBuilder->createTargetData(
8919 ompLoc, allocaIP, builder.saveIP(), deallocBlocks, deviceID, ifCond,
8920 info, genMapInfoCB, customMapperCB,
8921 /*MapperFunc=*/nullptr, bodyGenCB,
8922 /*DeviceAddrCB=*/nullptr, srcLocOverride);
8923 return ompBuilder->createTargetData(
8924 ompLoc, allocaIP, builder.saveIP(), deallocBlocks, deviceID, ifCond,
8925 info, genMapInfoCB, customMapperCB, &RTLFn,
8926 /*BodyGenCB=*/nullptr,
8927 /*DeviceAddrCB=*/nullptr, srcLocOverride);
8928 }();
8929
8930 if (failed(handleError(afterIP, *op)))
8931 return failure();
8932
8933 builder.restoreIP(*afterIP);
8934 return success();
8935}
8936
8937static LogicalResult
8938convertOmpDistribute(Operation &opInst, llvm::IRBuilderBase &builder,
8939 LLVM::ModuleTranslation &moduleTranslation) {
8940 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8941 auto distributeOp = cast<omp::DistributeOp>(opInst);
8942 if (failed(checkImplementationStatus(opInst)))
8943 return failure();
8944
8945 /// Process teams op reduction in distribute if the reduction is contained in
8946 /// this specific distribute op.
8947 omp::TeamsOp teamsOp = opInst.getParentOfType<omp::TeamsOp>();
8948 bool doDistributeReduction =
8949 teamsOp && getDistributeCapturingTeamsReduction(teamsOp) == distributeOp;
8950
8951 DenseMap<Value, llvm::Value *> reductionVariableMap;
8952 unsigned numReductionVars = teamsOp ? teamsOp.getNumReductionVars() : 0;
8954 SmallVector<llvm::Value *> privateReductionVariables(numReductionVars);
8955 llvm::ArrayRef<bool> isByRef;
8956
8957 if (doDistributeReduction) {
8958 isByRef = getIsByRef(teamsOp.getReductionByref());
8959 assert(isByRef.size() == teamsOp.getNumReductionVars());
8960
8961 collectReductionDecls(teamsOp, reductionDecls);
8962 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
8963 findAllocInsertPoints(builder, moduleTranslation);
8964
8965 MutableArrayRef<BlockArgument> reductionArgs =
8966 llvm::cast<omp::BlockArgOpenMPOpInterface>(*teamsOp)
8967 .getReductionBlockArgs();
8968
8970 teamsOp, reductionArgs, builder, moduleTranslation, allocaIP,
8971 reductionDecls, privateReductionVariables, reductionVariableMap,
8972 isByRef)))
8973 return failure();
8974 }
8975
8976 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
8977 auto bodyGenCB =
8978 [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
8979 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
8980 llvm::BasicBlock *allocaBlock = allocaIP.getNodeParent();
8981 // DistributeOp has only one region associated with it.
8982 builder.restoreIP(codeGenIP);
8983 PrivateVarsInfo privVarsInfo(distributeOp);
8984
8986 distributeOp, builder, moduleTranslation, privVarsInfo, allocaIP);
8987 if (handleError(afterAllocas, opInst).failed())
8988 return llvm::make_error<PreviouslyReportedError>();
8989
8990 // Save the alloca insertion point on ModuleTranslation stack for use in
8991 // nested regions. This must be after allocatePrivateVars, which splits
8992 // the alloca block and updates allocaIP.
8994 moduleTranslation, allocaIP.getNodeParent(), deallocBlocks);
8995
8996 if (handleError(initPrivateVars(builder, moduleTranslation, privVarsInfo),
8997 opInst)
8998 .failed())
8999 return llvm::make_error<PreviouslyReportedError>();
9000
9001 if (failed(completePrivateVars(distributeOp, builder, moduleTranslation,
9002 privVarsInfo.mlirVars, privVarsInfo.llvmVars,
9003 privVarsInfo.privatizers,
9004 distributeOp.getPrivateNeedsBarrier())))
9005 return llvm::make_error<PreviouslyReportedError>();
9006
9007 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9008 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
9010 convertOmpOpRegions(distributeOp.getRegion(), "omp.distribute.region",
9011 builder, moduleTranslation);
9012 if (!regionBlock)
9013 return regionBlock.takeError();
9014 builder.SetInsertPoint((*regionBlock)->begin());
9015
9016 // Skip applying a workshare loop below when translating 'distribute
9017 // parallel do' (it's been already handled by this point while translating
9018 // the nested omp.wsloop).
9019 if (!isa_and_present<omp::WsloopOp>(distributeOp.getNestedWrapper())) {
9020 // TODO: Add support for clauses which are valid for DISTRIBUTE
9021 // constructs. Static schedule is the default.
9022 bool hasDistSchedule = distributeOp.getDistScheduleStatic();
9023 auto schedule = hasDistSchedule ? omp::ClauseScheduleKind::Distribute
9024 : omp::ClauseScheduleKind::Static;
9025 // dist_schedule clauses are ordered - otherise this should be false
9026 bool isOrdered = hasDistSchedule;
9027 std::optional<omp::ScheduleModifier> scheduleMod;
9028 bool isSimd = false;
9029 llvm::omp::WorksharingLoopType workshareLoopType =
9030 llvm::omp::WorksharingLoopType::DistributeStaticLoop;
9031 bool loopNeedsBarrier = false;
9032 llvm::Value *chunk = moduleTranslation.lookupValue(
9033 distributeOp.getDistScheduleChunkSize());
9034 llvm::CanonicalLoopInfo *loopInfo =
9035 findCurrentLoopInfo(moduleTranslation);
9036 // Nested regions can move the instruction referenced by allocaIP.
9037 allocaIP = allocaBlock->getFirstInsertionPt();
9038 llvm::OpenMPIRBuilder::InsertPointOrErrorTy wsloopIP =
9039 ompBuilder->applyWorkshareLoop(
9040 ompLoc.DL, loopInfo, allocaIP, loopNeedsBarrier,
9041 convertToScheduleKind(schedule), chunk, isSimd,
9042 scheduleMod == omp::ScheduleModifier::monotonic,
9043 scheduleMod == omp::ScheduleModifier::nonmonotonic, isOrdered,
9044 workshareLoopType, false, hasDistSchedule, chunk);
9045
9046 if (!wsloopIP)
9047 return wsloopIP.takeError();
9048 }
9049 if (failed(cleanupPrivateVars(distributeOp, builder, moduleTranslation,
9050 distributeOp.getLoc(), privVarsInfo)))
9051 return llvm::make_error<PreviouslyReportedError>();
9052
9053 return llvm::Error::success();
9054 };
9055
9057 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
9058 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
9059 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
9060 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
9061 ompBuilder->createDistribute(ompLoc, allocaIP, deallocBlocks, bodyGenCB);
9062
9063 if (failed(handleError(afterIP, opInst)))
9064 return failure();
9065
9066 builder.restoreIP(*afterIP);
9067
9068 if (doDistributeReduction) {
9069 // Process the reductions if required.
9071 teamsOp, builder, moduleTranslation, allocaIP, reductionDecls,
9072 privateReductionVariables, isByRef,
9073 /*isNoWait*/ false, /*isTeamsReduction*/ true);
9074 }
9075 return success();
9076}
9077
9078/// Lowers the FlagsAttr which is applied to the module when offloading. This
9079/// attribute contains OpenMP RTL globals that can be passed as flags to the
9080/// frontend, otherwise they are set to default
9081static LogicalResult
9082convertFlagsAttr(Operation *op, mlir::omp::FlagsAttr attribute,
9083 LLVM::ModuleTranslation &moduleTranslation) {
9084 auto offloadMod = dyn_cast<omp::OffloadModuleInterface>(op);
9085 if (!offloadMod)
9086 return op->emitOpError() << "omp flags attached to non offload module op";
9087
9088 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9089
9090 if (offloadMod.getIsTargetDevice())
9091 ompBuilder->M.addModuleFlag(llvm::Module::Max, "openmp-device",
9092 attribute.getOpenmpDeviceVersion());
9093
9094 // The flags below are only intended to be emitted for GPU offload targets.
9095 if (!offloadMod.getIsGPU())
9096 return success();
9097
9098 if (attribute.getNoGpuLib())
9099 return success();
9100
9101 ompBuilder->createGlobalFlag(attribute.getDebugKind(),
9102 "__omp_rtl_debug_kind");
9103 ompBuilder->createGlobalFlag(attribute.getAssumeTeamsOversubscription(),
9104 "__omp_rtl_assume_teams_oversubscription");
9105 ompBuilder->createGlobalFlag(attribute.getAssumeThreadsOversubscription(),
9106 "__omp_rtl_assume_threads_oversubscription");
9107 ompBuilder->createGlobalFlag(attribute.getAssumeNoThreadState(),
9108 "__omp_rtl_assume_no_thread_state");
9109 ompBuilder->createGlobalFlag(attribute.getAssumeNoNestedParallelism(),
9110 "__omp_rtl_assume_no_nested_parallelism");
9111 return success();
9112}
9113
9114static void getTargetEntryUniqueInfo(llvm::TargetRegionEntryInfo &targetInfo,
9115 omp::TargetOp targetOp,
9116 llvm::OpenMPIRBuilder &ompBuilder,
9117 llvm::vfs::FileSystem &vfs,
9118 llvm::StringRef parentName = "") {
9119 auto fileLoc = targetOp.getLoc()->findInstanceOf<FileLineColLoc>();
9120 assert(fileLoc && "No file found from location");
9121
9122 auto fileInfoCallBack = [&fileLoc]() {
9123 return std::pair<std::string, uint64_t>(
9124 llvm::StringRef(fileLoc.getFilename()), fileLoc.getLine());
9125 };
9126
9127 targetInfo =
9128 ompBuilder.getTargetEntryUniqueInfo(fileInfoCallBack, vfs, parentName);
9129}
9130
9131// The createDeviceArgumentAccessor function generates
9132// instructions for retrieving (acessing) kernel
9133// arguments inside of the device kernel for use by
9134// the kernel. This enables different semantics such as
9135// the creation of temporary copies of data allowing
9136// semantics like read-only/no host write back kernel
9137// arguments.
9138//
9139// This currently implements a very light version of Clang's
9140// EmitParmDecl's handling of direct argument handling as well
9141// as a portion of the argument access generation based on
9142// capture types found at the end of emitOutlinedFunctionPrologue
9143// in Clang. The indirect path handling of EmitParmDecl's may be
9144// required for future work, but a direct 1-to-1 copy doesn't seem
9145// possible as the logic is rather scattered throughout Clang's
9146// lowering and perhaps we wish to deviate slightly.
9147//
9148// \param mapData - A container containing vectors of information
9149// corresponding to the input argument, which should have a
9150// corresponding entry in the MapInfoData containers
9151// OrigialValue's.
9152// \param arg - This is the generated kernel function argument that
9153// corresponds to the passed in input argument. We generated different
9154// accesses of this Argument, based on capture type and other Input
9155// related information.
9156// \param input - This is the host side value that will be passed to
9157// the kernel i.e. the kernel input, we rewrite all uses of this within
9158// the kernel (as we generate the kernel body based on the target's region
9159// which maintians references to the original input) to the retVal argument
9160// apon exit of this function inside of the OMPIRBuilder. This interlinks
9161// the kernel argument to future uses of it in the function providing
9162// appropriate "glue" instructions inbetween.
9163// \param retVal - This is the value that all uses of input inside of the
9164// kernel will be re-written to, the goal of this function is to generate
9165// an appropriate location for the kernel argument to be accessed from,
9166// e.g. ByRef will result in a temporary allocation location and then
9167// a store of the kernel argument into this allocated memory which
9168// will then be loaded from, ByCopy will use the allocated memory
9169// directly.
9170static llvm::IRBuilderBase::InsertPoint createDeviceArgumentAccessor(
9171 omp::TargetOp targetOp, MapInfoData &mapData, llvm::Argument &arg,
9172 llvm::Value *input, llvm::Value *&retVal, llvm::IRBuilderBase &builder,
9173 llvm::OpenMPIRBuilder &ompBuilder,
9174 LLVM::ModuleTranslation &moduleTranslation,
9175 llvm::IRBuilderBase::InsertPoint allocaIP,
9176 llvm::IRBuilderBase::InsertPoint codeGenIP,
9178 assert(ompBuilder.Config.isTargetDevice() &&
9179 "function only supported for target device codegen");
9180 builder.restoreIP(allocaIP);
9181
9182 omp::VariableCaptureKind capture = omp::VariableCaptureKind::ByRef;
9183 LLVM::TypeToLLVMIRTranslator typeToLLVMIRTranslator(
9184 ompBuilder.M.getContext());
9185 unsigned alignmentValue = 0;
9186 BlockArgument mlirArg;
9188 cast<omp::BlockArgOpenMPOpInterface>(*targetOp).getBlockArgsPairs(
9189 blockArgsPairs);
9190 // Find the associated MapInfoData entry for the current input
9191 for (size_t i = 0; i < mapData.MapClause.size(); ++i) {
9192 if (mapData.OriginalValue[i] == input) {
9193 auto mapOp = cast<omp::MapInfoOp>(mapData.MapClause[i]);
9194 capture = mapOp.getMapCaptureType();
9195 // Get information of alignment of mapped object
9196 alignmentValue = typeToLLVMIRTranslator.getPreferredAlignment(
9197 mapOp.getVarPtrType(), ompBuilder.M.getDataLayout());
9198
9199 // Find the corresponding entry block argument, which can be associated to
9200 // a map, use_device* or has_device* clause.
9201 for (auto &[val, arg] : blockArgsPairs) {
9202 if (mapOp.getResult() == val) {
9203 mlirArg = arg;
9204 break;
9205 }
9206 }
9207 assert(mlirArg && "expected to find entry block argument for map clause");
9208 break;
9209 }
9210 }
9211
9212 unsigned int allocaAS = ompBuilder.M.getDataLayout().getAllocaAddrSpace();
9213 unsigned int defaultAS =
9214 ompBuilder.M.getDataLayout().getProgramAddressSpace();
9215
9216 // Create the allocation for the argument.
9217 llvm::Value *v = nullptr;
9218 if (omp::opInSharedDeviceContext(*targetOp) &&
9220 // Use the beginning of the codeGenIP rather than the usual allocation point
9221 // for shared memory allocations because otherwise these would be done prior
9222 // to the target initialization call. Also, the exit block (where the
9223 // deallocation is placed) is only executed if the initialization call
9224 // succeeds.
9225 builder.SetInsertPoint(codeGenIP.getNodeParent()->getFirstInsertionPt());
9226 v = ompBuilder.createOMPAllocShared(builder, arg.getType());
9227
9228 // Create deallocations in all provided deallocation points and then restore
9229 // the insertion point to right after the new allocations.
9230 llvm::IRBuilderBase::InsertPointGuard guard(builder);
9231 for (auto deallocIP : deallocIPs) {
9232 builder.SetInsertPoint(deallocIP);
9233 ompBuilder.createOMPFreeShared(builder, v, arg.getType());
9234 }
9235 } else {
9236 // Use the current point, which was previously set to allocaIP.
9237 v = builder.CreateAlloca(arg.getType(), allocaAS);
9238
9239 if (allocaAS != defaultAS && arg.getType()->isPointerTy())
9240 v = builder.CreateAddrSpaceCast(v, builder.getPtrTy(defaultAS));
9241 }
9242
9243 builder.CreateStore(&arg, v);
9244
9245 builder.restoreIP(codeGenIP);
9246
9247 switch (capture) {
9248 case omp::VariableCaptureKind::ByCopy: {
9249 retVal = v;
9250 break;
9251 }
9252 case omp::VariableCaptureKind::ByRef: {
9253 llvm::LoadInst *loadInst = builder.CreateAlignedLoad(
9254 v->getType(), v,
9255 ompBuilder.M.getDataLayout().getPrefTypeAlign(v->getType()));
9256 // CreateAlignedLoad function creates similar LLVM IR:
9257 // %res = load ptr, ptr %input, align 8
9258 // This LLVM IR does not contain information about alignment
9259 // of the loaded value. We need to add !align metadata to unblock
9260 // optimizer. The existence of the !align metadata on the instruction
9261 // tells the optimizer that the value loaded is known to be aligned to
9262 // a boundary specified by the integer value in the metadata node.
9263 // Example:
9264 // %res = load ptr, ptr %input, align 8, !align !align_md_node
9265 // ^ ^
9266 // | |
9267 // alignment of %input address |
9268 // |
9269 // alignment of %res object
9270 if (v->getType()->isPointerTy() && alignmentValue) {
9271 llvm::MDBuilder MDB(builder.getContext());
9272 loadInst->setMetadata(
9273 llvm::LLVMContext::MD_align,
9274 llvm::MDNode::get(builder.getContext(),
9275 MDB.createConstant(llvm::ConstantInt::get(
9276 llvm::Type::getInt64Ty(builder.getContext()),
9277 alignmentValue))));
9278 }
9279 retVal = loadInst;
9280
9281 break;
9282 }
9283 case omp::VariableCaptureKind::This:
9284 case omp::VariableCaptureKind::VLAType:
9285 // TODO: Consider returning error to use standard reporting for
9286 // unimplemented features.
9287 assert(false && "Currently unsupported capture kind");
9288 break;
9289 }
9290
9291 return builder.saveIP();
9292}
9293
9294/// Follow uses of `host_eval`-defined block arguments of the given `omp.target`
9295/// operation and populate output variables with their corresponding host value
9296/// (i.e. operand evaluated outside of the target region), based on their uses
9297/// inside of the target region.
9298///
9299/// Loop bounds and steps are only optionally populated, if output vectors are
9300/// provided.
9301static void
9302extractHostEvalClauses(omp::TargetOp targetOp, Value &numThreads,
9303 Value &numTeamsLower, Value &numTeamsUpper,
9304 Value &threadLimit,
9305 llvm::SmallVectorImpl<Value> *lowerBounds = nullptr,
9306 llvm::SmallVectorImpl<Value> *upperBounds = nullptr,
9307 llvm::SmallVectorImpl<Value> *steps = nullptr) {
9308 auto blockArgIface = llvm::cast<omp::BlockArgOpenMPOpInterface>(*targetOp);
9309 for (auto item : llvm::zip_equal(targetOp.getHostEvalVars(),
9310 blockArgIface.getHostEvalBlockArgs())) {
9311 Value hostEvalVar = std::get<0>(item), blockArg = std::get<1>(item);
9312
9313 for (Operation *user : blockArg.getUsers()) {
9315 .Case([&](omp::TeamsOp teamsOp) {
9316 if (teamsOp.getNumTeamsLower() == blockArg)
9317 numTeamsLower = hostEvalVar;
9318 else if (llvm::is_contained(teamsOp.getNumTeamsUpperVars(),
9319 blockArg))
9320 numTeamsUpper = hostEvalVar;
9321 else if (!teamsOp.getThreadLimitVars().empty() &&
9322 teamsOp.getThreadLimit(0) == blockArg)
9323 threadLimit = hostEvalVar;
9324 else
9325 llvm_unreachable("unsupported host_eval use");
9326 })
9327 .Case([&](omp::ParallelOp parallelOp) {
9328 if (!parallelOp.getNumThreadsVars().empty() &&
9329 parallelOp.getNumThreads(0) == blockArg)
9330 numThreads = hostEvalVar;
9331 else
9332 llvm_unreachable("unsupported host_eval use");
9333 })
9334 .Case([&](omp::LoopNestOp loopOp) {
9335 auto processBounds =
9336 [&](OperandRange opBounds,
9337 llvm::SmallVectorImpl<Value> *outBounds) -> bool {
9338 bool found = false;
9339 for (auto [i, lb] : llvm::enumerate(opBounds)) {
9340 if (lb == blockArg) {
9341 found = true;
9342 if (outBounds)
9343 (*outBounds)[i] = hostEvalVar;
9344 }
9345 }
9346 return found;
9347 };
9348 bool found =
9349 processBounds(loopOp.getLoopLowerBounds(), lowerBounds);
9350 found = processBounds(loopOp.getLoopUpperBounds(), upperBounds) ||
9351 found;
9352 found = processBounds(loopOp.getLoopSteps(), steps) || found;
9353 (void)found;
9354 assert(found && "unsupported host_eval use");
9355 })
9356 .DefaultUnreachable("unsupported host_eval use");
9357 }
9358 }
9359}
9360
9361/// If \p op is of the given type parameter, return it casted to that type.
9362/// Otherwise, if its immediate parent operation (or some other higher-level
9363/// parent, if \p immediateParent is false) is of that type, return that parent
9364/// casted to the given type.
9365///
9366/// If \p op is \c null or neither it or its parent(s) are of the specified
9367/// type, return a \c null operation.
9368template <typename OpTy>
9369static OpTy castOrGetParentOfType(Operation *op, bool immediateParent = false) {
9370 if (!op)
9371 return OpTy();
9372
9373 if (OpTy casted = dyn_cast<OpTy>(op))
9374 return casted;
9375
9376 if (immediateParent)
9377 return dyn_cast_if_present<OpTy>(op->getParentOp());
9378
9379 return op->getParentOfType<OpTy>();
9380}
9381
9382/// If the given \p value is defined by an \c llvm.mlir.constant operation and
9383/// it is of an integer type, return its value.
9384static std::optional<int64_t> extractConstInteger(Value value) {
9385 if (!value)
9386 return std::nullopt;
9387
9388 if (auto constOp = value.getDefiningOp<LLVM::ConstantOp>())
9389 if (auto constAttr = dyn_cast<IntegerAttr>(constOp.getValue()))
9390 return constAttr.getInt();
9391
9392 return std::nullopt;
9393}
9394
9395static uint64_t getTypeByteSize(mlir::Type type, const DataLayout &dl) {
9396 uint64_t sizeInBits = dl.getTypeSizeInBits(type);
9397 uint64_t sizeInBytes = sizeInBits / 8;
9398 return sizeInBytes;
9399}
9400
9401template <typename OpTy>
9402static uint64_t getReductionDataSize(OpTy &op) {
9403 if (op.getNumReductionVars() > 0) {
9405 collectReductionDecls(op, reductions);
9406
9408 members.reserve(reductions.size());
9409 for (omp::DeclareReductionOp &red : reductions) {
9410 // For by-ref reductions, use the actual element type rather than the
9411 // pointer type so that the buffer size matches the access pattern in
9412 // the copy/reduce callbacks generated by OMPIRBuilder.
9413 if (red.getByrefElementType())
9414 members.push_back(*red.getByrefElementType());
9415 else
9416 members.push_back(red.getType());
9417 }
9418 Operation *opp = op.getOperation();
9419 auto structType = mlir::LLVM::LLVMStructType::getLiteral(
9420 opp->getContext(), members, /*isPacked=*/false);
9421 DataLayout dl = DataLayout(opp->getParentOfType<ModuleOp>());
9422 return getTypeByteSize(structType, dl);
9423 }
9424 return 0;
9425}
9426
9427/// Populate default `MinTeams`, `MaxTeams` and `MaxThreads` to their default
9428/// values as stated by the corresponding clauses, if constant.
9429///
9430/// These default values must be set before the creation of the outlined LLVM
9431/// function for the target region, so that they can be used to initialize the
9432/// corresponding global `ConfigurationEnvironmentTy` structure.
9433static void
9434initTargetDefaultAttrs(omp::TargetOp targetOp, Operation *capturedOp,
9435 llvm::OpenMPIRBuilder::TargetKernelDefaultAttrs &attrs,
9436 bool isTargetDevice, bool isGPU) {
9437 // TODO: Handle constant 'if' clauses.
9438
9439 Value numThreads, numTeamsLower, numTeamsUpper, threadLimit;
9440 if (!isTargetDevice) {
9441 extractHostEvalClauses(targetOp, numThreads, numTeamsLower, numTeamsUpper,
9442 threadLimit);
9443 } else {
9444 // In the target device, values for these clauses are not passed as
9445 // host_eval, but instead evaluated prior to entry to the region. This
9446 // ensures values are mapped and available inside of the target region.
9447 if (auto teamsOp = castOrGetParentOfType<omp::TeamsOp>(capturedOp)) {
9448 numTeamsLower = teamsOp.getNumTeamsLower();
9449 // Handle num_teams upper bounds (only first value for now)
9450 if (!teamsOp.getNumTeamsUpperVars().empty())
9451 numTeamsUpper = teamsOp.getNumTeams(0);
9452 if (!teamsOp.getThreadLimitVars().empty())
9453 threadLimit = teamsOp.getThreadLimit(0);
9454 }
9455
9456 if (auto parallelOp = castOrGetParentOfType<omp::ParallelOp>(capturedOp)) {
9457 if (!parallelOp.getNumThreadsVars().empty())
9458 numThreads = parallelOp.getNumThreads(0);
9459 }
9460 }
9461
9462 // Handle clauses impacting the number of teams.
9463
9464 int32_t minTeamsVal = 1, maxTeamsVal = -1;
9465 if (castOrGetParentOfType<omp::TeamsOp>(capturedOp)) {
9466 // TODO: Use `hostNumTeamsLower` to initialize `minTeamsVal`. For now,
9467 // match clang and set min and max to the same value.
9468 if (numTeamsUpper) {
9469 if (auto val = extractConstInteger(numTeamsUpper))
9470 minTeamsVal = maxTeamsVal = *val;
9471 } else {
9472 minTeamsVal = maxTeamsVal = 0;
9473 }
9474 } else if (castOrGetParentOfType<omp::ParallelOp>(capturedOp,
9475 /*immediateParent=*/true) ||
9477 /*immediateParent=*/true)) {
9478 minTeamsVal = maxTeamsVal = 1;
9479 } else {
9480 minTeamsVal = maxTeamsVal = -1;
9481 }
9482
9483 // Handle clauses impacting the number of threads.
9484
9485 auto setMaxValueFromClause = [](Value clauseValue, int32_t &result) {
9486 if (!clauseValue)
9487 return;
9488
9489 if (auto val = extractConstInteger(clauseValue))
9490 result = *val;
9491
9492 // Found an applicable clause, so it's not undefined. Mark as unknown
9493 // because it's not constant.
9494 if (result < 0)
9495 result = 0;
9496 };
9497
9498 // Extract 'thread_limit' clause from 'target' and 'teams' directives.
9499 int32_t targetThreadLimitVal = -1, teamsThreadLimitVal = -1;
9500 if (!targetOp.getThreadLimitVars().empty())
9501 setMaxValueFromClause(targetOp.getThreadLimit(0), targetThreadLimitVal);
9502 setMaxValueFromClause(threadLimit, teamsThreadLimitVal);
9503
9504 // Extract 'max_threads' clause from 'parallel' or set to 1 if it's SIMD.
9505 int32_t maxThreadsVal = -1;
9507 setMaxValueFromClause(numThreads, maxThreadsVal);
9508 else if (castOrGetParentOfType<omp::SimdOp>(capturedOp,
9509 /*immediateParent=*/true))
9510 maxThreadsVal = 1;
9511
9512 // For max values, < 0 means unset, == 0 means set but unknown. Select the
9513 // minimum value between 'max_threads' and 'thread_limit' clauses that were
9514 // set.
9515 int32_t combinedMaxThreadsVal = targetThreadLimitVal;
9516 if (combinedMaxThreadsVal < 0 ||
9517 (teamsThreadLimitVal >= 0 && teamsThreadLimitVal < combinedMaxThreadsVal))
9518 combinedMaxThreadsVal = teamsThreadLimitVal;
9519
9520 if (combinedMaxThreadsVal < 0 ||
9521 (maxThreadsVal >= 0 && maxThreadsVal < combinedMaxThreadsVal))
9522 combinedMaxThreadsVal = maxThreadsVal;
9523
9524 int32_t reductionDataSize = 0;
9525 if (isGPU && capturedOp) {
9526 if (auto teamsOp = castOrGetParentOfType<omp::TeamsOp>(capturedOp))
9527 reductionDataSize = getReductionDataSize(teamsOp);
9528 }
9529
9530 // Update kernel bounds structure for the `OpenMPIRBuilder` to use.
9531 // Use the kernel_type attribute set by the frontend instead of analyzing IR.
9532 omp::TargetExecMode execMode = targetOp.getKernelType();
9533 switch (execMode) {
9534 case omp::TargetExecMode::bare:
9535 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_BARE;
9536 break;
9537 case omp::TargetExecMode::generic:
9538 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_GENERIC;
9539 break;
9540 case omp::TargetExecMode::spmd:
9541 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_SPMD;
9542 break;
9543 case omp::TargetExecMode::spmd_no_loop:
9544 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_SPMD_NO_LOOP;
9545 break;
9546 }
9547 attrs.MinTeams.front() = minTeamsVal;
9548 attrs.MaxTeams.front() = maxTeamsVal;
9549 attrs.MinThreads.front() = 1;
9550 attrs.MaxThreads.front() = combinedMaxThreadsVal;
9551 attrs.ReductionDataSize = reductionDataSize;
9552}
9553
9554/// Gather LLVM runtime values for all clauses evaluated in the host that are
9555/// passed to the kernel invocation.
9556///
9557/// This function must be called only when compiling for the host. Also, it will
9558/// only provide correct results if it's called after the body of \c targetOp
9559/// has been fully generated.
9560static void
9561initTargetRuntimeAttrs(llvm::IRBuilderBase &builder,
9562 LLVM::ModuleTranslation &moduleTranslation,
9563 omp::TargetOp targetOp, Operation *capturedOp,
9564 llvm::OpenMPIRBuilder::TargetKernelRuntimeAttrs &attrs) {
9565 omp::LoopNestOp loopOp = castOrGetParentOfType<omp::LoopNestOp>(capturedOp);
9566 unsigned numLoops = loopOp ? loopOp.getNumLoops() : 0;
9567
9568 Value numThreads, numTeamsLower, numTeamsUpper, teamsThreadLimit;
9569 llvm::SmallVector<Value> lowerBounds(numLoops), upperBounds(numLoops),
9570 steps(numLoops);
9571 extractHostEvalClauses(targetOp, numThreads, numTeamsLower, numTeamsUpper,
9572 teamsThreadLimit, &lowerBounds, &upperBounds, &steps);
9573
9574 // TODO: Handle constant 'if' clauses.
9575 if (!targetOp.getThreadLimitVars().empty()) {
9576 Value targetThreadLimit = targetOp.getThreadLimit(0);
9577 attrs.TargetThreadLimit.front() =
9578 moduleTranslation.lookupValue(targetThreadLimit);
9579 }
9580
9581 // The __kmpc_push_num_teams_51 function expects int32 as the arguments. So,
9582 // truncate or sign extend lower and upper num_teams bounds as well as
9583 // thread_limit to match int32 ABI requirements for the OpenMP runtime.
9584 if (numTeamsLower)
9585 attrs.MinTeams.front() = builder.CreateSExtOrTrunc(
9586 moduleTranslation.lookupValue(numTeamsLower), builder.getInt32Ty());
9587
9588 if (numTeamsUpper)
9589 attrs.MaxTeams.front() = builder.CreateSExtOrTrunc(
9590 moduleTranslation.lookupValue(numTeamsUpper), builder.getInt32Ty());
9591
9592 if (teamsThreadLimit)
9593 attrs.TeamsThreadLimit.front() = builder.CreateSExtOrTrunc(
9594 moduleTranslation.lookupValue(teamsThreadLimit), builder.getInt32Ty());
9595
9596 if (numThreads)
9597 attrs.MaxThreads.front() = moduleTranslation.lookupValue(numThreads);
9598
9599 if (targetOp.hasHostEvalTripCount()) {
9600 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9601 attrs.LoopTripCount = nullptr;
9602
9603 // To calculate the trip count, we multiply together the trip counts of
9604 // every collapsed canonical loop. We don't need to create the loop nests
9605 // here, since we're only interested in the trip count.
9606 for (auto [loopLower, loopUpper, loopStep] :
9607 llvm::zip_equal(lowerBounds, upperBounds, steps)) {
9608 llvm::Value *lowerBound = moduleTranslation.lookupValue(loopLower);
9609 llvm::Value *upperBound = moduleTranslation.lookupValue(loopUpper);
9610 llvm::Value *step = moduleTranslation.lookupValue(loopStep);
9611
9612 if (!lowerBound || !upperBound || !step) {
9613 attrs.LoopTripCount = nullptr;
9614 break;
9615 }
9616
9617 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
9618 llvm::Value *tripCount = ompBuilder->calculateCanonicalLoopTripCount(
9619 loc, lowerBound, upperBound, step, /*IsSigned=*/true,
9620 loopOp.getLoopInclusive());
9621
9622 if (!attrs.LoopTripCount) {
9623 attrs.LoopTripCount = tripCount;
9624 continue;
9625 }
9626
9627 // TODO: Enable UndefinedSanitizer to diagnose an overflow here.
9628 attrs.LoopTripCount = builder.CreateMul(attrs.LoopTripCount, tripCount,
9629 {}, /*HasNUW=*/true);
9630 }
9631 }
9632
9633 attrs.DeviceID = builder.getInt64(llvm::omp::OMP_DEVICEID_UNDEF);
9634 if (mlir::Value devId = targetOp.getDevice()) {
9635 attrs.DeviceID = moduleTranslation.lookupValue(devId);
9636 attrs.DeviceID =
9637 builder.CreateSExtOrTrunc(attrs.DeviceID, builder.getInt64Ty());
9638 }
9639}
9640
9641static llvm::omp::OMPDynGroupprivateFallbackType
9642getDynGroupprivateFallbackType(omp::FallbackModifierAttr fallbackAttr) {
9643 omp::FallbackModifier fb = fallbackAttr ? fallbackAttr.getValue()
9644 : omp::FallbackModifier::default_mem;
9645 switch (fb) {
9646 case omp::FallbackModifier::abort:
9647 return llvm::omp::OMPDynGroupprivateFallbackType::Abort;
9648 case omp::FallbackModifier::null:
9649 return llvm::omp::OMPDynGroupprivateFallbackType::Null;
9650 case omp::FallbackModifier::default_mem:
9651 return llvm::omp::OMPDynGroupprivateFallbackType::DefaultMem;
9652 }
9653
9654 llvm_unreachable("unexpected dyn_groupprivate fallback type");
9655}
9656
9657static LogicalResult
9658convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
9659 LLVM::ModuleTranslation &moduleTranslation) {
9660 auto targetOp = cast<omp::TargetOp>(opInst);
9661
9662 // The current debug location already has the DISubprogram for the outlined
9663 // function that will be created for the target op. We save it here so that
9664 // we can set it on the outlined function.
9665 llvm::DebugLoc outlinedFnLoc = builder.getCurrentDebugLocation();
9666 if (failed(checkImplementationStatus(opInst)))
9667 return failure();
9668
9669 // During the handling of target op, we will generate instructions in the
9670 // parent function like call to the oulined function or branch to a new
9671 // BasicBlock. We set the debug location here to parent function so that those
9672 // get the correct debug locations. For outlined functions, the normal MLIR op
9673 // conversion will automatically pick the correct location.
9674 llvm::BasicBlock *parentBB = builder.GetInsertBlock();
9675 assert(parentBB && "No insert block is set for the builder");
9676 llvm::Function *parentLLVMFn = parentBB->getParent();
9677 assert(parentLLVMFn && "Parent Function must be valid");
9678 if (llvm::DISubprogram *SP = parentLLVMFn->getSubprogram())
9679 builder.SetCurrentDebugLocation(llvm::DILocation::get(
9680 parentLLVMFn->getContext(), outlinedFnLoc.getLine(),
9681 outlinedFnLoc.getCol(), SP, outlinedFnLoc.getInlinedAt()));
9682
9683 // OMPIRBuilder emits runtime calls into the outlined function before bodyCB
9684 // below gets a chance to attach the subprogram to it, so it needs the
9685 // outlined function's location handed to it separately. Only pass it under
9686 // the same condition that decides whether the subprogram is attached at all:
9687 // a location may not be attached to an instruction in a function that has no
9688 // subprogram.
9689 llvm::DebugLoc outlinedFnDbgLoc;
9690 if (outlinedFnLoc && parentLLVMFn->getSubprogram())
9691 outlinedFnDbgLoc = outlinedFnLoc;
9692
9693 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9694 bool isTargetDevice = ompBuilder->Config.isTargetDevice();
9695 bool isGPU = ompBuilder->Config.isGPU();
9696
9697 auto parentFn = opInst.getParentOfType<LLVM::LLVMFuncOp>();
9698 auto argIface = cast<omp::BlockArgOpenMPOpInterface>(opInst);
9699 auto &targetRegion = targetOp.getRegion();
9700 // Holds the private vars that have been mapped along with the block
9701 // argument that corresponds to the MapInfoOp corresponding to the private
9702 // var in question. So, for instance:
9703 //
9704 // %10 = omp.map.info var_ptr(%6#0 : !fir.ref<!fir.box<!fir.heap<i32>>>, ..)
9705 // omp.target map_entries(%10 -> %arg0) private(@box.privatizer %6#0-> %arg1)
9706 //
9707 // Then, %10 has been created so that the descriptor can be used by the
9708 // privatizer @box.privatizer on the device side. Here we'd record {%6#0,
9709 // %arg0} in the mappedPrivateVars map.
9710 llvm::DenseMap<Value, Value> mappedPrivateVars;
9711 DataLayout dl = DataLayout(opInst.getParentOfType<ModuleOp>());
9712 SmallVector<Value> mapVars = targetOp.getMapVars();
9713 SmallVector<Value> hdaVars = targetOp.getHasDeviceAddrVars();
9714 ArrayRef<BlockArgument> mapBlockArgs = argIface.getMapBlockArgs();
9715 ArrayRef<BlockArgument> hdaBlockArgs = argIface.getHasDeviceAddrBlockArgs();
9716 llvm::Function *llvmOutlinedFn = nullptr;
9717 TargetDirectiveEnumTy targetDirective =
9718 getTargetDirectiveEnumTyFromOp(&opInst);
9719
9720 // TODO: It can also be false if a compile-time constant `false` IF clause is
9721 // specified.
9722 bool isOffloadEntry =
9723 isTargetDevice || !ompBuilder->Config.TargetTriples.empty();
9724
9725 // Resolve in_reduction clauses on omp.target for the host. From the target
9726 // device's perspective an in_reduction list item behaves as a regular
9727 // map(tofrom) variable, so no special handling is needed there; only the
9728 // host redirects the mapped value to the per-task reduction-private storage
9729 // returned by __kmpc_task_reduction_get_th_data (emitted inside the
9730 // to-be-outlined target task body). This applies to both offloading and
9731 // non-offloading host modules.
9732 //
9733 // The target body has no dedicated in_reduction block argument: each
9734 // in_reduction variable is accessed through its map_entries block argument.
9735 // So each in_reduction variable must also be captured by a matching
9736 // map_entries entry (guaranteed by the verifier); without one the outlined
9737 // body would reference a value defined in the host function. Record, for each
9738 // in_reduction variable, the position of that map entry so the corresponding
9739 // map block argument can be redirected inside the body. The in_reduction
9740 // operand itself is used as the `orig` argument of the runtime lookup.
9741 SmallVector<llvm::Value *> inRedOrigPtrs;
9742 SmallVector<unsigned> inRedMapArgIdx;
9743 if (!targetOp.getInReductionVars().empty() && !isTargetDevice) {
9744 inRedOrigPtrs.reserve(targetOp.getInReductionVars().size());
9745 inRedMapArgIdx.reserve(targetOp.getInReductionVars().size());
9746 for (Value v : targetOp.getInReductionVars()) {
9747 // Select the map_entries entry that captures this in_reduction operand.
9748 // The verifier guarantees at least one match exists; more than one
9749 // matching entry is a lowering ambiguity (the redirect cannot pick which
9750 // map argument to rebind).
9751 std::optional<unsigned> matchIdx;
9752 for (auto [idx, mapV] : llvm::enumerate(targetOp.getMapVars())) {
9753 auto mapInfo = mapV.getDefiningOp<omp::MapInfoOp>();
9754 if (v != mapInfo.getVarPtr())
9755 continue;
9756 if (matchIdx)
9757 return targetOp.emitError()
9758 << "in_reduction variable on omp.target has multiple matching "
9759 "map_entries entries; the redirect target is ambiguous";
9760 matchIdx = idx;
9761 }
9762 // The verifier requires a capturing map entry for every in_reduction
9763 // operand, so a match must exist here.
9764 assert(matchIdx &&
9765 "TargetOp verifier guarantees a matching map_entries entry for "
9766 "each in_reduction variable");
9767 inRedMapArgIdx.push_back(*matchIdx);
9768 // The runtime `orig` pointer is the in_reduction operand itself, the
9769 // reduction variable the enclosing taskgroup registered.
9770 inRedOrigPtrs.push_back(moduleTranslation.lookupValue(v));
9771 }
9772 }
9773
9774 // For some private variables, the MapsForPrivatizedVariablesPass
9775 // creates MapInfoOp instances. Go through the private variables and
9776 // the mapped variables so that during codegeneration we are able
9777 // to quickly look up the corresponding map variable, if any for each
9778 // private variable.
9779 if (!targetOp.getPrivateVars().empty() && !targetOp.getMapVars().empty()) {
9780 OperandRange privateVars = targetOp.getPrivateVars();
9781 std::optional<ArrayAttr> privateSyms = targetOp.getPrivateSyms();
9782 std::optional<DenseI64ArrayAttr> privateMapIndices =
9783 targetOp.getPrivateMapsAttr();
9784
9785 for (auto [privVarIdx, privVarSymPair] :
9786 llvm::enumerate(llvm::zip_equal(privateVars, *privateSyms))) {
9787 auto privVar = std::get<0>(privVarSymPair);
9788 auto privSym = std::get<1>(privVarSymPair);
9789
9790 SymbolRefAttr privatizerName = llvm::cast<SymbolRefAttr>(privSym);
9791 omp::PrivateClauseOp privatizer =
9792 findPrivatizer(targetOp, privatizerName);
9793
9794 if (!privatizer.needsMap())
9795 continue;
9796
9797 mlir::Value mappedValue =
9798 targetOp.getMappedValueForPrivateVar(privVarIdx);
9799 assert(mappedValue && "Expected to find mapped value for a privatized "
9800 "variable that needs mapping");
9801
9802 // The MapInfoOp defining the map var isn't really needed later.
9803 // So, we don't store it in any datastructure. Instead, we just
9804 // do some sanity checks on it right now.
9805 auto mapInfoOp = mappedValue.getDefiningOp<omp::MapInfoOp>();
9806 [[maybe_unused]] Type varType = mapInfoOp.getVarPtrType();
9807
9808 // Check #1: Check that the type of the private variable matches
9809 // the type of the variable being mapped.
9810 if (!isa<LLVM::LLVMPointerType>(privVar.getType()))
9811 assert(
9812 varType == privVar.getType() &&
9813 "Type of private var doesn't match the type of the mapped value");
9814
9815 // Ok, only 1 sanity check for now.
9816 // Record the block argument corresponding to this mapvar.
9817 mappedPrivateVars.insert(
9818 {privVar,
9819 targetRegion.getArgument(argIface.getMapBlockArgsStart() +
9820 (*privateMapIndices)[privVarIdx])});
9821 }
9822 }
9823
9824 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
9825 auto bodyCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
9826 ArrayRef<llvm::BasicBlock *> deallocBlocks)
9827 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
9828 llvm::IRBuilderBase::InsertPointGuard guard(builder);
9829 builder.SetCurrentDebugLocation(llvm::DebugLoc());
9830 // Forward target-cpu and target-features function attributes from the
9831 // original function to the new outlined function.
9832 llvm::Function *llvmParentFn =
9833 moduleTranslation.lookupFunction(parentFn.getName());
9834 llvmOutlinedFn = codeGenIP.getNodeParent()->getParent();
9835 assert(llvmParentFn && llvmOutlinedFn &&
9836 "Both parent and outlined functions must exist at this point");
9837
9838 if (outlinedFnLoc && llvmParentFn->getSubprogram())
9839 llvmOutlinedFn->setSubprogram(outlinedFnLoc->getScope()->getSubprogram());
9840
9841 if (auto attr = llvmParentFn->getFnAttribute("target-cpu");
9842 attr.isStringAttribute())
9843 llvmOutlinedFn->addFnAttr(attr);
9844
9845 if (auto attr = llvmParentFn->getFnAttribute("target-features");
9846 attr.isStringAttribute())
9847 llvmOutlinedFn->addFnAttr(attr);
9848
9849 for (auto [idx, arg] : llvm::enumerate(mapBlockArgs)) {
9850 // in_reduction list items on omp.target are accessed through their
9851 // map_entries block argument, which is redirected below to the per-task
9852 // reduction-private storage returned by the runtime. Skip the default
9853 // host-value mapping for those block arguments so the write-once
9854 // mapValue mapping is free to be set to the private pointer.
9855 if (llvm::is_contained(inRedMapArgIdx, idx))
9856 continue;
9857 auto mapInfoOp = cast<omp::MapInfoOp>(mapVars[idx].getDefiningOp());
9858 llvm::Value *mapOpValue =
9859 moduleTranslation.lookupValue(mapInfoOp.getVarPtr());
9860 moduleTranslation.mapValue(arg, mapOpValue);
9861 }
9862 for (auto [arg, mapOp] : llvm::zip_equal(hdaBlockArgs, hdaVars)) {
9863 auto mapInfoOp = cast<omp::MapInfoOp>(mapOp.getDefiningOp());
9864 llvm::Value *mapOpValue =
9865 moduleTranslation.lookupValue(mapInfoOp.getVarPtr());
9866 moduleTranslation.mapValue(arg, mapOpValue);
9867 }
9868
9869 // Do privatization after moduleTranslation has already recorded
9870 // mapped values.
9871 PrivateVarsInfo privateVarsInfo(targetOp);
9872
9874 allocatePrivateVars(targetOp, builder, moduleTranslation,
9875 privateVarsInfo, allocaIP, &mappedPrivateVars);
9876
9877 if (failed(handleError(afterAllocas, *targetOp)))
9878 return llvm::make_error<PreviouslyReportedError>();
9879
9880 builder.restoreIP(codeGenIP);
9881 if (handleError(initPrivateVars(builder, moduleTranslation, privateVarsInfo,
9882 &mappedPrivateVars),
9883 *targetOp)
9884 .failed())
9885 return llvm::make_error<PreviouslyReportedError>();
9886
9887 if (failed(completePrivateVars(
9888 targetOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
9889 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
9890 targetOp.getPrivateNeedsBarrier(), &mappedPrivateVars)))
9891 return llvm::make_error<PreviouslyReportedError>();
9892
9893 // The target body accesses each in_reduction variable through its
9894 // map_entries block argument. Redirect that block argument to the per-task
9895 // private storage returned by __kmpc_task_reduction_get_th_data so the body
9896 // accumulates into the reduction-private copy rather than the mapped
9897 // original. The lookup must run inside the target task body so the gtid
9898 // corresponds to the executing thread. The descriptor argument is NULL: the
9899 // runtime walks enclosing taskgroups to locate the matching task_reduction
9900 // registration for `origPtr`. Mirrors the in_reduction handling on
9901 // omp.taskloop.context.
9902 if (!inRedOrigPtrs.empty()) {
9903 // Collect, per item, the type the private pointer must have (the map
9904 // block argument's type), and, through the callback, rebind the map block
9905 // argument that stands in for each in_reduction list item to the per-task
9906 // reduction-private storage the runtime returns.
9907 SmallVector<llvm::Type *> inRedResultPtrTys;
9908 inRedResultPtrTys.reserve(inRedMapArgIdx.size());
9909 for (unsigned mapArgIdx : inRedMapArgIdx)
9910 inRedResultPtrTys.push_back(
9911 moduleTranslation.convertType(mapBlockArgs[mapArgIdx].getType()));
9912
9913 llvm::OpenMPIRBuilder::LocationDescription bodyLoc(builder);
9914 llvm::OpenMPIRBuilder::InsertPointTy redIP =
9915 ompBuilder->createTargetInReduction(
9916 bodyLoc, inRedOrigPtrs, inRedResultPtrTys,
9917 [&](unsigned idx, llvm::Value *priv) {
9918 moduleTranslation.mapValue(mapBlockArgs[inRedMapArgIdx[idx]],
9919 priv);
9920 });
9921 builder.restoreIP(redIP);
9922 }
9923
9925 moduleTranslation, allocaIP.getNodeParent(), deallocBlocks);
9927 targetRegion, "omp.target", builder, moduleTranslation);
9928
9929 if (failed(handleError(exitBlock, *targetOp)))
9930 return llvm::make_error<PreviouslyReportedError>();
9931
9932 builder.SetInsertPoint(exitBlock.get()->getTerminator());
9933
9934 if (failed(cleanupPrivateVars(targetOp, builder, moduleTranslation,
9935 targetOp.getLoc(), privateVarsInfo)))
9936 return llvm::make_error<PreviouslyReportedError>();
9937
9938 return builder.saveIP();
9939 };
9940
9941 StringRef parentName = parentFn.getName();
9942
9943 llvm::TargetRegionEntryInfo entryInfo;
9944
9945 getTargetEntryUniqueInfo(entryInfo, targetOp,
9946 *moduleTranslation.getOpenMPBuilder(),
9947 moduleTranslation.getFileSystem(), parentName);
9948
9949 MapInfoData mapData;
9950 collectMapDataFromMapOperands(mapData, mapVars, moduleTranslation, dl,
9951 builder, /*useDevPtrOperands=*/{},
9952 /*useDevAddrOperands=*/{}, hdaVars);
9953
9954 MapInfosTy combinedInfos;
9955 auto genMapInfoCB =
9956 [&](llvm::OpenMPIRBuilder::InsertPointTy codeGenIP) -> MapInfosTy & {
9957 builder.restoreIP(codeGenIP);
9958 genMapInfos(builder, moduleTranslation, dl, combinedInfos, mapData,
9959 targetDirective);
9960
9961 // Append a null entry for the implicit dyn_ptr argument so the argument
9962 // count sent to the runtime already includes it.
9963 auto *nullPtr = llvm::Constant::getNullValue(builder.getPtrTy());
9964 combinedInfos.BasePointers.push_back(nullPtr);
9965 combinedInfos.Pointers.push_back(nullPtr);
9966 combinedInfos.DevicePointers.push_back(
9967 llvm::OpenMPIRBuilder::DeviceInfoTy::None);
9968 combinedInfos.Sizes.push_back(builder.getInt64(0));
9969 combinedInfos.Types.push_back(
9970 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TARGET_PARAM |
9971 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL);
9972 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
9973 combinedInfos.HasAttachPtr.push_back(false);
9974 if (!combinedInfos.Names.empty())
9975 combinedInfos.Names.push_back(nullPtr);
9976 combinedInfos.Mappers.push_back(nullptr);
9977
9978 return combinedInfos;
9979 };
9980
9981 auto argAccessorCB = [&](llvm::Argument &arg, llvm::Value *input,
9982 llvm::Value *&retVal, InsertPointTy allocaIP,
9983 InsertPointTy codeGenIP,
9985 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
9986 llvm::IRBuilderBase::InsertPointGuard guard(builder);
9987 builder.SetCurrentDebugLocation(llvm::DebugLoc());
9988 // We just return the unaltered argument for the host function
9989 // for now, some alterations may be required in the future to
9990 // keep host fallback functions working identically to the device
9991 // version (e.g. pass ByCopy values should be treated as such on
9992 // host and device, currently not always the case)
9993 if (!isTargetDevice) {
9994 retVal = cast<llvm::Value>(&arg);
9995 return codeGenIP;
9996 }
9997
9998 return createDeviceArgumentAccessor(targetOp, mapData, arg, input, retVal,
9999 builder, *ompBuilder, moduleTranslation,
10000 allocaIP, codeGenIP, deallocIPs);
10001 };
10002
10003 llvm::OpenMPIRBuilder::TargetKernelRuntimeAttrs runtimeAttrs;
10004 llvm::OpenMPIRBuilder::TargetKernelDefaultAttrs defaultAttrs;
10005 Operation *targetCapturedOp =
10006 cast<omp::ComposableOpInterface>(*targetOp).findCapturedOp();
10007 initTargetDefaultAttrs(targetOp, targetCapturedOp, defaultAttrs,
10008 isTargetDevice, isGPU);
10009
10010 // Collect host-evaluated values needed to properly launch the kernel from the
10011 // host.
10012 if (!isTargetDevice)
10013 initTargetRuntimeAttrs(builder, moduleTranslation, targetOp,
10014 targetCapturedOp, runtimeAttrs);
10015
10016 // Pass host-evaluated values as parameters to the kernel / host fallback,
10017 // except if they are constants. In any case, map the MLIR block argument to
10018 // the corresponding LLVM values.
10020 SmallVector<Value> hostEvalVars = targetOp.getHostEvalVars();
10021 ArrayRef<BlockArgument> hostEvalBlockArgs = argIface.getHostEvalBlockArgs();
10022 for (auto [arg, var] : llvm::zip_equal(hostEvalBlockArgs, hostEvalVars)) {
10023 llvm::Value *value = moduleTranslation.lookupValue(var);
10024 moduleTranslation.mapValue(arg, value);
10025
10026 if (!llvm::isa<llvm::Constant>(value))
10027 kernelInput.push_back(value);
10028 }
10029
10030 for (size_t i = 0, e = mapData.OriginalValue.size(); i != e; ++i) {
10031 // 1) Declare target arguments are not passed to kernels as arguments.
10032 // 2) Attach maps are not passed in as arguments to kernels, except for
10033 // private attach maps used for corresponding-pointer initialization.
10034 // 3) Children of record objects are not passed in as arguments.
10035 // TODO: We currently do not handle cases where a member is explicitly
10036 // passed in as an argument, this will likley need to be handled in
10037 // the near future, rather than using IsAMember, it may be better to
10038 // test if the relevant BlockArg is used within the target region and
10039 // then use that as a basis for exclusion in the kernel inputs.
10040 using MapFlags = llvm::omp::OpenMPOffloadMappingFlags;
10041 bool isAttachMap = (mapData.Types[i] & MapFlags::OMP_MAP_ATTACH) ==
10042 MapFlags::OMP_MAP_ATTACH;
10043 bool isPrivateTargetParam =
10044 (mapData.Types[i] &
10045 (MapFlags::OMP_MAP_PRIVATE | MapFlags::OMP_MAP_TARGET_PARAM)) ==
10046 (MapFlags::OMP_MAP_PRIVATE | MapFlags::OMP_MAP_TARGET_PARAM);
10047
10048 if (!mapData.IsDeclareTarget[i] && !mapData.IsAMember[i] &&
10049 (!isAttachMap || (isAttachMap && isPrivateTargetParam)))
10050 kernelInput.push_back(mapData.OriginalValue[i]);
10051 }
10052
10054 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
10055 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
10056
10057 llvm::OpenMPIRBuilder::DependenciesInfo dds;
10058 if (failed(buildDependData(
10059 targetOp.getDependVars(), targetOp.getDependKinds(),
10060 targetOp.getDependIterated(), targetOp.getDependIteratedKinds(),
10061 builder, moduleTranslation, dds)))
10062 return failure();
10063
10064 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
10065
10066 llvm::OpenMPIRBuilder::TargetDataInfo info(
10067 /*RequiresDevicePointerInfo=*/false,
10068 /*SeparateBeginEndCalls=*/true);
10069
10070 auto customMapperCB =
10071 [&](unsigned int i) -> llvm::Expected<llvm::Function *> {
10072 if (!combinedInfos.Mappers[i])
10073 return nullptr;
10074 info.HasMapper = true;
10075 return getOrCreateUserDefinedMapperFunc(combinedInfos.Mappers[i], builder,
10076 moduleTranslation, targetDirective);
10077 };
10078
10079 llvm::Value *ifCond = nullptr;
10080 if (Value targetIfCond = targetOp.getIfExpr())
10081 ifCond = moduleTranslation.lookupValue(targetIfCond);
10082
10083 Value dynGroupPrivateSize = targetOp.getDynGroupprivateSize();
10084 llvm::Value *dynSizeVal = nullptr;
10085 if (dynGroupPrivateSize) {
10086 dynSizeVal = moduleTranslation.lookupValue(dynGroupPrivateSize);
10087 dynSizeVal = builder.CreateIntCast(dynSizeVal, builder.getInt32Ty(),
10088 /*isSigned=*/false);
10089 }
10090
10091 llvm::omp::OMPDynGroupprivateFallbackType fallbackType =
10092 getDynGroupprivateFallbackType(targetOp.getDynGroupprivateFallbackAttr());
10093
10094 // Pass the target region's source location to the runtime, taken from the
10095 // op's own location. Restricted to the host offload path that actually emits
10096 // the kernel launch, to avoid creating an unused identifier on the device.
10097 llvm::Value *rtLocOverride =
10098 (!isTargetDevice && isOffloadEntry)
10099 ? getSourceLocIdentFromOp(builder, *ompBuilder, targetOp)
10100 : nullptr;
10101
10102 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
10103 moduleTranslation.getOpenMPBuilder()->createTarget(
10104 ompLoc, isOffloadEntry, allocaIP, builder.saveIP(), deallocBlocks,
10105 info, entryInfo, defaultAttrs, runtimeAttrs, ifCond, kernelInput,
10106 genMapInfoCB, bodyCB, argAccessorCB, customMapperCB, dds,
10107 targetOp.getNowait(), dynSizeVal, fallbackType, outlinedFnDbgLoc,
10108 rtLocOverride);
10109
10110 if (failed(handleError(afterIP, opInst)))
10111 return failure();
10112
10113 builder.restoreIP(*afterIP);
10114
10115 if (dds.DepArray)
10116 builder.CreateFree(dds.DepArray);
10117
10118 return success();
10119}
10120
10121static LogicalResult
10122convertDeclareTargetAttr(Operation *op, mlir::omp::DeclareTargetAttr attribute,
10123 llvm::OpenMPIRBuilder *ompBuilder,
10124 LLVM::ModuleTranslation &moduleTranslation) {
10125 // Amend omp.declare_target by deleting the IR of the outlined functions
10126 // created for target regions. They cannot be filtered out from MLIR earlier
10127 // because the omp.target operation inside must be translated to LLVM, but
10128 // the wrapper functions themselves must not remain at the end of the
10129 // process. We know that functions where omp.declare_target does not match
10130 // omp.is_target_device at this stage can only be wrapper functions because
10131 // those that aren't are removed earlier as an MLIR transformation pass.
10132 if (FunctionOpInterface funcOp = dyn_cast<FunctionOpInterface>(op)) {
10133 if (auto offloadMod = dyn_cast<omp::OffloadModuleInterface>(
10134 op->getParentOfType<ModuleOp>().getOperation())) {
10135 if (!offloadMod.getIsTargetDevice())
10136 return success();
10137
10138 omp::DeclareTargetDeviceType declareType = attribute.getDeviceType();
10139
10140 if (declareType == omp::DeclareTargetDeviceType::host) {
10141 llvm::Function *llvmFunc =
10142 moduleTranslation.lookupFunction(funcOp.getName());
10143 llvmFunc->dropAllReferences();
10144 llvmFunc->eraseFromParent();
10145
10146 // Invalidate the builder's current insertion point, as it now points to
10147 // a deleted block.
10148 ompBuilder->Builder.ClearInsertionPoint();
10149 ompBuilder->Builder.SetCurrentDebugLocation(llvm::DebugLoc());
10150 } else if (llvm::Function *llvmFunc =
10151 moduleTranslation.lookupFunction(funcOp.getName())) {
10152 // Device-side declare target functions are externally visible by
10153 // default so they can be referenced from other device translation
10154 // units. That also prevents the offload LTO from internalizing and
10155 // deleting them when they end up unused in the final device image.
10156 // Such dead functions can still reference internal LDS and trigger
10157 // spurious "local memory global used by non-kernel function" backend
10158 // warnings. Marking them hidden keeps the symbol usable within the
10159 // device image's linkage unit while letting LTO drop it when nothing
10160 // references it; symbols that must stay reachable (e.g. via an offload
10161 // entry that takes their address) are kept alive by that reference.
10162 if (!llvmFunc->isDeclaration() && llvmFunc->hasExternalLinkage() &&
10163 llvmFunc->getVisibility() == llvm::GlobalValue::DefaultVisibility)
10164 llvmFunc->setVisibility(llvm::GlobalValue::HiddenVisibility);
10165 }
10166 }
10167 return success();
10168 }
10169
10170 if (LLVM::GlobalOp gOp = dyn_cast<LLVM::GlobalOp>(op)) {
10171 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
10172 if (auto *gVal = llvmModule->getNamedValue(gOp.getSymName())) {
10173 auto *gVar = cast<llvm::GlobalVariable>(gVal);
10174 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
10175 bool isDeclaration = gOp.isDeclaration();
10176 bool isExternallyVisible =
10177 gOp.getVisibility() != mlir::SymbolTable::Visibility::Private;
10178 auto loc = op->getLoc()->findInstanceOf<FileLineColLoc>();
10179 llvm::StringRef mangledName = gOp.getSymName();
10180 mlir::omp::DeclareTargetCaptureClause captureClause =
10181 attribute.getCaptureClause();
10182 auto captureClauseKind = convertToCaptureClauseKind(captureClause);
10183 auto deviceClause = convertToDeviceClauseKind(attribute.getDeviceType());
10184 llvm::StringRef entryMangledName = mangledName;
10185 llvm::Constant *entryAddr = llvm::cast<llvm::Constant>(gVal);
10186 std::function<llvm::GlobalValue::LinkageTypes()> variableLinkage;
10187 llvm::SmallString<128> entryNameStorage;
10188 bool requiresUSM = ompBuilder->Config.hasRequiresUnifiedSharedMemory();
10189 bool isToOrEnter =
10190 captureClause == omp::DeclareTargetCaptureClause::to ||
10191 captureClause == omp::DeclareTargetCaptureClause::enter;
10192 bool isHostOnly =
10193 attribute.getDeviceType() == omp::DeclareTargetDeviceType::host;
10194
10195 // A to/enter declare-target variable needs a device-resident,
10196 // name-resolvable copy and a host offloading entry. A local-linkage
10197 // global provides neither, so we promote it to external.
10198 if (isToOrEnter && !isHostOnly && !requiresUSM &&
10199 gVar->hasLocalLinkage()) {
10200 gVar->setLinkage(llvm::GlobalValue::ExternalLinkage);
10201 isExternallyVisible = true;
10202
10203 // Clear the stale dso_local flag so it is referenced like a
10204 // module-scope declare target global.
10205 if (ompBuilder->Config.isTargetDevice())
10206 gVar->setDSOLocal(false);
10207 }
10208
10209 if (isToOrEnter &&
10210 deviceClause ==
10211 llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseAny &&
10212 !requiresUSM && !isDeclaration &&
10213 (gVal->hasLocalLinkage() || gVal->hasHiddenVisibility())) {
10214 // Keep the original symbol as-is for target code, but create a visible
10215 // alias for the offload entry so libomptarget can associate the host
10216 // global with the actual device global.
10217 entryNameStorage = (mangledName + llvm::Twine("_decl_tgt_entry")).str();
10218 entryMangledName = entryNameStorage;
10219 if (llvm::GlobalValue *existing =
10220 llvmModule->getNamedValue(entryMangledName)) {
10221 entryAddr = llvm::cast<llvm::Constant>(existing);
10222 } else {
10223 entryAddr = llvm::GlobalAlias::create(
10224 gVal->getValueType(), gVal->getAddressSpace(),
10225 llvm::GlobalValue::WeakAnyLinkage, entryMangledName, entryAddr,
10226 llvmModule);
10227 llvm::cast<llvm::GlobalAlias>(entryAddr)->setVisibility(
10228 llvm::GlobalValue::DefaultVisibility);
10229 }
10230 variableLinkage = [] { return llvm::GlobalValue::WeakAnyLinkage; };
10231 }
10232 // unused for MLIR at the moment, required in Clang for book
10233 // keeping
10234 std::vector<llvm::GlobalVariable *> generatedRefs;
10235
10236 std::vector<llvm::Triple> targetTriple;
10237 auto targetTripleAttr = dyn_cast_or_null<mlir::StringAttr>(
10238 op->getParentOfType<mlir::ModuleOp>()->getDiscardableAttr(
10239 LLVM::LLVMDialect::getTargetTripleAttrName()));
10240 if (targetTripleAttr)
10241 targetTriple.emplace_back(targetTripleAttr.data());
10242
10243 auto fileInfoCallBack = [&loc]() {
10244 std::string filename = "";
10245 std::uint64_t lineNo = 0;
10246
10247 if (loc) {
10248 filename = loc.getFilename().str();
10249 lineNo = loc.getLine();
10250 }
10251
10252 return std::pair<std::string, std::uint64_t>(llvm::StringRef(filename),
10253 lineNo);
10254 };
10255
10256 llvm::vfs::FileSystem &vfs = moduleTranslation.getFileSystem();
10257 ompBuilder->registerTargetGlobalVariable(
10258 captureClauseKind, deviceClause, isDeclaration, isExternallyVisible,
10259 ompBuilder->getTargetEntryUniqueInfo(fileInfoCallBack, vfs),
10260 entryMangledName, generatedRefs, /*OpenMPSimd*/ false, targetTriple,
10261 /*GlobalInitializer*/ nullptr, variableLinkage, gVal->getType(),
10262 entryAddr);
10263
10264 if (ompBuilder->Config.isTargetDevice() &&
10265 (captureClause == omp::DeclareTargetCaptureClause::link ||
10266 requiresUSM)) {
10267 // For USM and link we generate a global reference pointer in the
10268 // default address space (e.g address space 0), as opposed to the
10269 // globals original type and address space.
10270 llvm::Type *ptrTy = llvm::PointerType::get(llvmModule->getContext(), 0);
10271 llvm::Constant *refPtr = ompBuilder->getAddrOfDeclareTargetVar(
10272 captureClauseKind, deviceClause, isDeclaration, isExternallyVisible,
10273 ompBuilder->getTargetEntryUniqueInfo(fileInfoCallBack, vfs),
10274 mangledName, generatedRefs, /*OpenMPSimd*/ false, targetTriple,
10275 ptrTy, /*GlobalInitializer*/ nullptr,
10276 /*VariableLinkage*/ nullptr);
10277
10278 // For indirectly-accessed global pointers, we rely on "internal"
10279 // linkage to optimize out the unneeded full-variable storage later,
10280 // since we can't prevent the LLVM dialect from generating globals
10281 // without also breaking target lowering. However, We can only do
10282 // this for definiions, as global variable declarations must have
10283 // external or weak linkage.
10284 if (refPtr) {
10285 if (!gVar->isDeclaration())
10286 gVar->setLinkage(llvm::GlobalValue::InternalLinkage);
10287
10288 // Register the (original global, reference pointer) pair so that the
10289 // OpenMPIRBuilder can rewrite uses of the original global during
10290 // finalization.
10291 if (auto *newGV =
10292 dyn_cast<llvm::GlobalValue>(refPtr->stripPointerCasts()))
10293 ompBuilder->registerDeclareTargetGlobalReplacement(gVal, newGV);
10294 }
10295 }
10296
10297 // Mark 'device_type(host) enter(...)' variables as external in the device
10298 // since they're not supposed to have their own copy. This will cause
10299 // linker errors if accesses are attempted from the target device.
10300 if (ompBuilder->Config.isTargetDevice() && isHostOnly && isToOrEnter) {
10301 gVar->setLinkage(llvm::GlobalValue::ExternalLinkage);
10302 gVar->setInitializer(nullptr);
10303 }
10304 }
10305 }
10306
10307 return success();
10308}
10309
10310namespace {
10311
10312/// Implementation of the dialect interface that converts operations belonging
10313/// to the OpenMP dialect to LLVM IR.
10314class OpenMPDialectLLVMIRTranslationInterface
10315 : public LLVMTranslationDialectInterface {
10316public:
10317 using LLVMTranslationDialectInterface::LLVMTranslationDialectInterface;
10318
10319 /// Translates the given operation to LLVM IR using the provided IR builder
10320 /// and saving the state in `moduleTranslation`.
10321 LogicalResult
10322 convertOperation(Operation *op, llvm::IRBuilderBase &builder,
10323 LLVM::ModuleTranslation &moduleTranslation) const final;
10324
10325 /// Given an OpenMP MLIR attribute, create the corresponding LLVM-IR,
10326 /// runtime calls, or operation amendments
10327 LogicalResult
10328 amendOperation(Operation *op, ArrayRef<llvm::Instruction *> instructions,
10329 NamedAttribute attribute,
10330 LLVM::ModuleTranslation &moduleTranslation) const final;
10331
10332 /// Records the LLVM alloc pointer produced for an OMP ALLOCATE variable so
10333 /// that the paired omp.allocate_free op can generate the matching
10334 /// __kmpc_free call.
10335 void registerAllocatedPtr(Value var, llvm::Value *ptr) const {
10336 ompAllocatedPtrs[var] = ptr;
10337 }
10338
10339 /// Returns the LLVM alloc pointer previously registered for var, or
10340 /// nullptr if no allocation was recorded.
10341 llvm::Value *lookupAllocatedPtr(Value var) const {
10342 auto it = ompAllocatedPtrs.find(var);
10343 return it != ompAllocatedPtrs.end() ? it->second : nullptr;
10344 }
10345
10346private:
10347 /// Maps each MLIR variable value that appeared in an omp.allocate_dir op to
10348 /// the LLVM pointer returned by the corresponding __kmpc_alloc call. The
10349 /// paired omp.allocate_free op looks up these pointers to emit __kmpc_free.
10350 mutable DenseMap<Value, llvm::Value *> ompAllocatedPtrs;
10351};
10352
10353} // namespace
10354
10355LogicalResult OpenMPDialectLLVMIRTranslationInterface::amendOperation(
10356 Operation *op, ArrayRef<llvm::Instruction *> instructions,
10357 NamedAttribute attribute,
10358 LLVM::ModuleTranslation &moduleTranslation) const {
10359 return llvm::StringSwitch<llvm::function_ref<LogicalResult(Attribute)>>(
10360 attribute.getName())
10361 .Case("omp.is_target_device",
10362 [&](Attribute attr) {
10363 if (auto deviceAttr = dyn_cast<BoolAttr>(attr)) {
10364 llvm::OpenMPIRBuilderConfig &config =
10365 moduleTranslation.getOpenMPBuilder()->Config;
10366 config.setIsTargetDevice(deviceAttr.getValue());
10367 return success();
10368 }
10369 return failure();
10370 })
10371 .Case("omp.is_gpu",
10372 [&](Attribute attr) {
10373 if (auto gpuAttr = dyn_cast<BoolAttr>(attr)) {
10374 llvm::OpenMPIRBuilderConfig &config =
10375 moduleTranslation.getOpenMPBuilder()->Config;
10376 config.setIsGPU(gpuAttr.getValue());
10377 return success();
10378 }
10379 return failure();
10380 })
10381 .Case("omp.host_ir_filepath",
10382 [&](Attribute attr) {
10383 if (auto filepathAttr = dyn_cast<StringAttr>(attr)) {
10384 llvm::OpenMPIRBuilder *ompBuilder =
10385 moduleTranslation.getOpenMPBuilder();
10386 ompBuilder->loadOffloadInfoMetadata(
10387 moduleTranslation.getFileSystem(), filepathAttr.getValue());
10388 return success();
10389 }
10390 return failure();
10391 })
10392 .Case("omp.flags",
10393 [&](Attribute attr) {
10394 if (auto rtlAttr = dyn_cast<omp::FlagsAttr>(attr))
10395 return convertFlagsAttr(op, rtlAttr, moduleTranslation);
10396 return failure();
10397 })
10398 .Case("omp.version",
10399 [&](Attribute attr) {
10400 if (auto versionAttr = dyn_cast<omp::VersionAttr>(attr)) {
10401 llvm::OpenMPIRBuilder *ompBuilder =
10402 moduleTranslation.getOpenMPBuilder();
10403 ompBuilder->M.addModuleFlag(llvm::Module::Max, "openmp",
10404 versionAttr.getVersion());
10405 return success();
10406 }
10407 return failure();
10408 })
10409 .Case("omp.declare_target",
10410 [&](Attribute attr) {
10411 if (auto declareTargetAttr =
10412 dyn_cast<omp::DeclareTargetAttr>(attr)) {
10413 llvm::OpenMPIRBuilder *ompBuilder =
10414 moduleTranslation.getOpenMPBuilder();
10415 return convertDeclareTargetAttr(op, declareTargetAttr,
10416 ompBuilder, moduleTranslation);
10417 }
10418 return failure();
10419 })
10420 .Case("omp.requires",
10421 [&](Attribute attr) {
10422 if (auto requiresAttr = dyn_cast<omp::ClauseRequiresAttr>(attr)) {
10423 using Requires = omp::ClauseRequires;
10424 Requires flags = requiresAttr.getValue();
10425 llvm::OpenMPIRBuilderConfig &config =
10426 moduleTranslation.getOpenMPBuilder()->Config;
10427 config.setHasRequiresReverseOffload(
10428 bitEnumContainsAll(flags, Requires::reverse_offload));
10429 config.setHasRequiresUnifiedAddress(
10430 bitEnumContainsAll(flags, Requires::unified_address));
10431 config.setHasRequiresUnifiedSharedMemory(
10432 bitEnumContainsAll(flags, Requires::unified_shared_memory));
10433 config.setHasRequiresDynamicAllocators(
10434 bitEnumContainsAll(flags, Requires::dynamic_allocators));
10435 return success();
10436 }
10437 return failure();
10438 })
10439 .Case("omp.target_triples",
10440 [&](Attribute attr) {
10441 if (auto triplesAttr = dyn_cast<ArrayAttr>(attr)) {
10442 llvm::OpenMPIRBuilderConfig &config =
10443 moduleTranslation.getOpenMPBuilder()->Config;
10444 config.TargetTriples.clear();
10445 config.TargetTriples.reserve(triplesAttr.size());
10446 for (Attribute tripleAttr : triplesAttr) {
10447 if (auto tripleStrAttr = dyn_cast<StringAttr>(tripleAttr))
10448 config.TargetTriples.emplace_back(tripleStrAttr.getValue());
10449 else
10450 return failure();
10451 }
10452 return success();
10453 }
10454 return failure();
10455 })
10456 .Case("omp.integer_wrap_around",
10457 [&](Attribute attr) {
10458 if (auto wrapAttr = dyn_cast<omp::IntegerWrapAroundAttr>(attr)) {
10459 llvm::OpenMPIRBuilderConfig &config =
10460 moduleTranslation.getOpenMPBuilder()->Config;
10461 config.setNoSignedWrap(!wrapAttr.getIntegerWrapAround());
10462 return success();
10463 }
10464 return failure();
10465 })
10466 .Default([](Attribute) {
10467 // Fall through for omp attributes that do not require lowering.
10468 return success();
10469 })(attribute.getValue());
10470
10471 return failure();
10472}
10473
10474// Returns true if the operation is not inside a TargetOp, it is part of a
10475// function and that function is not declare target.
10476static bool isHostDeviceOp(Operation *op) {
10477 // Assumes no reverse offloading
10478 if (op->getParentOfType<omp::TargetOp>())
10479 return false;
10480
10481 if (auto parentFn = op->getParentOfType<LLVM::LLVMFuncOp>()) {
10482 if (auto declareTargetIface =
10483 llvm::dyn_cast<mlir::omp::DeclareTargetInterface>(
10484 parentFn.getOperation())) {
10485 omp::DeclareTargetAttr declareTargetAttr =
10486 declareTargetIface.getDeclareTarget();
10487 if (declareTargetAttr && declareTargetAttr.getDeviceType() !=
10488 mlir::omp::DeclareTargetDeviceType::host)
10489 return false;
10490 }
10491
10492 return true;
10493 }
10494
10495 return false;
10496}
10497
10498static llvm::Function *getOmpTargetAlloc(llvm::IRBuilderBase &builder,
10499 llvm::Module *llvmModule) {
10500 llvm::Type *i64Ty = builder.getInt64Ty();
10501 llvm::Type *i32Ty = builder.getInt32Ty();
10502 llvm::Type *returnType = builder.getPtrTy(0);
10503 llvm::FunctionType *fnType =
10504 llvm::FunctionType::get(returnType, {i64Ty, i32Ty}, false);
10505 llvm::Function *func = cast<llvm::Function>(
10506 llvmModule->getOrInsertFunction("omp_target_alloc", fnType).getCallee());
10507 return func;
10508}
10509
10510template <typename T>
10511static llvm::Value *
10512getAllocationSize(llvm::IRBuilderBase &builder,
10513 LLVM::ModuleTranslation &moduleTranslation, T op) {
10514 llvm::DataLayout dataLayout =
10515 moduleTranslation.getLLVMModule()->getDataLayout();
10516 llvm::Type *llvmHeapTy =
10517 moduleTranslation.convertType(op.getMemElemTypeAttr().getValue());
10518
10519 auto alignment = op.getMemAlignment();
10520 llvm::TypeSize typeSize = llvm::alignTo(
10521 dataLayout.getTypeStoreSize(llvmHeapTy),
10522 alignment ? *alignment : dataLayout.getABITypeAlign(llvmHeapTy).value());
10523
10524 llvm::Value *allocSize = builder.getInt64(typeSize.getFixedValue());
10525 return builder.CreateMul(
10526 allocSize,
10527 builder.CreateIntCast(moduleTranslation.lookupValue(op.getMemArraySize()),
10528 builder.getInt64Ty(),
10529 /*isSigned=*/false));
10530}
10531
10532template <>
10533llvm::Value *getAllocationSize(llvm::IRBuilderBase &builder,
10534 LLVM::ModuleTranslation &moduleTranslation,
10535 omp::TargetAllocMemOp op) {
10536 llvm::DataLayout dataLayout =
10537 moduleTranslation.getLLVMModule()->getDataLayout();
10538 llvm::Type *llvmHeapTy = moduleTranslation.convertType(op.getAllocatedType());
10539 llvm::TypeSize typeSize = dataLayout.getTypeAllocSize(llvmHeapTy);
10540 llvm::Value *allocSize = builder.getInt64(typeSize.getFixedValue());
10541 for (auto typeParam : op.getTypeparams()) {
10542 allocSize = builder.CreateMul(
10543 allocSize,
10544 builder.CreateIntCast(moduleTranslation.lookupValue(typeParam),
10545 builder.getInt64Ty(),
10546 /*isSigned=*/false));
10547 }
10548 return allocSize;
10549}
10550
10551static LogicalResult
10552convertTargetAllocMemOp(Operation &opInst, llvm::IRBuilderBase &builder,
10553 LLVM::ModuleTranslation &moduleTranslation) {
10554 auto allocMemOp = cast<omp::TargetAllocMemOp>(opInst);
10555 if (!allocMemOp)
10556 return failure();
10557
10558 // Get "omp_target_alloc" function
10559 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
10560 llvm::Function *ompTargetAllocFunc = getOmpTargetAlloc(builder, llvmModule);
10561 // Get the corresponding device value in llvm
10562 mlir::Value deviceNum = allocMemOp.getDevice();
10563 llvm::Value *llvmDeviceNum = moduleTranslation.lookupValue(deviceNum);
10564 // Get the allocation size.
10565 llvm::Value *allocSize =
10566 getAllocationSize(builder, moduleTranslation, allocMemOp);
10567 // Create call to "omp_target_alloc" with the args as translated llvm values.
10568 llvm::CallInst *call =
10569 builder.CreateCall(ompTargetAllocFunc, {allocSize, llvmDeviceNum});
10570 llvm::Value *resultI64 = builder.CreatePtrToInt(call, builder.getInt64Ty());
10571
10572 // Map the result
10573 moduleTranslation.mapValue(allocMemOp.getResult(), resultI64);
10574 return success();
10575}
10576
10577static LogicalResult
10578convertAllocSharedMemOp(omp::AllocSharedMemOp allocMemOp,
10579 llvm::IRBuilderBase &builder,
10580 LLVM::ModuleTranslation &moduleTranslation) {
10581 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
10582 llvm::Value *size = getAllocationSize(builder, moduleTranslation, allocMemOp);
10583 moduleTranslation.mapValue(allocMemOp.getResult(),
10584 ompBuilder->createOMPAllocShared(builder, size));
10585 return success();
10586}
10587
10588static LogicalResult
10589convertAllocateDirOp(Operation &opInst, llvm::IRBuilderBase &builder,
10590 LLVM::ModuleTranslation &moduleTranslation,
10591 const OpenMPDialectLLVMIRTranslationInterface &ompIface) {
10592 auto allocateDirOp = cast<omp::AllocateDirOp>(opInst);
10593 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
10594
10595 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
10596 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
10597 llvm::DataLayout dataLayout = llvmModule->getDataLayout();
10598 SmallVector<Value> vars = allocateDirOp.getVarList();
10599 std::optional<int64_t> alignAttr = allocateDirOp.getAlign();
10600
10601 llvm::Value *allocator;
10602 if (auto allocatorVar = allocateDirOp.getAllocator()) {
10603 allocator = moduleTranslation.lookupValue(allocatorVar);
10604 if (allocator->getType()->isIntegerTy())
10605 allocator = builder.CreateIntToPtr(allocator, builder.getPtrTy());
10606 else if (allocator->getType()->isPointerTy())
10607 allocator = builder.CreatePointerBitCastOrAddrSpaceCast(
10608 allocator, builder.getPtrTy());
10609 } else {
10610 allocator = llvm::ConstantPointerNull::get(builder.getPtrTy());
10611 }
10612
10613 for (Value var : vars) {
10614 Value baseVar = getBaseValueForTypeLookup(var);
10615 llvm::Type *typeToInspect =
10616 getAllocatedLlvmTypeForVariable(var, baseVar, moduleTranslation);
10617
10618 llvm::Value *size;
10619 if (std::optional<llvm::Value *> dynamicSize = getDynamicAllocatedSize(
10620 var, baseVar, moduleTranslation, builder, dataLayout)) {
10621 size = *dynamicSize;
10622 } else if (typeToInspect->isArrayTy()) {
10623 size = builder.getInt64(
10624 dataLayout.getTypeAllocSize(typeToInspect).getFixedValue());
10625 } else {
10626 size = builder.getInt64(
10627 dataLayout.getTypeAllocSize(typeToInspect).getFixedValue());
10628 }
10629
10630 uint64_t alignValue =
10631 alignAttr ? alignAttr.value()
10632 : dataLayout.getABITypeAlign(typeToInspect).value();
10633 llvm::Value *alignConst = builder.getInt64(alignValue);
10634 // Align the size: ((size + align - 1) / align) * align
10635 size = builder.CreateAdd(size, builder.getInt64(alignValue - 1), "", true);
10636 size = builder.CreateUDiv(size, alignConst);
10637 size = builder.CreateMul(size, alignConst, "", true);
10638
10639 std::string allocName =
10640 ompBuilder->createPlatformSpecificName({".void.addr"});
10641 llvm::CallInst *allocCall;
10642 if (alignAttr.has_value()) {
10643 allocCall = ompBuilder->createOMPAlignedAlloc(
10644 ompLoc, builder.getInt64(alignAttr.value()), size, allocator,
10645 allocName);
10646 } else {
10647 allocCall =
10648 ompBuilder->createOMPAlloc(ompLoc, size, allocator, allocName);
10649 }
10650 // Record the alloc pointer keyed by the MLIR variable value.
10651 ompIface.registerAllocatedPtr(var, allocCall);
10652
10653 if (llvm::Value *baseLlvm = moduleTranslation.lookupValue(baseVar)) {
10654 llvm::Value *boundPtr = builder.CreatePointerBitCastOrAddrSpaceCast(
10655 allocCall, baseLlvm->getType());
10656 moduleTranslation.remapAllValuesWith(baseLlvm, boundPtr);
10657 } else if (llvm::Value *varLlvm = moduleTranslation.lookupValue(var)) {
10658 llvm::Value *boundPtr = builder.CreatePointerBitCastOrAddrSpaceCast(
10659 allocCall, varLlvm->getType());
10660 moduleTranslation.remapAllValuesWith(varLlvm, boundPtr);
10661 }
10662 }
10663
10664 return success();
10665}
10666
10667static LogicalResult
10668convertAllocateFreeOp(Operation &opInst, llvm::IRBuilderBase &builder,
10669 LLVM::ModuleTranslation &moduleTranslation,
10670 const OpenMPDialectLLVMIRTranslationInterface &ompIface) {
10671 auto freeOp = cast<omp::AllocateFreeOp>(opInst);
10672 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
10673 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
10674
10675 llvm::Value *allocator;
10676 if (auto allocatorVar = freeOp.getAllocator()) {
10677 allocator = moduleTranslation.lookupValue(allocatorVar);
10678 if (allocator->getType()->isIntegerTy())
10679 allocator = builder.CreateIntToPtr(allocator, builder.getPtrTy());
10680 else if (allocator->getType()->isPointerTy())
10681 allocator = builder.CreatePointerBitCastOrAddrSpaceCast(
10682 allocator, builder.getPtrTy());
10683 } else {
10684 allocator = llvm::ConstantPointerNull::get(builder.getPtrTy());
10685 }
10686
10687 // Emit __kmpc_free for each variable in reverse allocation order.
10688 SmallVector<Value> vars = freeOp.getVarList();
10689 for (Value var : llvm::reverse(vars)) {
10690 llvm::Value *allocPtr = ompIface.lookupAllocatedPtr(var);
10691 if (!allocPtr)
10692 return opInst.emitError("omp.allocate_free: no allocation recorded");
10693 ompBuilder->createOMPFree(ompLoc, allocPtr, allocator, "");
10694 }
10695
10696 return success();
10697}
10698
10699static llvm::Function *getOmpTargetFree(llvm::IRBuilderBase &builder,
10700 llvm::Module *llvmModule) {
10701 llvm::Type *ptrTy = builder.getPtrTy(0);
10702 llvm::Type *i32Ty = builder.getInt32Ty();
10703 llvm::Type *voidTy = builder.getVoidTy();
10704 llvm::FunctionType *fnType =
10705 llvm::FunctionType::get(voidTy, {ptrTy, i32Ty}, false);
10706 llvm::Function *func = dyn_cast<llvm::Function>(
10707 llvmModule->getOrInsertFunction("omp_target_free", fnType).getCallee());
10708 return func;
10709}
10710
10711static LogicalResult
10712convertTargetFreeMemOp(Operation &opInst, llvm::IRBuilderBase &builder,
10713 LLVM::ModuleTranslation &moduleTranslation) {
10714 auto freeMemOp = cast<omp::TargetFreeMemOp>(opInst);
10715 if (!freeMemOp)
10716 return failure();
10717
10718 // Get "omp_target_free" function
10719 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
10720 llvm::Function *ompTragetFreeFunc = getOmpTargetFree(builder, llvmModule);
10721 // Get the corresponding device value in llvm
10722 mlir::Value deviceNum = freeMemOp.getDevice();
10723 llvm::Value *llvmDeviceNum = moduleTranslation.lookupValue(deviceNum);
10724 // Get the corresponding heapref value in llvm
10725 mlir::Value heapref = freeMemOp.getHeapref();
10726 llvm::Value *llvmHeapref = moduleTranslation.lookupValue(heapref);
10727 // Convert heapref int to ptr and call "omp_target_free"
10728 llvm::Value *intToPtr =
10729 builder.CreateIntToPtr(llvmHeapref, builder.getPtrTy(0));
10730 builder.CreateCall(ompTragetFreeFunc, {intToPtr, llvmDeviceNum});
10731 return success();
10732}
10733
10734static LogicalResult
10735convertFreeSharedMemOp(omp::FreeSharedMemOp freeMemOp,
10736 llvm::IRBuilderBase &builder,
10737 LLVM::ModuleTranslation &moduleTranslation) {
10738 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
10739 llvm::Value *size = getAllocationSize(builder, moduleTranslation, freeMemOp);
10740 ompBuilder->createOMPFreeShared(
10741 builder, moduleTranslation.lookupValue(freeMemOp.getHeapref()), size);
10742 return success();
10743}
10744
10745/// Converts an OpenMP groupprivate operation into LLVM IR.
10746static LogicalResult
10747convertOmpGroupprivate(Operation &opInst, llvm::IRBuilderBase &builder,
10748 LLVM::ModuleTranslation &moduleTranslation) {
10749 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
10750 auto groupprivateOp = cast<omp::GroupprivateOp>(opInst);
10751
10752 if (failed(checkImplementationStatus(opInst)))
10753 return failure();
10754
10755 bool isTargetDevice = ompBuilder->Config.isTargetDevice();
10756
10757 // Determine whether group-private storage should be allocated based on
10758 // device_type. When not specified, default to 'any' (allocate on both).
10759 bool shouldAllocate = true;
10760 switch (groupprivateOp.getDeviceType().value_or(
10761 mlir::omp::DeclareTargetDeviceType::any)) {
10762 case mlir::omp::DeclareTargetDeviceType::host:
10763 shouldAllocate = !isTargetDevice;
10764 break;
10765 case mlir::omp::DeclareTargetDeviceType::nohost:
10766 shouldAllocate = isTargetDevice;
10767 break;
10768 case mlir::omp::DeclareTargetDeviceType::any:
10769 shouldAllocate = true;
10770 break;
10771 }
10772
10773 // Look up the global variable directly by symbol name.
10775 &opInst, groupprivateOp.getSymNameAttr());
10776 if (!global)
10777 return opInst.emitError()
10778 << "expected symbol '" << groupprivateOp.getSymName()
10779 << "' to reference an LLVM global variable";
10780
10781 llvm::GlobalValue *globalValue = moduleTranslation.lookupGlobal(global);
10782 llvm::Type *varType = moduleTranslation.convertType(global.getType());
10783 std::string varName = globalValue->getName().str();
10784
10785 llvm::Value *resultPtr;
10786 if (shouldAllocate && isTargetDevice) {
10787 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
10788 llvm::Triple targetTriple(llvmModule->getTargetTriple());
10789 unsigned sharedAddressSpace;
10790 if (targetTriple.isAMDGCN())
10791 sharedAddressSpace = llvm::AMDGPUAS::LOCAL_ADDRESS;
10792 else if (targetTriple.isNVPTX())
10793 sharedAddressSpace = llvm::NVPTXAS::ADDRESS_SPACE_SHARED;
10794 else
10795 return opInst.emitError() << "groupprivate is not supported for target: "
10796 << targetTriple.str();
10797 llvm::GlobalVariable *sharedVar = new llvm::GlobalVariable(
10798 *llvmModule, varType, /*isConstant=*/false,
10799 llvm::GlobalValue::InternalLinkage, llvm::PoisonValue::get(varType),
10800 varName, /*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal,
10801 sharedAddressSpace,
10802 /*isExternallyInitialized=*/false);
10803 resultPtr = sharedVar;
10804 } else {
10805 if (shouldAllocate && !isTargetDevice)
10806 opInst.emitWarning("groupprivate directive is currently ignored on the "
10807 "host, using original global");
10808 resultPtr = globalValue;
10809 }
10810
10811 moduleTranslation.mapValue(opInst.getResult(0), resultPtr);
10812 return success();
10813}
10814
10815/// Given an OpenMP MLIR operation, create the corresponding LLVM IR (including
10816/// OpenMP runtime calls).
10817LogicalResult OpenMPDialectLLVMIRTranslationInterface::convertOperation(
10818 Operation *op, llvm::IRBuilderBase &builder,
10819 LLVM::ModuleTranslation &moduleTranslation) const {
10820 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
10821
10822 if (ompBuilder->Config.isTargetDevice() &&
10823 !isa<omp::TargetOp, omp::MapInfoOp, omp::TerminatorOp, omp::YieldOp>(
10824 op) &&
10825 isHostDeviceOp(op))
10826 return op->emitOpError() << "unsupported host op found in device";
10827
10828 // For each loop, introduce one stack frame to hold loop information. Ensure
10829 // this is only done for the outermost loop wrapper to prevent introducing
10830 // multiple stack frames for a single loop. Initially set to null, the loop
10831 // information structure is initialized during translation of the nested
10832 // omp.loop_nest operation, making it available to translation of all loop
10833 // wrappers after their body has been successfully translated.
10834 bool isOutermostLoopWrapper =
10835 isa_and_present<omp::LoopWrapperInterface>(op) &&
10836 !dyn_cast_if_present<omp::LoopWrapperInterface>(op->getParentOp());
10837
10838 // The TASKLOOP construct is implemented with an outer taskloop.context
10839 // operation which is not a loop wrapper, containing an inner taskloop
10840 // operation which is a loop wrapper. The stack frame should be pushed when
10841 // translating the outer taskloop.context and popped when translating the
10842 // inner taskloop which is a loop wrapper. We need access to the loop
10843 // information in the outer taskloop context so we need to create it and pop
10844 // it around the taskloop context not the inner loop wrapper.
10845 if (isa<omp::TaskloopContextOp>(op))
10846 isOutermostLoopWrapper = true;
10847 else if (isa<omp::TaskloopWrapperOp>(op))
10848 isOutermostLoopWrapper = false;
10849
10850 if (isOutermostLoopWrapper)
10851 moduleTranslation.stackPush<OpenMPLoopInfoStackFrame>();
10852
10853 auto result =
10854 llvm::TypeSwitch<Operation *, LogicalResult>(op)
10855 .Case([&](omp::BarrierOp op) -> LogicalResult {
10857 return failure();
10858
10859 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
10860 ompBuilder->createBarrier(builder, llvm::omp::OMPD_barrier);
10861 LogicalResult res = handleError(afterIP, *op);
10862 if (res.succeeded()) {
10863 // If the barrier generated a cancellation check, the insertion
10864 // point might now need to be changed to a new continuation block
10865 builder.restoreIP(*afterIP);
10866 }
10867 return res;
10868 })
10869 .Case([&](omp::TaskyieldOp op) {
10871 return failure();
10872
10873 ompBuilder->createTaskyield(builder);
10874 return success();
10875 })
10876 .Case([&](omp::FlushOp op) {
10878 return failure();
10879
10880 // No support in Openmp runtime function (__kmpc_flush) to accept
10881 // the argument list.
10882 // OpenMP standard states the following:
10883 // "An implementation may implement a flush with a list by ignoring
10884 // the list, and treating it the same as a flush without a list."
10885 //
10886 // The argument list is discarded so that, flush with a list is
10887 // treated same as a flush without a list.
10888 ompBuilder->createFlush(builder);
10889 return success();
10890 })
10891 .Case([&](omp::ErrorOp op) {
10893 return failure();
10894
10895 llvm::Value *message = nullptr;
10896 if (mlir::Value messageExpr = op.getMessageExpr())
10897 message = moduleTranslation.lookupValue(messageExpr);
10898 else if (std::optional<StringRef> msg = op.getMessage();
10899 msg && !msg->empty())
10900 message = builder.CreateGlobalString(*msg);
10901 ompBuilder->createError(
10902 llvm::OpenMPIRBuilder::LocationDescription(builder),
10903 op.getSeverity() == omp::ClauseSeverity::fatal, message);
10904 return success();
10905 })
10906 .Case([&](omp::ParallelOp op) {
10907 return convertOmpParallel(op, builder, moduleTranslation);
10908 })
10909 .Case([&](omp::DispatchOp) {
10910 return convertOmpDispatch(*op, builder, moduleTranslation);
10911 })
10912 .Case([&](omp::MaskedOp) {
10913 return convertOmpMasked(*op, builder, moduleTranslation);
10914 })
10915 .Case([&](omp::MasterOp) {
10916 return convertOmpMaster(*op, builder, moduleTranslation);
10917 })
10918 .Case([&](omp::CriticalOp) {
10919 return convertOmpCritical(*op, builder, moduleTranslation);
10920 })
10921 .Case([&](omp::OrderedRegionOp) {
10922 return convertOmpOrderedRegion(*op, builder, moduleTranslation);
10923 })
10924 .Case([&](omp::OrderedOp) {
10925 return convertOmpOrdered(*op, builder, moduleTranslation);
10926 })
10927 .Case([&](omp::WsloopOp) {
10928 return convertOmpWsloop(*op, builder, moduleTranslation);
10929 })
10930 .Case([&](omp::SimdOp) {
10931 return convertOmpSimd(*op, builder, moduleTranslation);
10932 })
10933 .Case([&](omp::AtomicReadOp) {
10934 return convertOmpAtomicRead(*op, builder, moduleTranslation);
10935 })
10936 .Case([&](omp::AtomicWriteOp) {
10937 return convertOmpAtomicWrite(*op, builder, moduleTranslation);
10938 })
10939 .Case([&](omp::AtomicUpdateOp op) {
10940 return convertOmpAtomicUpdate(op, builder, moduleTranslation);
10941 })
10942 .Case([&](omp::AtomicCaptureOp op) {
10943 return convertOmpAtomicCapture(op, builder, moduleTranslation);
10944 })
10945 .Case([&](omp::AtomicCompareOp op) {
10946 return convertOmpAtomicCompare(op, builder, moduleTranslation);
10947 })
10948 .Case([&](omp::CancelOp op) {
10949 return convertOmpCancel(op, builder, moduleTranslation);
10950 })
10951 .Case([&](omp::CancellationPointOp op) {
10952 return convertOmpCancellationPoint(op, builder, moduleTranslation);
10953 })
10954 .Case([&](omp::SectionsOp) {
10955 return convertOmpSections(*op, builder, moduleTranslation);
10956 })
10957 .Case([&](omp::ScopeOp op) {
10958 return convertOmpScope(op, builder, moduleTranslation);
10959 })
10960 .Case([&](omp::SingleOp op) {
10961 return convertOmpSingle(op, builder, moduleTranslation);
10962 })
10963 .Case([&](omp::TeamsOp op) {
10964 return convertOmpTeams(op, builder, moduleTranslation);
10965 })
10966 .Case([&](omp::TaskOp op) {
10967 return convertOmpTaskOp(op, builder, moduleTranslation);
10968 })
10969 .Case([&](omp::TaskloopWrapperOp op) {
10970 return convertOmpTaskloopWrapperOp(op, builder, moduleTranslation);
10971 })
10972 .Case([&](omp::TaskloopContextOp op) {
10973 return convertOmpTaskloopContextOp(op, builder, moduleTranslation);
10974 })
10975 .Case([&](omp::TaskgroupOp op) {
10976 return convertOmpTaskgroupOp(op, builder, moduleTranslation);
10977 })
10978 .Case([&](omp::TaskwaitOp op) {
10979 return convertOmpTaskwaitOp(op, builder, moduleTranslation);
10980 })
10981 .Case([&](omp::InteropInitOp op) {
10982 return convertOmpInteropInitOp(op, builder, moduleTranslation);
10983 })
10984 .Case([&](omp::InteropDestroyOp op) {
10985 return convertOmpInteropDestroyOp(op, builder, moduleTranslation);
10986 })
10987 .Case([&](omp::InteropUseOp op) {
10988 return convertOmpInteropUseOp(op, builder, moduleTranslation);
10989 })
10990 .Case<omp::YieldOp, omp::TerminatorOp, omp::DeclareMapperOp,
10991 omp::DeclareMapperInfoOp, omp::DeclareReductionOp,
10992 omp::CriticalDeclareOp>([](auto op) {
10993 // `yield` and `terminator` can be just omitted. The block structure
10994 // was created in the region that handles their parent operation.
10995 // `declare_reduction` will be used by reductions and is not
10996 // converted directly, skip it.
10997 // `declare_mapper` and `declare_mapper.info` are handled whenever
10998 // they are referred to through a `map` clause.
10999 // `critical.declare` is only used to declare names of critical
11000 // sections which will be used by `critical` ops and hence can be
11001 // ignored for lowering. The OpenMP IRBuilder will create unique
11002 // name for critical section names.
11003 return success();
11004 })
11005 .Case([&](omp::ThreadprivateOp) {
11006 return convertOmpThreadprivate(*op, builder, moduleTranslation);
11007 })
11008 .Case<omp::TargetDataOp, omp::TargetEnterDataOp,
11009 omp::TargetExitDataOp, omp::TargetUpdateOp>([&](auto op) {
11010 return convertOmpTargetData(op, builder, moduleTranslation);
11011 })
11012 .Case([&](omp::TargetOp) {
11013 return convertOmpTarget(*op, builder, moduleTranslation);
11014 })
11015 .Case([&](omp::DistributeOp) {
11016 return convertOmpDistribute(*op, builder, moduleTranslation);
11017 })
11018 .Case([&](omp::LoopNestOp) {
11019 return convertOmpLoopNest(*op, builder, moduleTranslation);
11020 })
11021 .Case<omp::MapInfoOp, omp::MapBoundsOp, omp::PrivateClauseOp,
11022 omp::AffinityEntryOp, omp::IteratorOp>([&](auto op) {
11023 // No-op, should be handled by relevant owning operations e.g.
11024 // TargetOp, TargetEnterDataOp, TargetExitDataOp, TargetDataOp
11025 // etc. and then discarded
11026 return success();
11027 })
11028 .Case([&](omp::NewCliOp op) {
11029 // Meta-operation: Doesn't do anything by itself, but used to
11030 // identify a loop.
11031 return success();
11032 })
11033 .Case([&](omp::CanonicalLoopOp op) {
11034 return convertOmpCanonicalLoopOp(op, builder, moduleTranslation);
11035 })
11036 .Case([&](omp::UnrollHeuristicOp op) {
11037 // FIXME: Handling omp.unroll_heuristic as an executable requires
11038 // that the generator (e.g. omp.canonical_loop) has been seen first.
11039 // For construct that require all codegen to occur inside a callback
11040 // (e.g. OpenMPIRBilder::createParallel), all codegen of that
11041 // contained region including their transformations must occur at
11042 // the omp.canonical_loop.
11043 return applyUnrollHeuristic(op, builder, moduleTranslation);
11044 })
11045 .Case([&](omp::UnrollFullOp op) {
11046 return applyUnrollFull(op, builder, moduleTranslation);
11047 })
11048 .Case([&](omp::UnrollPartialOp op) {
11049 return applyUnrollPartial(op, builder, moduleTranslation);
11050 })
11051 .Case([&](omp::TileOp op) {
11052 return applyTile(op, builder, moduleTranslation);
11053 })
11054 .Case([&](omp::FuseOp op) {
11055 return applyFuse(op, builder, moduleTranslation);
11056 })
11057 .Case([&](omp::TargetAllocMemOp) {
11058 return convertTargetAllocMemOp(*op, builder, moduleTranslation);
11059 })
11060 .Case([&](omp::TargetFreeMemOp) {
11061 return convertTargetFreeMemOp(*op, builder, moduleTranslation);
11062 })
11063 .Case([&](omp::AllocateDirOp) {
11064 return convertAllocateDirOp(*op, builder, moduleTranslation, *this);
11065 })
11066 .Case([&](omp::AllocateFreeOp) {
11067 return convertAllocateFreeOp(*op, builder, moduleTranslation,
11068 *this);
11069 })
11070 .Case([&](omp::AllocSharedMemOp op) {
11071 return convertAllocSharedMemOp(op, builder, moduleTranslation);
11072 })
11073 .Case([&](omp::FreeSharedMemOp op) {
11074 return convertFreeSharedMemOp(op, builder, moduleTranslation);
11075 })
11076 .Case([&](omp::GroupprivateOp) {
11077 return convertOmpGroupprivate(*op, builder, moduleTranslation);
11078 })
11079 .Case([&](omp::DeclareSimdOp) {
11080 // TODO: Implement.
11081 // DECLARE_SIMD can be ignored. There is warning emitted in
11082 // semantic checks about the current situation, but not having an
11083 // implementation should not prevent compilation.
11084 return success();
11085 })
11086 .Default([&](Operation *inst) {
11087 return inst->emitError()
11088 << "not yet implemented: " << inst->getName();
11089 });
11090
11091 if (isOutermostLoopWrapper)
11092 moduleTranslation.stackPop();
11093
11094 return result;
11095}
11096
11098 registry.insert<omp::OpenMPDialect>();
11099 registry.addExtension(+[](MLIRContext *ctx, omp::OpenMPDialect *dialect) {
11100 dialect->addInterfaces<OpenMPDialectLLVMIRTranslationInterface>();
11101 });
11102}
11103
11105 DialectRegistry registry;
11107 context.appendDialectRegistry(registry);
11108}
for(Operation *op :ops)
return success()
if(failed(verifyVectorMemoryOp(getOperation(), memrefType, getVectorType()))) return failure()
static void copy(Location loc, Value dst, Value src, Value size, OpBuilder &builder)
Copies the given number of bytes from src to dst pointers.
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
ArrayAttr()
auto load
static mlir::LogicalResult buildDependData(OperandRange dependVars, std::optional< ArrayAttr > dependKinds, OperandRange dependIterated, std::optional< ArrayAttr > dependIteratedKinds, llvm::IRBuilderBase &builder, mlir::LLVM::ModuleTranslation &moduleTranslation, llvm::OpenMPIRBuilder::DependenciesInfo &taskDeps)
static LogicalResult convertOmpAtomicUpdate(omp::AtomicUpdateOp &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP atomic update operation using OpenMPIRBuilder.
static llvm::omp::OrderKind convertOrderKind(std::optional< omp::ClauseOrderKind > o)
Convert Order attribute to llvm::omp::OrderKind.
static void mapParentWithMembers(LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder &ompBuilder, DataLayout &dl, MapInfosTy &combinedInfo, MapInfoData &mapData, uint64_t mapDataIndex, llvm::omp::OpenMPOffloadMappingFlags memberOfFlag, TargetDirectiveEnumTy targetDirective)
static void processIndividualMap(llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder &ompBuilder, MapInfoData &mapData, size_t mapDataIdx, MapInfosTy &combinedInfo, TargetDirectiveEnumTy targetDirective, llvm::omp::OpenMPOffloadMappingFlags memberOfFlag=llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE, bool isTargetParam=true, int mapDataParentIdx=-1)
This function handles the insertion of a single item of map data from MapInfoData into the OMPIRBuild...
static llvm::OpenMPIRBuilder::InsertPointTy findAllocInsertPoints(llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, llvm::SmallVectorImpl< llvm::BasicBlock * > *deallocBlocks=nullptr)
Find the insertion point for allocas given the current insertion point for normal operations in the b...
static void sortMapIndices(llvm::SmallVectorImpl< size_t > &indices, omp::MapInfoOp mapInfo, bool first=true)
static LogicalResult convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
owningDataPtrPtrReductionGens[i]
static LogicalResult convertOmpTaskloopContextOp(omp::TaskloopContextOp contextOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static Operation * getGlobalOpFromValue(Value value)
static llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryKind convertToCaptureClauseKind(mlir::omp::DeclareTargetCaptureClause captureClause)
static omp::MapInfoOp getFirstOrLastMappedMemberPtr(omp::MapInfoOp mapInfo, bool first)
static LogicalResult convertOmpDispatch(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Convert 'dispatch' operation into LLVM IR.
static OpTy castOrGetParentOfType(Operation *op, bool immediateParent=false)
If op is of the given type parameter, return it casted to that type. Otherwise, if its immediate pare...
static LogicalResult convertOmpOrderedRegion(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP 'ordered_region' operation into LLVM IR using OpenMPIRBuilder.
static LogicalResult convertFreeSharedMemOp(omp::FreeSharedMemOp freeMemOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static LogicalResult convertTargetFreeMemOp(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static LogicalResult convertOmpAtomicWrite(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an omp.atomic.write operation to LLVM IR.
static OwningAtomicReductionGen makeAtomicReductionGen(omp::DeclareReductionOp decl, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Create an OpenMPIRBuilder-compatible atomic reduction generator for the given reduction declaration.
static OwningDataPtrPtrReductionGen makeRefDataPtrGen(omp::DeclareReductionOp decl, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, bool isByRef)
Create an OpenMPIRBuilder-compatible data_ptr_ptr reduction generator for the given reduction declara...
static llvm::Value * getRefPtrIfDeclareTarget(Value value, LLVM::ModuleTranslation &moduleTranslation)
static llvm::Function * emitTaskReductionCombFn(omp::DeclareReductionOp decl, StringRef baseName, LLVM::ModuleTranslation &moduleTranslation)
Build an outlined combiner helper for a task_reduction declare_reduction op. Signature: void(ptr lhs,...
static LogicalResult convertOmpWsloop(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP workshare loop into LLVM IR using OpenMPIRBuilder.
static LogicalResult applyUnrollHeuristic(omp::UnrollHeuristicOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Apply a #pragma omp unroll / "!$omp unroll" transformation using the OpenMPIRBuilder.
static LogicalResult convertOmpMaster(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP 'master' operation into LLVM IR using OpenMPIRBuilder.
static void getAsIntegers(ArrayAttr values, llvm::SmallVector< int64_t > &ints)
static void emitComplexAtomicCmpXchg(llvm::IRBuilderBase &builder, llvm::Value *llvmX, llvm::Type *complexTy, llvm::Value *eVal, llvm::Value *dVal, llvm::AtomicOrdering atomicOrdering, llvm::AtomicOrdering failOrdering, bool isWeak, llvm::Value *&oldComplex, llvm::Value *&cmpOk)
Emit an IEEE-754-correct cmpxchg for a complex (struct-typed) atomic compare with fcmp oeq....
static llvm::Value * findAssociatedValue(Value privateVar, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, llvm::DenseMap< Value, Value > *mappedPrivateVars=nullptr)
Return the llvm::Value * corresponding to the privateVar that is being privatized....
static ArrayRef< bool > getIsByRef(std::optional< ArrayRef< bool > > attr)
static llvm::Expected< llvm::Value * > lookupOrTranslatePureValue(Value value, LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder)
Look up the given value in the mapping, and if it's not there, translate its defining operation at th...
static LogicalResult allocReductionVars(T op, ArrayRef< BlockArgument > reductionArgs, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, const llvm::OpenMPIRBuilder::InsertPointTy &allocaIP, SmallVectorImpl< omp::DeclareReductionOp > &reductionDecls, SmallVectorImpl< llvm::Value * > &privateReductionVariables, DenseMap< Value, llvm::Value * > &reductionVariableMap, SmallVectorImpl< DeferredStore > &deferredStores, llvm::ArrayRef< bool > isByRefs)
Allocate space for privatized reduction variables.
static void emitTaskReductionModifierFini(bool isWorksharing, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Emits __kmpc_task_reduction_modifier_fini(loc, gtid, is_ws) at the current builder insertion point,...
static LogicalResult convertOmpInteropUseOp(omp::InteropUseOp useOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static LogicalResult convertOmpTaskwaitOp(omp::TaskwaitOp twOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static LogicalResult collectAndValidateTaskloopRedDecls(Operation *contextOp, std::optional< ArrayAttr > syms, StringRef opName, StringRef clauseName, SmallVectorImpl< omp::DeclareReductionOp > &out)
Look up and validate the declare_reduction ops referenced by a reduction-like clause on the omp....
static LogicalResult convertOmpLoopNest(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP loop nest into LLVM IR using OpenMPIRBuilder.
static mlir::LogicalResult fillIteratorLoop(mlir::omp::IteratorOp itersOp, llvm::IRBuilderBase &builder, mlir::LLVM::ModuleTranslation &moduleTranslation, IteratorInfo &iterInfo, llvm::StringRef loopName, IteratorStoreEntryTy genStoreEntry)
static void createAlteredByCaptureMap(MapInfoData &mapData, LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder)
static LogicalResult convertOmpTaskOp(omp::TaskOp taskOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP task construct into LLVM IR using OpenMPIRBuilder.
static void genMapInfos(llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, DataLayout &dl, MapInfosTy &combinedInfo, MapInfoData &mapData, TargetDirectiveEnumTy targetDirective)
static llvm::AtomicOrdering convertAtomicOrdering(std::optional< omp::ClauseMemoryOrderKind > ao)
Convert an Atomic Ordering attribute to llvm::AtomicOrdering.
static LogicalResult convertOmpInteropInitOp(omp::InteropInitOp initOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static void setInsertPointForPossiblyEmptyBlock(llvm::IRBuilderBase &builder, llvm::BasicBlock *block=nullptr)
static mlir::LogicalResult convertIteratorRegion(llvm::Value *linearIV, IteratorInfo &iterInfo, mlir::Region &iteratorRegion, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
llvm::function_ref< void(llvm::Value *linearIV, mlir::omp::YieldOp yield)> IteratorStoreEntryTy
static llvm::Function * emitTaskReductionInitFn(omp::DeclareReductionOp decl, StringRef baseName, LLVM::ModuleTranslation &moduleTranslation)
Build an outlined init helper for a task_reduction declare_reduction op. Signature: void(ptr priv,...
static LogicalResult convertOmpSections(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static llvm::Expected< llvm::BasicBlock * > allocatePrivateVars(T op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, PrivateVarsInfo &privateVarsInfo, llvm::OpenMPIRBuilder::InsertPointTy &allocaIP, llvm::DenseMap< Value, Value > *mappedPrivateVars=nullptr, std::optional< llvm::OpenMPIRBuilder::InsertPointTy > allocatorIP=std::nullopt)
Allocate and initialize delayed private variables. Returns the basic block which comes after all of t...
static LogicalResult applyUnrollPartial(omp::UnrollPartialOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Apply a #pragma omp unroll partial / !$omp unroll partial transformation using the OpenMPIRBuilder.
static LogicalResult convertOmpCritical(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP 'critical' operation into LLVM IR using OpenMPIRBuilder.
static LogicalResult convertTargetAllocMemOp(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static omp::DistributeOp getDistributeCapturingTeamsReduction(omp::TeamsOp teamsOp)
static LogicalResult convertOmpCanonicalLoopOp(omp::CanonicalLoopOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Convert an omp.canonical_loop to LLVM-IR.
static LogicalResult convertOmpTargetData(Operation *op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static std::optional< int64_t > extractConstInteger(Value value)
If the given value is defined by an llvm.mlir.constant operation and it is of an integer type,...
static llvm::Expected< llvm::Value * > initPrivateVar(llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, omp::PrivateClauseOp &privDecl, llvm::Value *nonPrivateVar, BlockArgument &blockArg, llvm::Value *llvmPrivateVar, llvm::BasicBlock *privInitBlock, llvm::DenseMap< Value, Value > *mappedPrivateVars=nullptr)
Initialize a single (first)private variable. You probably want to use allocateAndInitPrivateVars inst...
static mlir::LogicalResult buildAffinityData(mlir::omp::TaskOp &taskOp, llvm::IRBuilderBase &builder, mlir::LLVM::ModuleTranslation &moduleTranslation, llvm::OpenMPIRBuilder::AffinityData &ad)
static LogicalResult allocAndInitializeReductionVars(OP op, ArrayRef< BlockArgument > reductionArgs, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, llvm::OpenMPIRBuilder::InsertPointTy &allocaIP, SmallVectorImpl< omp::DeclareReductionOp > &reductionDecls, SmallVectorImpl< llvm::Value * > &privateReductionVariables, DenseMap< Value, llvm::Value * > &reductionVariableMap, llvm::ArrayRef< bool > isByRef)
static LogicalResult convertOmpSimd(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP simd loop into LLVM IR using OpenMPIRBuilder.
static LogicalResult convertOmpInteropDestroyOp(omp::InteropDestroyOp destroyOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static LogicalResult convertOmpDistribute(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static llvm::Value * getAllocationSize(llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, T op)
static llvm::Function * getOmpTargetAlloc(llvm::IRBuilderBase &builder, llvm::Module *llvmModule)
static llvm::omp::OMPDynGroupprivateFallbackType getDynGroupprivateFallbackType(omp::FallbackModifierAttr fallbackAttr)
static llvm::Expected< llvm::Function * > emitUserDefinedMapper(Operation *declMapperOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, llvm::StringRef mapperFuncName, TargetDirectiveEnumTy targetDirective)
static LogicalResult convertOmpOrdered(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP 'ordered' operation into LLVM IR using OpenMPIRBuilder.
static LogicalResult cleanupPrivateVars(T op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, Location loc, PrivateVarsInfo &privateVarsInfo)
static void processMapWithMembersOf(LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder &ompBuilder, DataLayout &dl, MapInfosTy &combinedInfo, MapInfoData &mapData, uint64_t mapDataIndex, TargetDirectiveEnumTy targetDirective)
static LogicalResult convertOmpMasked(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP 'masked' operation into LLVM IR using OpenMPIRBuilder.
static llvm::AtomicRMWInst::BinOp convertBinOpToAtomic(Operation &op)
Converts an LLVM dialect binary operation to the corresponding enum value for atomicrmw supported bin...
static LogicalResult convertOmpCancel(omp::CancelOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static int getMapDataMemberIdx(MapInfoData &mapData, omp::MapInfoOp memberOp)
allocatedType moduleTranslation static convertType(allocatedType) LogicalResult inlineOmpRegionCleanup(llvm::SmallVectorImpl< Region * > &cleanupRegions, llvm::ArrayRef< llvm::Value * > privateVariables, LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder, StringRef regionName, bool shouldLoadCleanupRegionArg=true)
handling of DeclareReductionOp's cleanup region
static LogicalResult applyFuse(omp::FuseOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Apply a #pragma omp fuse / !$omp fuse transformation using the OpenMPIRBuilder.
static llvm::Value * materializeRegionArgValue(llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, BlockArgument regionArg, llvm::Value *value)
static LogicalResult convertOmpScope(omp::ScopeOp &scopeOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP scope construct into LLVM IR.
static bool isPrivatizeableAttachMap(omp::ClauseMapFlags mapType)
static llvm::Value * getSizeInBytes(DataLayout &dl, const mlir::Type &type, Operation *clauseOp, llvm::Value *basePointer, llvm::Type *baseType, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static llvm::Error initPrivateVars(llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, PrivateVarsInfo &privateVarsInfo, llvm::DenseMap< Value, Value > *mappedPrivateVars=nullptr)
static LogicalResult convertAllocSharedMemOp(omp::AllocSharedMemOp allocMemOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static llvm::CanonicalLoopInfo * findCurrentLoopInfo(LLVM::ModuleTranslation &moduleTranslation)
Find the loop information structure for the loop nest being translated.
static OwningReductionGen makeReductionGen(omp::DeclareReductionOp decl, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Create an OpenMPIRBuilder-compatible reduction generator for the given reduction declaration.
static void storeAffinityEntry(llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder &ompBuilder, llvm::Value *affinityList, llvm::Value *index, llvm::Value *addr, llvm::Value *len)
static LogicalResult convertOmpParallel(omp::ParallelOp opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts the OpenMP parallel operation to LLVM IR.
static void pushCancelFinalizationCB(SmallVectorImpl< llvm::UncondBrInst * > &cancelTerminators, llvm::IRBuilderBase &llvmBuilder, llvm::OpenMPIRBuilder &ompBuilder, mlir::Operation *op, llvm::omp::Directive cancelDirective)
Shared implementation of a callback which adds a termiator for the new block created for the branch t...
static LogicalResult inlineConvertOmpRegions(Region &region, StringRef blockName, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, SmallVectorImpl< llvm::Value * > *continuationBlockArgs=nullptr)
Translates the blocks contained in the given region and appends them to at the current insertion poin...
static void getTargetEntryUniqueInfo(llvm::TargetRegionEntryInfo &targetInfo, omp::TargetOp targetOp, llvm::OpenMPIRBuilder &ompBuilder, llvm::vfs::FileSystem &vfs, llvm::StringRef parentName="")
static LogicalResult convertOmpThreadprivate(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP Threadprivate operation into LLVM IR using OpenMPIRBuilder.
static omp::PrivateClauseOp findPrivatizer(Operation *from, SymbolRefAttr symbolName)
Looks up from the operation from and returns the PrivateClauseOp with name symbolName.
static LogicalResult applyUnrollFull(omp::UnrollFullOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Apply a #pragma omp unroll full / !$omp unroll full transformation using the OpenMPIRBuilder.
static LogicalResult convertOmpGroupprivate(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP groupprivate operation into LLVM IR.
static llvm::Expected< llvm::Function * > getOrCreateUserDefinedMapperFunc(Operation *op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, TargetDirectiveEnumTy targetDirective)
static llvm::Value * getSourceLocIdentFromOp(llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder &ompBuilder, Operation *op)
static uint64_t getTypeByteSize(mlir::Type type, const DataLayout &dl)
static llvm::SmallString< 64 > getDeclareTargetRefPtrSuffix(LLVM::GlobalOp globalOp, llvm::OpenMPIRBuilder &ompBuilder, llvm::vfs::FileSystem &vfs)
static void extractHostEvalClauses(omp::TargetOp targetOp, Value &numThreads, Value &numTeamsLower, Value &numTeamsUpper, Value &threadLimit, llvm::SmallVectorImpl< Value > *lowerBounds=nullptr, llvm::SmallVectorImpl< Value > *upperBounds=nullptr, llvm::SmallVectorImpl< Value > *steps=nullptr)
Follow uses of host_eval-defined block arguments of the given omp.target operation and populate outpu...
static llvm::Expected< llvm::BasicBlock * > convertOmpOpRegions(Region &region, StringRef blockName, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, SmallVectorImpl< llvm::PHINode * > *continuationBlockPHIs=nullptr)
Converts the given region that appears within an OpenMP dialect operation to LLVM IR,...
static LogicalResult extractAtomicComparePattern(Block &block, llvm::function_ref< llvm::Value *(mlir::Value)> materializeValue, omp::AtomicCompareOp atomicCompareOp, AtomicComparePatternInfo &info)
Extract comparison predicate, expected value (e), desired value (d), and related flags from an atomic...
static LogicalResult convertOmpAtomicCompare(omp::AtomicCompareOp atomicCompareOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an omp.atomic.compare operation to LLVM IR.
static LogicalResult convertAllocateDirOp(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, const OpenMPDialectLLVMIRTranslationInterface &ompIface)
static bool constructIsCancellable(Operation *op)
Returns true if the construct contains omp.cancel or omp.cancellation_point.
static llvm::omp::OpenMPOffloadMappingFlags convertClauseMapFlags(omp::ClauseMapFlags mlirFlags)
static LogicalResult convertAllocatorVars(Operation &op, ValueRange allocatorVars, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, PrivateVarsInfo &privateVarsInfo)
static void buildDependDataLocator(std::optional< ArrayAttr > dependKinds, OperandRange dependVars, LLVM::ModuleTranslation &moduleTranslation, SmallVectorImpl< llvm::OpenMPIRBuilder::DependData > &dds)
static std::optional< llvm::omp::OMPAtomicCompareOp > convertFCmpPredicateToAtomicCompareOp(LLVM::FCmpPredicate predicate)
Helper to extract the OMPAtomicCompareOp from a floating-point comparison predicate....
static llvm::Value * emitTaskReductionInitCall(ArrayRef< omp::DeclareReductionOp > redDecls, ArrayRef< llvm::Value * > origPtrs, StringRef helperNamePrefix, llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder::InsertPointTy allocaIP, LLVM::ModuleTranslation &moduleTranslation, bool isModifier=false, bool isWorksharing=false)
Emit the per-taskgroup task_reduction descriptor array and the __kmpc_taskred_init runtime call....
static llvm::Expected< llvm::Value * > getPrivateDataAddress(llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, omp::PrivateClauseOp privatizer, llvm::Value *var)
Returns the address of the data in var, via the data_address region.
static void mapInitializationArgs(T loop, LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder, SmallVectorImpl< omp::DeclareReductionOp > &reductionDecls, DenseMap< Value, llvm::Value * > &reductionVariableMap, unsigned i)
Map input arguments to reduction initialization region.
static llvm::omp::ProcBindKind getProcBindKind(omp::ClauseProcBindKind kind)
Convert ProcBindKind from MLIR-generated enum to LLVM enum.
static void fillAffinityLocators(Operation::operand_range affinityVars, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, llvm::Value *affinityList)
static LogicalResult convertOmpTaskloopWrapperOp(omp::TaskloopWrapperOp loopWrapperOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
The correct entry point is convertOmpTaskloopContextOp. This gets called whilst lowering the body of ...
static void getOverlappedMembers(llvm::SmallVectorImpl< size_t > &overlapMapDataIdxs, omp::MapInfoOp parentOp)
static LogicalResult convertOmpSingle(omp::SingleOp &singleOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP single construct into LLVM IR using OpenMPIRBuilder.
static bool isDeclareTargetTo(Value value)
static uint64_t getArrayElementSizeInBits(LLVM::LLVMArrayType arrTy, DataLayout &dl)
static void popCancelFinalizationCB(const ArrayRef< llvm::UncondBrInst * > cancelTerminators, llvm::OpenMPIRBuilder &ompBuilder, llvm::BasicBlock *afterBB)
If we cancelled the construct, we should branch to the finalization block of that construct....
static void collectReductionDecls(T op, SmallVectorImpl< omp::DeclareReductionOp > &reductions)
Populates reductions with reduction declarations used in the given op.
static LogicalResult handleError(llvm::Error error, Operation &op)
static LogicalResult convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseKind convertToDeviceClauseKind(mlir::omp::DeclareTargetDeviceType deviceClause)
static std::optional< llvm::omp::OMPAtomicCompareOp > convertICmpPredicateToAtomicCompareOp(LLVM::ICmpPredicate predicate)
Helper to extract the OMPAtomicCompareOp from an integer comparison predicate. Returns std::nullopt f...
static llvm::Error computeTaskloopBounds(omp::LoopNestOp loopOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, llvm::Value *&lbVal, llvm::Value *&ubVal, llvm::Value *&stepVal)
static LogicalResult checkImplementationStatus(Operation &op)
Check whether translation to LLVM IR for the given operation is currently supported.
static llvm::IRBuilderBase::InsertPoint createDeviceArgumentAccessor(omp::TargetOp targetOp, MapInfoData &mapData, llvm::Argument &arg, llvm::Value *input, llvm::Value *&retVal, llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder &ompBuilder, LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase::InsertPoint allocaIP, llvm::IRBuilderBase::InsertPoint codeGenIP, llvm::ArrayRef< llvm::IRBuilderBase::InsertPoint > deallocIPs)
static LogicalResult createReductionsAndCleanup(OP op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, llvm::OpenMPIRBuilder::InsertPointTy &allocaIP, SmallVectorImpl< omp::DeclareReductionOp > &reductionDecls, ArrayRef< llvm::Value * > privateReductionVariables, ArrayRef< bool > isByRef, bool isNowait=false, bool isTeamsReduction=false)
static LogicalResult convertOmpCancellationPoint(omp::CancellationPointOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static uint64_t getReductionDataSize(OpTy &op)
static bool opMightBeSafeForBarriers(mlir::Operation *op)
A compiler-generated barrier is unsafe when only part of the current team can reach it....
static LogicalResult convertOmpAtomicRead(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Convert omp.atomic.read operation to LLVM IR.
static llvm::omp::Directive convertCancellationConstructType(omp::ClauseCancellationConstructType directive)
static void initTargetDefaultAttrs(omp::TargetOp targetOp, Operation *capturedOp, llvm::OpenMPIRBuilder::TargetKernelDefaultAttrs &attrs, bool isTargetDevice, bool isGPU)
Populate default MinTeams, MaxTeams and MaxThreads to their default values as stated by the correspon...
static llvm::AtomicOrdering getAtomicCompareFailureOrdering(omp::AtomicCompareOp atomicCompareOp, llvm::AtomicOrdering atomicOrdering)
Compute the cmpxchg failure ordering for an atomic compare op: use the fail clause ordering when pres...
static llvm::omp::RTLDependenceKindTy convertDependKind(mlir::omp::ClauseTaskDepend kind)
static void initTargetRuntimeAttrs(llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, omp::TargetOp targetOp, Operation *capturedOp, llvm::OpenMPIRBuilder::TargetKernelRuntimeAttrs &attrs)
Gather LLVM runtime values for all clauses evaluated in the host that are passed to the kernel invoca...
static LogicalResult emitPrivatizationBarrier(mlir::Operation *op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static ComplexComparePattern detectComplexCompareEq(Block &block)
Detect a decomposed complex equality comparison in an atomic compare region: re_x = llvm....
static LogicalResult convertOmpTeams(omp::TeamsOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static Value getBaseValueForTypeLookup(Value value)
static bool isHostDeviceOp(Operation *op)
static LogicalResult convertDeclareTargetAttr(Operation *op, mlir::omp::DeclareTargetAttr attribute, llvm::OpenMPIRBuilder *ompBuilder, LLVM::ModuleTranslation &moduleTranslation)
static LogicalResult completePrivateVars(mlir::Operation *op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, SmallVectorImpl< llvm::Value * > &moldVars, ArrayRef< llvm::Value * > llvmPrivateVars, SmallVectorImpl< omp::PrivateClauseOp > &privateDecls, bool insertBarrier, llvm::DenseMap< Value, Value > *mappedPrivateVars=nullptr)
static bool isDeclareTargetLink(Value value)
static LogicalResult convertFlagsAttr(Operation *op, mlir::omp::FlagsAttr attribute, LLVM::ModuleTranslation &moduleTranslation)
Lowers the FlagsAttr which is applied to the module when offloading. This attribute contains OpenMP R...
static bool checkIfPointerMap(omp::MapInfoOp mapOp)
static llvm::Type * getAllocatedLlvmTypeForVariable(Value var, Value baseVar, LLVM::ModuleTranslation &moduleTranslation)
static LogicalResult applyTile(omp::TileOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Apply a #pragma omp tile / !$omp tile transformation using the OpenMPIRBuilder.
static LogicalResult convertAllocateFreeOp(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, const OpenMPDialectLLVMIRTranslationInterface &ompIface)
static StringRef getMapClauseName(omp::MapInfoOp mapOp)
static std::vector< llvm::Value * > calculateBoundsOffset(LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder, bool isArrayTy, OperandRange bounds, bool &isByteOffset)
This function calculates the array/pointer offset for map data provided with bounds operations,...
static llvm::Function * getOmpTargetFree(llvm::IRBuilderBase &builder, llvm::Module *llvmModule)
static LogicalResult convertOmpTaskgroupOp(omp::TaskgroupOp tgOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP taskgroup construct into LLVM IR using OpenMPIRBuilder.
static std::optional< llvm::Value * > getDynamicAllocatedSize(Value var, Value baseVar, LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder, const llvm::DataLayout &dataLayout)
static void collectMapDataFromMapOperands(MapInfoData &mapData, SmallVectorImpl< Value > &mapVars, LLVM::ModuleTranslation &moduleTranslation, DataLayout &dl, llvm::IRBuilderBase &builder, ArrayRef< Value > useDevPtrOperands={}, ArrayRef< Value > useDevAddrOperands={}, ArrayRef< Value > hasDevAddrOperands={})
static void extractAtomicControlFlags(omp::AtomicUpdateOp atomicUpdateOp, bool &isIgnoreDenormalMode, bool &isFineGrainedMemory, bool &isRemoteMemory)
static Operation * genLoop(CodegenEnv &env, OpBuilder &builder, LoopId curr, unsigned numCases, bool needsUniv, ArrayRef< TensorLevel > tidLvls)
Generates a for-loop or a while-loop, depending on whether it implements singleton iteration or co-it...
#define MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(CLASS_NAME)
Definition TypeID.h:331
Attributes are known-constant values of operations.
Definition Attributes.h:25
This class represents an argument of a Block.
Definition Value.h:306
Block represents an ordered list of Operations.
Definition Block.h:34
BlockArgument getArgument(unsigned i)
Definition Block.h:154
unsigned getNumArguments()
Definition Block.h:153
OpListType & getOperations()
Definition Block.h:162
Operation & front()
Definition Block.h:178
Operation & back()
Definition Block.h:177
Operation * getTerminator()
Get the terminator operation of this block.
Definition Block.cpp:249
iterator_range< iterator > without_terminator()
Return an iterator range over the operation within this block excluding the terminator operation at t...
Definition Block.h:222
iterator begin()
Definition Block.h:168
The main mechanism for performing data layout queries.
llvm::TypeSize getTypeSize(Type t) const
Returns the size of the given type in the current scope.
llvm::TypeSize getTypeSizeInBits(Type t) const
Returns the size in bits of the given type in the current scope.
The DialectRegistry maps a dialect namespace to a constructor for the matching dialect.
bool addExtension(TypeID extensionID, std::unique_ptr< DialectExtensionBase > extension)
Add the given extension to the registry.
An instance of this location represents a tuple of file, line number, and column number.
Definition Location.h:174
Implementation class for module translation.
llvm::BasicBlock * lookupBlock(Block *block) const
Finds an LLVM IR basic block that corresponds to the given MLIR block.
WalkResult stackWalk(llvm::function_ref< WalkResult(T &)> callback)
Calls callback for every ModuleTranslation stack frame of type T starting from the top of the stack.
void stackPush(Args &&...args)
Creates a stack frame of type T on ModuleTranslation stack.
LogicalResult convertBlock(Block &bb, bool ignoreArguments, llvm::IRBuilderBase &builder)
Translates the contents of the given block to LLVM IR using this translator.
SmallVector< llvm::Value * > lookupValues(ValueRange values)
Looks up remapped a list of remapped values.
void mapFunction(StringRef name, llvm::Function *func)
Stores the mapping between a function name and its LLVM IR representation.
llvm::Value * lookupValue(Value value) const
Finds an LLVM IR value corresponding to the given MLIR value.
void invalidateOmpLoop(omp::NewCliOp mlir)
Mark an OpenMP loop as having been consumed.
SymbolTableCollection & symbolTable()
llvm::Type * convertType(Type type)
Converts the type from MLIR LLVM dialect to LLVM.
llvm::OpenMPIRBuilder * getOpenMPBuilder()
Returns the OpenMP IR builder associated with the LLVM IR module being constructed.
llvm::vfs::FileSystem & getFileSystem()
Returns the virtual filesystem to use for file operations.
void mapOmpLoop(omp::NewCliOp mlir, llvm::CanonicalLoopInfo *llvm)
Map an MLIR OpenMP dialect CanonicalLoopInfo to its lowered LLVM-IR OpenMPIRBuilder CanonicalLoopInfo...
llvm::GlobalValue * lookupGlobal(Operation *op)
Finds an LLVM IR global value that corresponds to the given MLIR operation defining a global value.
SaveStateStack< T, ModuleTranslation > SaveStack
RAII object calling stackPush/stackPop on construction/destruction.
void remapAllValuesWith(llvm::Value *oldValue, llvm::Value *newValue)
Remap old value with new value in the MLIR-to-LLVM value map so later translations use the replacemen...
LogicalResult convertOperation(Operation &op, llvm::IRBuilderBase &builder)
Converts the given MLIR operation into LLVM IR using this translator.
llvm::Function * lookupFunction(StringRef name) const
Finds an LLVM IR function by its name.
void mapBlock(Block *mlir, llvm::BasicBlock *llvm)
Stores the mapping between an MLIR block and LLVM IR basic block.
llvm::Module * getLLVMModule()
Returns the LLVM module in which the IR is being constructed.
void stackPop()
Pops the last element from the ModuleTranslation stack.
void forgetMapping(Region &region)
Removes the mapping for blocks contained in the region and values defined in these blocks.
void mapValue(Value mlir, llvm::Value *llvm)
Stores the mapping between an MLIR value and its LLVM IR counterpart.
llvm::CanonicalLoopInfo * lookupOMPLoop(omp::NewCliOp mlir) const
Find the LLVM-IR loop that represents an MLIR loop.
llvm::LLVMContext & getLLVMContext() const
Returns the LLVM context in which the IR is being constructed.
Utility class to translate MLIR LLVM dialect types to LLVM IR.
Definition TypeToLLVM.h:39
unsigned getPreferredAlignment(Type type, const llvm::DataLayout &layout)
Returns the preferred alignment for the type given the data layout.
T findInstanceOf()
Return an instance of the given location type if one is nested under the current location.
Definition Location.h:45
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:64
void appendDialectRegistry(const DialectRegistry &registry)
Append the contents of the given dialect registry to the registry associated with this context.
StringAttr getName() const
Return the name of the attribute.
Attribute getValue() const
Return the value of the attribute.
Definition Attributes.h:179
This class implements the operand iterators for the Operation class.
Definition ValueRange.h:44
StringAttr getIdentifier() const
Return the name of this operation as a StringAttr.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Region & getRegion(unsigned index)
Returns the region held by this operation at position 'index'.
Definition Operation.h:738
Value getOperand(unsigned idx)
Definition Operation.h:375
InFlightDiagnostic emitWarning(const Twine &message={})
Emit a warning about this operation, reporting up to any diagnostic handlers that may be listening.
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
unsigned getNumRegions()
Returns the number of regions held by this operation.
Definition Operation.h:726
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
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
OperandRange operand_range
Definition Operation.h:396
InFlightDiagnostic emitError(const Twine &message={})
Emit an error about fatal conditions with this operation, reporting up to any diagnostic handlers tha...
OpTy getParentOfType()
Return the closest surrounding parent operation that is of type 'OpTy'.
Definition Operation.h:255
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
std::enable_if_t< llvm::function_traits< std::decay_t< FnT > >::num_args==1, RetT > walk(FnT &&callback)
Walk the operation by calling the callback for each nested operation (including this one),...
Definition Operation.h:849
user_range getUsers()
Returns a range of all users.
Definition Operation.h:925
result_range getResults()
Definition Operation.h:440
MLIRContext * getContext()
Return the context this operation is associated with.
Definition Operation.h:233
InFlightDiagnostic emitOpError(const Twine &message={})
Emit an error with the op name prefixed, like "'dim' op " which is convenient for verifiers.
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:429
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
Block & front()
Definition Region.h:65
BlockArgListType getArguments()
Definition Region.h:94
bool empty()
Definition Region.h:60
unsigned getNumArguments()
Definition Region.h:136
iterator begin()
Definition Region.h:55
Operation * getParentOp()
Return the parent operation this region is attached to.
Definition Region.h:198
BlockListType & getBlocks()
Definition Region.h:45
bool hasOneBlock()
Return true if this region has exactly one block.
Definition Region.h:68
Concrete CRTP base class for StateStack frames.
Definition StateStack.h:47
@ Private
The symbol is private and may only be referenced by SymbolRefAttrs local to the operations within the...
Definition SymbolTable.h:92
static Operation * lookupNearestSymbolFrom(Operation *from, StringAttr symbol)
Returns the operation registered with the given symbol name within the closest parent operation of,...
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
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
user_range getUsers() const
Definition Value.h:218
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
A utility result that is used to signal how to proceed with an ongoing walk:
Definition WalkResult.h:29
static WalkResult skip()
Definition WalkResult.h:48
static WalkResult advance()
Definition WalkResult.h:47
bool wasInterrupted() const
Returns true if the walk was interrupted.
Definition WalkResult.h:51
static WalkResult interrupt()
Definition WalkResult.h:46
The OpAsmOpInterface, see OpAsmInterface.td for more details.
Definition CallGraph.h:227
void connectPHINodes(Region &region, const ModuleTranslation &state)
For all blocks in the region that were converted to LLVM IR using the given ModuleTranslation,...
llvm::Constant * createMappingInformation(Location loc, llvm::OpenMPIRBuilder &builder, StringRef mapName={})
Create a constant string representing the mapping information extracted from the MLIR location inform...
llvm::Constant * createSourceLocStrFromLocation(Location loc, llvm::OpenMPIRBuilder &builder, StringRef name, uint32_t &strLen, bool ForOffloadMap=false)
Create a constant string location from the MLIR Location information.
int64_t getOpenMPVersionAttribute(ModuleOp module, int64_t fallback=-1)
Returns the value of the omp.version attribute, if present, or the fallback.
Definition Utils.cpp:56
bool opInSharedDeviceContext(Operation &op)
Check whether the given operation is located in a context where an allocation to be used by multiple ...
Definition Utils.cpp:113
bool allocaUsesRequireSharedMem(Value alloc)
Check whether the value representing an allocation, assumed to have been defined in a shared device c...
Definition Utils.cpp:98
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:734
auto getDims(VectorType vType)
Returns a range over the dims (size and scalability) of a VectorType.
Include the generated interface declarations.
detail::DenseArrayAttrImpl< int64_t > DenseI64ArrayAttr
SetVector< Block * > getBlocksSortedByDominance(Region &region)
Gets a list of blocks that is sorted according to dominance.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:311
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
bool isPure(Operation *op)
Returns true if the given operation is pure, i.e., is speculatable that does not touch memory.
void registerOpenMPDialectTranslation(DialectRegistry &registry)
Register the OpenMP dialect and the translation from it to the LLVM IR in the given registry;.
llvm::SetVector< T, Vector, Set, N > SetVector
Definition LLVM.h:128
SmallVector< Loops, 8 > tile(ArrayRef< scf::ForOp > forOps, ArrayRef< Value > sizes, ArrayRef< scf::ForOp > targets)
Performs tiling fo imperfectly nested loops (with interchange) by strip-mining the forOps by sizes an...
Definition Utils.cpp:1380
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:123
Holds the extracted comparison pattern information from an atomic compare region.
llvm::omp::OMPAtomicCompareOp compareOp
Result of matching the decomposed complex equality pattern inside an atomic compare region.
A util to collect info needed to convert delayed privatizers from MLIR to LLVM.
size_t numPrivateVars
Number of private variables before the linear ones.
SmallVector< BlockArgument > blockArgs
SmallVector< mlir::Value > mlirVars
SmallVector< omp::PrivateClauseOp > privatizers
llvm::DenseMap< Value, llvm::Value * > convertedAllocators
void addLinearVars(OP op)
Adds the linear variables of op, which are privatized by their linear privatizers,...
SmallVector< llvm::Value * > llvmVars
SmallVector< AllocatorPrivateInfo > allocatorPrivates
Eliminates variable at the specified position using Fourier-Motzkin variable elimination.