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
54namespace {
55static llvm::omp::ScheduleKind
56convertToScheduleKind(std::optional<omp::ClauseScheduleKind> schedKind) {
57 if (!schedKind.has_value())
58 return llvm::omp::OMP_SCHEDULE_Default;
59 switch (schedKind.value()) {
60 case omp::ClauseScheduleKind::Static:
61 return llvm::omp::OMP_SCHEDULE_Static;
62 case omp::ClauseScheduleKind::Dynamic:
63 return llvm::omp::OMP_SCHEDULE_Dynamic;
64 case omp::ClauseScheduleKind::Guided:
65 return llvm::omp::OMP_SCHEDULE_Guided;
66 case omp::ClauseScheduleKind::Auto:
67 return llvm::omp::OMP_SCHEDULE_Auto;
68 case omp::ClauseScheduleKind::Runtime:
69 return llvm::omp::OMP_SCHEDULE_Runtime;
70 case omp::ClauseScheduleKind::Distribute:
71 return llvm::omp::OMP_SCHEDULE_Distribute;
72 }
73 llvm_unreachable("unhandled schedule clause argument");
74}
75
76/// ModuleTranslation stack frame for OpenMP operations. This keeps track of the
77/// insertion points for allocas.
78class OpenMPAllocStackFrame
79 : public StateStackFrameBase<OpenMPAllocStackFrame> {
80public:
82
83 explicit OpenMPAllocStackFrame(
84 llvm::OpenMPIRBuilder::InsertPointTy allocaIP,
85 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks)
86 : allocInsertPoint(allocaIP), deallocBlocks(deallocBlocks) {}
87 llvm::OpenMPIRBuilder::InsertPointTy allocInsertPoint;
88 llvm::SmallVector<llvm::BasicBlock *> deallocBlocks;
89};
90
91/// Stack frame to hold a \see llvm::CanonicalLoopInfo representing the
92/// collapsed canonical loop information corresponding to an \c omp.loop_nest
93/// operation.
94class OpenMPLoopInfoStackFrame
95 : public StateStackFrameBase<OpenMPLoopInfoStackFrame> {
96public:
97 MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(OpenMPLoopInfoStackFrame)
98 llvm::CanonicalLoopInfo *loopInfo = nullptr;
99};
100
101/// Custom error class to signal translation errors that don't need reporting,
102/// since encountering them will have already triggered relevant error messages.
103///
104/// Its purpose is to serve as the glue between MLIR failures represented as
105/// \see LogicalResult instances and \see llvm::Error instances used to
106/// propagate errors through the \see llvm::OpenMPIRBuilder. Generally, when an
107/// error of the first type is raised, a message is emitted directly (the \see
108/// LogicalResult itself does not hold any information). If we need to forward
109/// this error condition as an \see llvm::Error while avoiding triggering some
110/// redundant error reporting later on, we need a custom \see llvm::ErrorInfo
111/// class to just signal this situation has happened.
112///
113/// For example, this class should be used to trigger errors from within
114/// callbacks passed to the \see OpenMPIRBuilder when they were triggered by the
115/// translation of their own regions. This unclutters the error log from
116/// redundant messages.
117class PreviouslyReportedError
118 : public llvm::ErrorInfo<PreviouslyReportedError> {
119public:
120 void log(raw_ostream &) const override {
121 // Do not log anything.
122 }
123
124 std::error_code convertToErrorCode() const override {
125 llvm_unreachable(
126 "PreviouslyReportedError doesn't support ECError conversion");
127 }
128
129 // Used by ErrorInfo::classID.
130 static char ID;
131};
132
133char PreviouslyReportedError::ID = 0;
134
135/*
136 * Custom class for processing linear clause for omp.wsloop
137 * and omp.simd. Linear clause translation requires setup,
138 * initialization, update, and finalization at varying
139 * basic blocks in the IR. This class helps maintain
140 * internal state to allow consistent translation in
141 * each of these stages.
142 */
143
144class LinearClauseProcessor {
145
146private:
147 SmallVector<llvm::Value *> linearPreconditionVars;
148 SmallVector<llvm::Value *> linearLoopBodyTemps;
149 SmallVector<llvm::Value *> linearOrigVal;
150 SmallVector<llvm::Value *> linearSteps;
151 SmallVector<llvm::Type *> linearVarTypes;
152 llvm::BasicBlock *linearFinalizationBB;
153 llvm::BasicBlock *linearExitBB;
154 llvm::BasicBlock *linearLastIterExitBB;
155 Value linearLoopIV;
156
157public:
158 // Register type for the linear variables
159 void registerType(LLVM::ModuleTranslation &moduleTranslation,
160 mlir::Attribute &ty) {
161 linearVarTypes.push_back(moduleTranslation.convertType(
162 mlir::cast<mlir::TypeAttr>(ty).getValue()));
163 }
164
165 // Allocate space for linear variabes
166 void createLinearVar(llvm::IRBuilderBase &builder,
167 LLVM::ModuleTranslation &moduleTranslation,
168 llvm::Value *linearVar, int idx) {
169 linearPreconditionVars.push_back(
170 builder.CreateAlloca(linearVarTypes[idx], nullptr, ".linear_var"));
171 llvm::Value *linearLoopBodyTemp =
172 builder.CreateAlloca(linearVarTypes[idx], nullptr, ".linear_result");
173 linearOrigVal.push_back(linearVar);
174 linearLoopBodyTemps.push_back(linearLoopBodyTemp);
175 }
176
177 // Initialize linear step
178 inline void initLinearStep(LLVM::ModuleTranslation &moduleTranslation,
179 mlir::Value &linearStep) {
180 linearSteps.push_back(moduleTranslation.lookupValue(linearStep));
181 }
182
183 // Emit IR for initialization of linear variables
184 void initLinearVar(llvm::IRBuilderBase &builder,
185 LLVM::ModuleTranslation &moduleTranslation,
186 llvm::BasicBlock *loopPreHeader) {
187 builder.SetInsertPoint(loopPreHeader->getTerminator());
188 for (size_t index = 0; index < linearOrigVal.size(); index++) {
189 llvm::LoadInst *linearVarLoad =
190 builder.CreateLoad(linearVarTypes[index], linearOrigVal[index]);
191 builder.CreateStore(linearVarLoad, linearPreconditionVars[index]);
192 }
193 }
194
195 // Find linear iteration variable and save it for later updates
196 LogicalResult initLinearIV(omp::SimdOp simdOp) {
197 auto loopOp = cast<omp::LoopNestOp>(simdOp.getWrappedLoop());
198 // NOTE iteration variables can only be linear in non-nested loops.
199 if (loopOp.getIVs().size() != 1)
200 return success();
201 // Currently, frontends using `omp.simd` always generate a store from the
202 // `omp.loop_nest`'s IV to the corresponding iteration variable.
203 // We leverage this to find the linear iteration variable.
204 //
205 // TODO Add an attribute to `omp.loop_nest` that explicitly lists the
206 // variables that correspond to the loop induction variables.
207 BlockArgument arg = loopOp.getIVs().front();
208 for (const Operation *user : arg.getUsers()) {
209 if (auto storeOp = dyn_cast<LLVM::StoreOp>(user)) {
210 for (Value linearVar : simdOp.getLinearVars()) {
211 if (linearVar == storeOp.getAddr()) {
212 if (linearLoopIV && linearLoopIV != linearVar)
213 return simdOp.emitError(
214 "Could not determine the linear variable associated with the "
215 "loop nest induction variable");
216 linearLoopIV = linearVar;
217 }
218 }
219 }
220 }
221 return success();
222 }
223
224 // Emit IR for updating Linear variables
225 void updateLinearVar(llvm::IRBuilderBase &builder, llvm::BasicBlock *loopBody,
226 llvm::Value *loopInductionVar) {
227 builder.SetInsertPoint(loopBody->getTerminator());
228 for (size_t index = 0; index < linearPreconditionVars.size(); index++) {
229 llvm::Type *linearVarType = linearVarTypes[index];
230 llvm::Value *iv = loopInductionVar;
231 llvm::Value *step = linearSteps[index];
232
233 if (!iv->getType()->isIntegerTy())
234 llvm_unreachable("OpenMP loop induction variable must be an integer "
235 "type");
236
237 if (linearVarType->isIntegerTy()) {
238 // Integer path: normalize all arithmetic to linearVarType
239 iv = builder.CreateSExtOrTrunc(iv, linearVarType);
240 step = builder.CreateSExtOrTrunc(step, linearVarType);
241
242 llvm::LoadInst *linearVarStart =
243 builder.CreateLoad(linearVarType, linearPreconditionVars[index]);
244 llvm::Value *mulInst = builder.CreateMul(iv, step);
245 llvm::Value *addInst = builder.CreateAdd(linearVarStart, mulInst);
246 builder.CreateStore(addInst, linearLoopBodyTemps[index]);
247 } else if (linearVarType->isFloatingPointTy()) {
248 // Float path: perform multiply in integer, then convert to float
249 step = builder.CreateSExtOrTrunc(step, iv->getType());
250 llvm::Value *mulInst = builder.CreateMul(iv, step);
251
252 llvm::LoadInst *linearVarStart =
253 builder.CreateLoad(linearVarType, linearPreconditionVars[index]);
254 llvm::Value *mulFp = builder.CreateSIToFP(mulInst, linearVarType);
255 llvm::Value *addInst = builder.CreateFAdd(linearVarStart, mulFp);
256 builder.CreateStore(addInst, linearLoopBodyTemps[index]);
257 } else {
258 llvm_unreachable(
259 "Linear variable must be of integer or floating-point type");
260 }
261 }
262 }
263
264 // Emit IR for updating linear iteration variables on loop exit
265 void updateLinearIV(llvm::IRBuilderBase &builder,
266 LLVM::ModuleTranslation &moduleTranslation) {
267 if (!linearLoopIV)
268 return;
269 llvm::Value *linearIV = moduleTranslation.lookupValue(linearLoopIV);
270
271 // Find linearIV's index
272 size_t index;
273 for (index = 0; index < linearOrigVal.size(); index++)
274 if (linearIV == linearOrigVal[index])
275 break;
276 if (index == linearOrigVal.size())
277 return;
278
279 // Add one more step to the linear iteration variable
280 llvm::Type *varType = linearVarTypes[index];
281 llvm::Value *var = linearLoopBodyTemps[index];
282 llvm::Value *step = linearSteps[index];
283 if (!varType->isIntegerTy())
284 llvm_unreachable("Linear iteration variable must be of integer type");
285
286 step = builder.CreateSExtOrTrunc(step, varType);
287 llvm::Value *val = builder.CreateLoad(varType, var);
288 llvm::Value *addInst = builder.CreateAdd(val, step);
289 builder.CreateStore(addInst, var);
290 }
291
292 // Linear variable finalization is conditional on the last logical iteration.
293 // Create BB splits to manage the same.
294 void splitLinearFiniBB(llvm::IRBuilderBase &builder,
295 llvm::BasicBlock *loopExit) {
296 linearFinalizationBB = loopExit->splitBasicBlock(
297 loopExit->getTerminator(), "omp_loop.linear_finalization");
298 linearExitBB = linearFinalizationBB->splitBasicBlock(
299 linearFinalizationBB->getTerminator(), "omp_loop.linear_exit");
300 linearLastIterExitBB = linearFinalizationBB->splitBasicBlock(
301 linearFinalizationBB->getTerminator(), "omp_loop.linear_lastiter_exit");
302 }
303
304 // Finalize the linear vars
305 llvm::OpenMPIRBuilder::InsertPointOrErrorTy
306 finalizeLinearVar(llvm::IRBuilderBase &builder,
307 LLVM::ModuleTranslation &moduleTranslation,
308 llvm::Value *lastIter) {
309 // Emit condition to check whether last logical iteration is being executed
310 builder.SetInsertPoint(linearFinalizationBB->getTerminator());
311 llvm::Value *loopLastIterLoad = builder.CreateLoad(
312 llvm::Type::getInt32Ty(builder.getContext()), lastIter);
313 llvm::Value *isLast =
314 builder.CreateCmp(llvm::CmpInst::ICMP_NE, loopLastIterLoad,
315 llvm::ConstantInt::get(
316 llvm::Type::getInt32Ty(builder.getContext()), 0));
317 // Store the linear variable values to original variables.
318 builder.SetInsertPoint(linearLastIterExitBB->getTerminator());
319 for (size_t index = 0; index < linearOrigVal.size(); index++) {
320 llvm::LoadInst *linearVarTemp =
321 builder.CreateLoad(linearVarTypes[index], linearLoopBodyTemps[index]);
322 builder.CreateStore(linearVarTemp, linearOrigVal[index]);
323 }
324
325 // Create conditional branch such that the linear variable
326 // values are stored to original variables only at the
327 // last logical iteration
328 builder.SetInsertPoint(linearFinalizationBB->getTerminator());
329 builder.CreateCondBr(isLast, linearLastIterExitBB, linearExitBB);
330 linearFinalizationBB->getTerminator()->eraseFromParent();
331 // Emit barrier
332 builder.SetInsertPoint(linearExitBB->getTerminator());
333 return moduleTranslation.getOpenMPBuilder()->createBarrier(
334 builder, llvm::omp::OMPD_barrier);
335 }
336
337 // Emit stores for linear variables. Useful in case of SIMD
338 // construct.
339 void emitStoresForLinearVar(llvm::IRBuilderBase &builder) {
340 for (size_t index = 0; index < linearOrigVal.size(); index++) {
341 llvm::LoadInst *linearVarTemp =
342 builder.CreateLoad(linearVarTypes[index], linearLoopBodyTemps[index]);
343 builder.CreateStore(linearVarTemp, linearOrigVal[index]);
344 }
345 }
346
347 // Rewrite all uses of the original variable, in the basic blocks in the
348 // [startBB, endBB] interval, with the linear variable in-place.
349 void rewriteInPlace(llvm::IRBuilderBase &builder, llvm::BasicBlock *startBB,
350 llvm::BasicBlock *endBB, size_t varIndex) {
351 llvm::SmallVector<llvm::BasicBlock *, 32> worklist;
352 llvm::SmallPtrSet<llvm::BasicBlock *, 32> collectedBBs;
353
354 assert(startBB && endBB && "Invalid startBB/endBB");
355
356 // Collect basic blocks from startBB to endBB.
357 worklist.push_back(startBB);
358 collectedBBs.insert(startBB);
359
360 while (!worklist.empty()) {
361 llvm::BasicBlock *bb = worklist.pop_back_val();
362
363 if (bb == endBB)
364 continue;
365
366 for (llvm::BasicBlock *succ : llvm::successors(bb)) {
367 if (collectedBBs.insert(succ).second)
368 worklist.push_back(succ);
369 }
370 }
371
372 // Rewrite all uses in the collected BBs.
373 llvm::SmallVector<llvm::User *> users(linearOrigVal[varIndex]->users());
374 for (auto *user : users) {
375 if (auto *userInst = dyn_cast<llvm::Instruction>(user)) {
376 if (collectedBBs.contains(userInst->getParent()))
377 user->replaceUsesOfWith(linearOrigVal[varIndex],
378 linearLoopBodyTemps[varIndex]);
379 }
380 }
381 }
382};
383
384} // namespace
385
386/// Looks up from the operation from and returns the PrivateClauseOp with
387/// name symbolName
388static omp::PrivateClauseOp findPrivatizer(Operation *from,
389 SymbolRefAttr symbolName) {
390 omp::PrivateClauseOp privatizer =
392 symbolName);
393 assert(privatizer && "privatizer not found in the symbol table");
394 return privatizer;
395}
396
397/// Check whether translation to LLVM IR for the given operation is currently
398/// supported. If not, descriptive diagnostics will be emitted to let users know
399/// this is a not-yet-implemented feature.
400///
401/// \returns success if no unimplemented features are needed to translate the
402/// given operation.
403static LogicalResult checkImplementationStatus(Operation &op) {
404 auto todo = [&op](StringRef clauseName) {
405 return op.emitError() << "not yet implemented: Unhandled clause "
406 << clauseName << " in " << op.getName()
407 << " operation";
408 };
409
410 auto checkAllocate = [&todo](auto op, LogicalResult &result) {
411 if (!op.getAllocateVars().empty() || !op.getAllocatorVars().empty())
412 result = todo("allocate");
413 };
414 auto checkBare = [&todo](auto op, LogicalResult &result) {
415 if (op.getKernelType() == omp::TargetExecMode::bare)
416 result = todo("ompx_bare");
417 };
418 auto checkDepend = [&todo](auto op, LogicalResult &result) {
419 if (!op.getDependVars().empty() || op.getDependKinds())
420 result = todo("depend");
421 };
422 auto checkHint = [](auto op, LogicalResult &) {
423 if (op.getHint())
424 op.emitWarning("hint clause discarded");
425 };
426 auto checkInReduction = [&todo](auto op, LogicalResult &result) {
427 if (isa<omp::TargetOp, omp::TaskOp, omp::TaskloopContextOp>(
428 op.getOperation())) {
429 if (auto byrefAttr = op.getInReductionByref()) {
430 for (bool isByRef : *byrefAttr) {
431 if (isByRef) {
432 result = todo("in_reduction with byref modifier");
433 return;
434 }
435 }
436 }
437 if (isa<omp::TargetOp>(op.getOperation())) {
438 if (auto inReductionSyms = op.getInReductionSyms()) {
439 for (auto sym :
440 (*inReductionSyms).template getAsRange<SymbolRefAttr>()) {
441 auto decl =
443 op, sym);
444 assert(decl &&
445 "symbol resolution should be guaranteed by the op verifier");
446 if (decl.getInitializerRegion().front().getNumArguments() != 1) {
447 result = todo("in_reduction with two-argument initializer");
448 return;
449 }
450 if (!decl.getCleanupRegion().empty()) {
451 result = todo("in_reduction with cleanup region");
452 return;
453 }
454 }
455 }
456 }
457 } else if (!op.getInReductionVars().empty() || op.getInReductionByref() ||
458 op.getInReductionSyms()) {
459 result = todo("in_reduction");
460 }
461 };
462 auto checkNowait = [&todo](auto op, LogicalResult &result) {
463 if (op.getNowait())
464 result = todo("nowait");
465 };
466 auto checkOrder = [&todo](auto op, LogicalResult &result) {
467 if (op.getOrder() || op.getOrderMod())
468 result = todo("order");
469 };
470 auto checkPrivate = [&todo](auto op, LogicalResult &result) {
471 if (!op.getPrivateVars().empty() || op.getPrivateSyms())
472 result = todo("privatization");
473 };
474 auto checkReduction = [&todo](auto op, LogicalResult &result) {
475 if (isa<omp::TeamsOp>(op))
476 if (!op.getReductionVars().empty() || op.getReductionByref() ||
477 op.getReductionSyms())
478 result = todo("reduction");
479 if (op.getReductionMod() &&
480 op.getReductionMod().value() != omp::ReductionModifier::defaultmod) {
481 omp::ReductionModifier mod = op.getReductionMod().value();
482 // The `task` reduction modifier is supported on the parallel and
483 // worksharing (do/for and sections) constructs. Other modifiers, and the
484 // `task` modifier on other constructs, are not yet implemented.
485 bool taskModifierSupported =
486 mod == omp::ReductionModifier::task &&
487 isa<omp::ParallelOp, omp::WsloopOp, omp::SectionsOp>(op);
488 if (!taskModifierSupported) {
489 result = todo("reduction with modifier");
490 } else if (auto byref = op.getReductionByref()) {
491 // The task reduction modifier lowering only handles non-byref
492 // reductions for now.
493 for (bool isByRef : *byref)
494 if (isByRef) {
495 result = todo("task reduction modifier with by-ref reduction");
496 break;
497 }
498 }
499 }
500 };
501 auto checkTaskReductionByref = [&todo](auto op, LogicalResult &result) {
502 if (auto byrefAttr = op.getTaskReductionByref())
503 for (bool isByRef : *byrefAttr)
504 if (isByRef) {
505 result = todo("task_reduction with byref modifier");
506 return;
507 }
508 };
509 auto checkReductionByref = [&todo](auto op, LogicalResult &result) {
510 if (auto byrefAttr = op.getReductionByref())
511 for (bool isByRef : *byrefAttr)
512 if (isByRef) {
513 result = todo("reduction with byref modifier");
514 return;
515 }
516 };
517 auto checkNumTeams = [&todo](auto op, LogicalResult &result) {
518 if (op.hasNumTeamsMultiDim())
519 result = todo("num_teams with multi-dimensional values");
520 };
521 auto checkNumThreads = [&todo](auto op, LogicalResult &result) {
522 if (op.hasNumThreadsMultiDim())
523 result = todo("num_threads with multi-dimensional values");
524 };
525
526 auto checkThreadLimit = [&todo](auto op, LogicalResult &result) {
527 if (op.hasThreadLimitMultiDim())
528 result = todo("thread_limit with multi-dimensional values");
529 };
530 auto checkMap = [&todo](auto op, LogicalResult &result) {
531 if (!op.getMapIterated().empty())
532 result = todo("map/motion clause with iterator modifier");
533 };
534
535 auto checkDynGroupprivate = [&todo](auto op, LogicalResult &result) {
536 if (op.getDynGroupprivateSize())
537 result = todo("dyn_groupprivate");
538 };
539
540 LogicalResult result = success();
542 .Case([&](omp::DistributeOp op) {
543 checkAllocate(op, result);
544 checkOrder(op, result);
545 })
546 .Case([&](omp::SectionsOp op) {
547 checkAllocate(op, result);
548 checkPrivate(op, result);
549 checkReduction(op, result);
550 })
551 .Case([&](omp::ScopeOp op) {
552 checkAllocate(op, result);
553 checkReduction(op, result);
554 })
555 .Case([&](omp::SingleOp op) {
556 checkAllocate(op, result);
557 checkPrivate(op, result);
558 })
559 .Case([&](omp::TeamsOp op) {
560 checkAllocate(op, result);
561 checkPrivate(op, result);
562 checkNumTeams(op, result);
563 checkThreadLimit(op, result);
564 checkDynGroupprivate(op, result);
565 })
566 .Case([&](omp::TaskOp op) {
567 checkAllocate(op, result);
568 checkInReduction(op, result);
569 })
570 .Case([&](omp::TaskgroupOp op) {
571 checkAllocate(op, result);
572 checkTaskReductionByref(op, result);
573 })
574 .Case([&](omp::TaskwaitOp op) { checkNowait(op, result); })
575 .Case([&](omp::TaskloopContextOp op) {
576 checkAllocate(op, result);
577 checkInReduction(op, result);
578 checkReduction(op, result);
579 checkReductionByref(op, result);
580 })
581 .Case([&](omp::WsloopOp op) {
582 checkAllocate(op, result);
583 checkOrder(op, result);
584 checkReduction(op, result);
585 })
586 .Case([&](omp::ParallelOp op) {
587 checkReduction(op, result);
588 checkNumThreads(op, result);
589 })
590 .Case([&](omp::SimdOp op) { checkReduction(op, result); })
591 .Case<omp::AtomicReadOp, omp::AtomicWriteOp, omp::AtomicUpdateOp,
592 omp::AtomicCaptureOp>([&](auto op) { checkHint(op, result); })
593 .Case([&](omp::AtomicCompareOp op) {
594 checkHint(op, result);
595 Region &region = op.getRegion();
596 if (region.empty())
597 return;
598 mlir::Type argType = region.front().getArgument(0).getType();
599 auto structTy = dyn_cast<LLVM::LLVMStructType>(argType);
600 if (!structTy)
601 return;
602 DataLayout dl = DataLayout(op->getParentOfType<ModuleOp>());
603 unsigned totalBits = dl.getTypeSizeInBits(structTy);
604 if (totalBits > 128)
605 result = todo("compare for complex types wider than 128 bits");
606 })
607 .Case<omp::TargetEnterDataOp, omp::TargetExitDataOp>([&](auto op) {
608 checkDepend(op, result);
609 checkMap(op, result);
610 })
611 .Case([&](omp::TargetUpdateOp op) {
612 checkDepend(op, result);
613 checkMap(op, result);
614 })
615 .Case([&](omp::TargetOp op) {
616 checkAllocate(op, result);
617 checkBare(op, result);
618 checkInReduction(op, result);
619 checkMap(op, result);
620 checkThreadLimit(op, result);
621 })
622 .Case([&](omp::TargetDataOp op) { checkMap(op, result); })
623 .Case([&](omp::DeclareMapperInfoOp op) { checkMap(op, result); })
624 .Default([](Operation &) {
625 // Assume all clauses for an operation can be translated unless they are
626 // checked above.
627 });
628 return result;
629}
630
631static LogicalResult handleError(llvm::Error error, Operation &op) {
632 LogicalResult result = success();
633 if (error) {
634 llvm::handleAllErrors(
635 std::move(error),
636 [&](const PreviouslyReportedError &) { result = failure(); },
637 [&](const llvm::ErrorInfoBase &err) {
638 result = op.emitError(err.message());
639 });
640 }
641 return result;
642}
643
644template <typename T>
645static LogicalResult handleError(llvm::Expected<T> &result, Operation &op) {
646 if (!result)
647 return handleError(result.takeError(), op);
648
649 return success();
650}
651
652/// Find the insertion point for allocas given the current insertion point for
653/// normal operations in the builder.
654static llvm::OpenMPIRBuilder::InsertPointTy findAllocInsertPoints(
655 llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation,
656 llvm::SmallVectorImpl<llvm::BasicBlock *> *deallocBlocks = nullptr) {
657 // If there is an allocation insertion point on stack, i.e. we are in a nested
658 // operation and a specific point was provided by some surrounding operation,
659 // use it.
660 llvm::OpenMPIRBuilder::InsertPointTy allocInsertPoint;
661 llvm::ArrayRef<llvm::BasicBlock *> deallocInsertPoints;
662 WalkResult walkResult = moduleTranslation.stackWalk<OpenMPAllocStackFrame>(
663 [&](OpenMPAllocStackFrame &frame) {
664 allocInsertPoint = frame.allocInsertPoint;
665 deallocInsertPoints = frame.deallocBlocks;
666 return WalkResult::interrupt();
667 });
668 // In cases with multiple levels of outlining, the tree walk might find an
669 // insertion point that is inside the original function while the builder
670 // insertion point is inside the outlined function. We need to make sure that
671 // we do not use it in those cases.
672 if (walkResult.wasInterrupted() &&
673 allocInsertPoint.getBlock()->getParent() ==
674 builder.GetInsertBlock()->getParent()) {
675 if (deallocBlocks)
676 deallocBlocks->insert(deallocBlocks->end(), deallocInsertPoints.begin(),
677 deallocInsertPoints.end());
678 return allocInsertPoint;
679 }
680
681 // Otherwise, insert to the entry block of the surrounding function.
682 // If the current IRBuilder InsertPoint is the function's entry, it cannot
683 // also be used for alloca insertion which would result in insertion order
684 // confusion. Create a new BasicBlock for the Builder and use the entry block
685 // for the allocs.
686 // TODO: Create a dedicated alloca BasicBlock at function creation such that
687 // we do not need to move the current InsertPoint here.
688 if (builder.GetInsertBlock() ==
689 &builder.GetInsertBlock()->getParent()->getEntryBlock()) {
690 assert(builder.GetInsertPoint() == builder.GetInsertBlock()->end() &&
691 "Assuming end of basic block");
692 llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(
693 builder.getContext(), "entry", builder.GetInsertBlock()->getParent(),
694 builder.GetInsertBlock()->getNextNode());
695 builder.CreateBr(entryBB);
696 builder.SetInsertPoint(entryBB);
697 }
698
699 // Collect exit blocks, which is where explicit deallocations should happen in
700 // this case.
701 if (deallocBlocks) {
702 for (llvm::BasicBlock &block : *builder.GetInsertBlock()->getParent()) {
703 // TODO: This currently results in no blocks being added to the list when
704 // all exit blocks of the enclosing function have not been lowered before
705 // this is reached.
706 llvm::Instruction *terminator = block.getTerminatorOrNull();
707 if (isa_and_present<llvm::ReturnInst>(terminator))
708 deallocBlocks->emplace_back(&block);
709 }
710 }
711
712 llvm::BasicBlock &funcEntryBlock =
713 builder.GetInsertBlock()->getParent()->getEntryBlock();
714 return llvm::OpenMPIRBuilder::InsertPointTy(
715 &funcEntryBlock, funcEntryBlock.getFirstInsertionPt());
716}
717
718/// Find the loop information structure for the loop nest being translated. It
719/// will return a `null` value unless called from the translation function for
720/// a loop wrapper operation after successfully translating its body.
721static llvm::CanonicalLoopInfo *
723 llvm::CanonicalLoopInfo *loopInfo = nullptr;
724 moduleTranslation.stackWalk<OpenMPLoopInfoStackFrame>(
725 [&](OpenMPLoopInfoStackFrame &frame) {
726 loopInfo = frame.loopInfo;
727 return WalkResult::interrupt();
728 });
729 return loopInfo;
730}
731
732/// Converts the given region that appears within an OpenMP dialect operation to
733/// LLVM IR, creating a branch from the `sourceBlock` to the entry block of the
734/// region, and a branch from any block with an successor-less OpenMP terminator
735/// to `continuationBlock`. Populates `continuationBlockPHIs` with the PHI nodes
736/// of the continuation block if provided.
738 Region &region, StringRef blockName, llvm::IRBuilderBase &builder,
739 LLVM::ModuleTranslation &moduleTranslation,
740 SmallVectorImpl<llvm::PHINode *> *continuationBlockPHIs = nullptr) {
741 bool isLoopWrapper = isa<omp::LoopWrapperInterface>(region.getParentOp());
742
743 llvm::BasicBlock *continuationBlock =
744 splitBB(builder, true, "omp.region.cont");
745 llvm::BasicBlock *sourceBlock = builder.GetInsertBlock();
746
747 llvm::LLVMContext &llvmContext = builder.getContext();
748 for (Block &bb : region) {
749 llvm::BasicBlock *llvmBB = llvm::BasicBlock::Create(
750 llvmContext, blockName, builder.GetInsertBlock()->getParent(),
751 builder.GetInsertBlock()->getNextNode());
752 moduleTranslation.mapBlock(&bb, llvmBB);
753 }
754
755 llvm::Instruction *sourceTerminator = sourceBlock->getTerminator();
756
757 // Terminators (namely YieldOp) may be forwarding values to the region that
758 // need to be available in the continuation block. Collect the types of these
759 // operands in preparation of creating PHI nodes. This is skipped for loop
760 // wrapper operations, for which we know in advance they have no terminators.
761 SmallVector<llvm::Type *> continuationBlockPHITypes;
762 unsigned numYields = 0;
763
764 if (!isLoopWrapper) {
765 bool operandsProcessed = false;
766 for (Block &bb : region.getBlocks()) {
767 if (omp::YieldOp yield = dyn_cast<omp::YieldOp>(bb.getTerminator())) {
768 if (!operandsProcessed) {
769 for (unsigned i = 0, e = yield->getNumOperands(); i < e; ++i) {
770 continuationBlockPHITypes.push_back(
771 moduleTranslation.convertType(yield->getOperand(i).getType()));
772 }
773 operandsProcessed = true;
774 } else {
775 assert(continuationBlockPHITypes.size() == yield->getNumOperands() &&
776 "mismatching number of values yielded from the region");
777 for (unsigned i = 0, e = yield->getNumOperands(); i < e; ++i) {
778 llvm::Type *operandType =
779 moduleTranslation.convertType(yield->getOperand(i).getType());
780 (void)operandType;
781 assert(continuationBlockPHITypes[i] == operandType &&
782 "values of mismatching types yielded from the region");
783 }
784 }
785 numYields++;
786 }
787 }
788 }
789
790 // Insert PHI nodes in the continuation block for any values forwarded by the
791 // terminators in this region.
792 if (!continuationBlockPHITypes.empty())
793 assert(
794 continuationBlockPHIs &&
795 "expected continuation block PHIs if converted regions yield values");
796 if (continuationBlockPHIs) {
797 llvm::IRBuilderBase::InsertPointGuard guard(builder);
798 continuationBlockPHIs->reserve(continuationBlockPHITypes.size());
799 builder.SetInsertPoint(continuationBlock, continuationBlock->begin());
800 for (llvm::Type *ty : continuationBlockPHITypes)
801 continuationBlockPHIs->push_back(builder.CreatePHI(ty, numYields));
802 }
803
804 // Convert blocks one by one in topological order to ensure
805 // defs are converted before uses.
807 for (Block *bb : blocks) {
808 llvm::BasicBlock *llvmBB = moduleTranslation.lookupBlock(bb);
809 // Retarget the branch of the entry block to the entry block of the
810 // converted region (regions are single-entry).
811 if (bb->isEntryBlock()) {
812 assert(sourceTerminator->getNumSuccessors() == 1 &&
813 "provided entry block has multiple successors");
814 assert(sourceTerminator->getSuccessor(0) == continuationBlock &&
815 "ContinuationBlock is not the successor of the entry block");
816 sourceTerminator->setSuccessor(0, llvmBB);
817 }
818
819 llvm::IRBuilderBase::InsertPointGuard guard(builder);
820 if (failed(
821 moduleTranslation.convertBlock(*bb, bb->isEntryBlock(), builder)))
822 return llvm::make_error<PreviouslyReportedError>();
823
824 // Create a direct branch here for loop wrappers to prevent their lack of a
825 // terminator from causing a crash below.
826 if (isLoopWrapper) {
827 builder.CreateBr(continuationBlock);
828 continue;
829 }
830
831 // Special handling for `omp.yield` and `omp.terminator` (we may have more
832 // than one): they return the control to the parent OpenMP dialect operation
833 // so replace them with the branch to the continuation block. We handle this
834 // here to avoid relying inter-function communication through the
835 // ModuleTranslation class to set up the correct insertion point. This is
836 // also consistent with MLIR's idiom of handling special region terminators
837 // in the same code that handles the region-owning operation.
838 Operation *terminator = bb->getTerminator();
839 if (isa<omp::TerminatorOp, omp::YieldOp>(terminator)) {
840 builder.CreateBr(continuationBlock);
841
842 for (unsigned i = 0, e = terminator->getNumOperands(); i < e; ++i)
843 (*continuationBlockPHIs)[i]->addIncoming(
844 moduleTranslation.lookupValue(terminator->getOperand(i)), llvmBB);
845 }
846 }
847 // After all blocks have been traversed and values mapped, connect the PHI
848 // nodes to the results of preceding blocks.
849 LLVM::detail::connectPHINodes(region, moduleTranslation);
850
851 // Remove the blocks and values defined in this region from the mapping since
852 // they are not visible outside of this region. This allows the same region to
853 // be converted several times, that is cloned, without clashes, and slightly
854 // speeds up the lookups.
855 moduleTranslation.forgetMapping(region);
856
857 return continuationBlock;
858}
859
860/// Convert ProcBindKind from MLIR-generated enum to LLVM enum.
861static llvm::omp::ProcBindKind getProcBindKind(omp::ClauseProcBindKind kind) {
862 switch (kind) {
863 case omp::ClauseProcBindKind::Close:
864 return llvm::omp::ProcBindKind::OMP_PROC_BIND_close;
865 case omp::ClauseProcBindKind::Master:
866 return llvm::omp::ProcBindKind::OMP_PROC_BIND_master;
867 case omp::ClauseProcBindKind::Primary:
868 return llvm::omp::ProcBindKind::OMP_PROC_BIND_primary;
869 case omp::ClauseProcBindKind::Spread:
870 return llvm::omp::ProcBindKind::OMP_PROC_BIND_spread;
871 }
872 llvm_unreachable("Unknown ClauseProcBindKind kind");
873}
874
875/// Converts an OpenMP 'masked' operation into LLVM IR using OpenMPIRBuilder.
876static LogicalResult
877convertOmpMasked(Operation &opInst, llvm::IRBuilderBase &builder,
878 LLVM::ModuleTranslation &moduleTranslation) {
879 auto maskedOp = cast<omp::MaskedOp>(opInst);
880 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
881
882 if (failed(checkImplementationStatus(opInst)))
883 return failure();
884
885 auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
887 // MaskedOp has only one region associated with it.
888 auto &region = maskedOp.getRegion();
889 builder.restoreIP(codeGenIP);
890 return convertOmpOpRegions(region, "omp.masked.region", builder,
891 moduleTranslation)
892 .takeError();
893 };
894
895 // TODO: Perform finalization actions for variables. This has to be
896 // called for variables which have destructors/finalizers.
897 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
898
899 llvm::Value *filterVal = nullptr;
900 if (auto filterVar = maskedOp.getFilteredThreadId()) {
901 filterVal = moduleTranslation.lookupValue(filterVar);
902 } else {
903 llvm::LLVMContext &llvmContext = builder.getContext();
904 filterVal =
905 llvm::ConstantInt::get(llvm::Type::getInt32Ty(llvmContext), /*V=*/0);
906 }
907 assert(filterVal != nullptr);
908 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
909 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
910 moduleTranslation.getOpenMPBuilder()->createMasked(ompLoc, bodyGenCB,
911 finiCB, filterVal);
912
913 if (failed(handleError(afterIP, opInst)))
914 return failure();
915
916 builder.restoreIP(*afterIP);
917 return success();
918}
919
920/// Converts an OpenMP 'master' operation into LLVM IR using OpenMPIRBuilder.
921static LogicalResult
922convertOmpMaster(Operation &opInst, llvm::IRBuilderBase &builder,
923 LLVM::ModuleTranslation &moduleTranslation) {
924 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
925 auto masterOp = cast<omp::MasterOp>(opInst);
926
927 if (failed(checkImplementationStatus(opInst)))
928 return failure();
929
930 auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
932 // MasterOp has only one region associated with it.
933 auto &region = masterOp.getRegion();
934 builder.restoreIP(codeGenIP);
935 return convertOmpOpRegions(region, "omp.master.region", builder,
936 moduleTranslation)
937 .takeError();
938 };
939
940 // TODO: Perform finalization actions for variables. This has to be
941 // called for variables which have destructors/finalizers.
942 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
943
944 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
945 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
946 moduleTranslation.getOpenMPBuilder()->createMaster(ompLoc, bodyGenCB,
947 finiCB);
948
949 if (failed(handleError(afterIP, opInst)))
950 return failure();
951
952 builder.restoreIP(*afterIP);
953 return success();
954}
955
956/// Converts an OpenMP 'critical' operation into LLVM IR using OpenMPIRBuilder.
957static LogicalResult
958convertOmpCritical(Operation &opInst, llvm::IRBuilderBase &builder,
959 LLVM::ModuleTranslation &moduleTranslation) {
960 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
961 auto criticalOp = cast<omp::CriticalOp>(opInst);
962
963 if (failed(checkImplementationStatus(opInst)))
964 return failure();
965
966 auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
968 // CriticalOp has only one region associated with it.
969 auto &region = cast<omp::CriticalOp>(opInst).getRegion();
970 builder.restoreIP(codeGenIP);
971 return convertOmpOpRegions(region, "omp.critical.region", builder,
972 moduleTranslation)
973 .takeError();
974 };
975
976 // TODO: Perform finalization actions for variables. This has to be
977 // called for variables which have destructors/finalizers.
978 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
979
980 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
981 llvm::LLVMContext &llvmContext = moduleTranslation.getLLVMContext();
982 llvm::Constant *hint = nullptr;
983
984 // If it has a name, it probably has a hint too.
985 if (criticalOp.getNameAttr()) {
986 // The verifiers in OpenMP Dialect guarentee that all the pointers are
987 // non-null
988 auto symbolRef = cast<SymbolRefAttr>(criticalOp.getNameAttr());
989 auto criticalDeclareOp =
991 symbolRef);
992 hint =
993 llvm::ConstantInt::get(llvm::Type::getInt32Ty(llvmContext),
994 static_cast<int>(criticalDeclareOp.getHint()));
995 }
996 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
997 moduleTranslation.getOpenMPBuilder()->createCritical(
998 ompLoc, bodyGenCB, finiCB, criticalOp.getName().value_or(""), hint);
999
1000 if (failed(handleError(afterIP, opInst)))
1001 return failure();
1002
1003 builder.restoreIP(*afterIP);
1004 return success();
1005}
1006
1007/// A util to collect info needed to convert delayed privatizers from MLIR to
1008/// LLVM.
1011 llvm::Value *allocatedPtr;
1012 llvm::Value *allocator;
1013 };
1014
1015 template <typename OP>
1017 : blockArgs(
1018 cast<omp::BlockArgOpenMPOpInterface>(*op).getPrivateBlockArgs()) {
1019 mlirVars.reserve(blockArgs.size());
1020 llvmVars.reserve(blockArgs.size());
1021 collectPrivatizationDecls<OP>(op);
1022
1023 for (mlir::Value privateVar : op.getPrivateVars())
1024 mlirVars.push_back(privateVar);
1025 }
1026
1033
1034private:
1035 /// Populates `privatizations` with privatization declarations used for the
1036 /// given op.
1037 template <class OP>
1038 void collectPrivatizationDecls(OP op) {
1039 std::optional<ArrayAttr> attr = op.getPrivateSyms();
1040 if (!attr)
1041 return;
1042
1043 privatizers.reserve(privatizers.size() + attr->size());
1044 for (auto symbolRef : attr->getAsRange<SymbolRefAttr>()) {
1045 privatizers.push_back(findPrivatizer(op, symbolRef));
1046 }
1047 }
1048};
1049
1050/// Populates `reductions` with reduction declarations used in the given op.
1051template <typename T>
1052static void
1055 std::optional<ArrayAttr> attr = op.getReductionSyms();
1056 if (!attr)
1057 return;
1058
1059 reductions.reserve(reductions.size() + op.getNumReductionVars());
1060 for (auto symbolRef : attr->getAsRange<SymbolRefAttr>()) {
1061 reductions.push_back(
1063 op, symbolRef));
1064 }
1065}
1066
1067/// Look up and validate the declare_reduction ops referenced by a
1068/// reduction-like clause on the omp.taskloop.context translation path. Only
1069/// the non-byref, single-init-arg, no-cleanup form is supported in this
1070/// initial cut; richer shapes are rejected here with a diagnostic. \p syms
1071/// is the clause's symbol list (e.g. `getReductionSyms()` or
1072/// `getInReductionSyms()`), \p opName is the textual op name used in
1073/// diagnostics, and \p clauseName distinguishes "reduction" from
1074/// "in_reduction" in those diagnostics.
1076 Operation *contextOp, std::optional<ArrayAttr> syms, StringRef opName,
1077 StringRef clauseName, SmallVectorImpl<omp::DeclareReductionOp> &out) {
1078 if (!syms)
1079 return success();
1080 out.reserve(out.size() + syms->size());
1081 for (auto sym : syms->getAsRange<SymbolRefAttr>()) {
1083 contextOp, sym);
1084 if (!decl)
1085 return contextOp->emitError()
1086 << "failed to resolve " << clauseName
1087 << " declare_reduction symbol " << sym.getRootReference() << " in "
1088 << opName;
1089 if (decl.getInitializerRegion().front().getNumArguments() != 1)
1090 return contextOp->emitError()
1091 << "not yet implemented: " << clauseName
1092 << " with two-argument initializer in " << opName;
1093 if (!decl.getCleanupRegion().empty())
1094 return contextOp->emitError() << "not yet implemented: " << clauseName
1095 << " with cleanup region in " << opName;
1096 if (decl.getReductionRegion().empty())
1097 return contextOp->emitError()
1098 << clauseName << " declare_reduction is missing a combiner region";
1099 out.push_back(decl);
1100 }
1101 return success();
1102}
1103
1104/// Translates the blocks contained in the given region and appends them to at
1105/// the current insertion point of `builder`. The operations of the entry block
1106/// are appended to the current insertion block. If set, `continuationBlockArgs`
1107/// is populated with translated values that correspond to the values
1108/// omp.yield'ed from the region.
1109static LogicalResult inlineConvertOmpRegions(
1110 Region &region, StringRef blockName, llvm::IRBuilderBase &builder,
1111 LLVM::ModuleTranslation &moduleTranslation,
1112 SmallVectorImpl<llvm::Value *> *continuationBlockArgs = nullptr) {
1113 if (region.empty())
1114 return success();
1115
1116 // Special case for single-block regions that don't create additional blocks:
1117 // insert operations without creating additional blocks.
1118 if (region.hasOneBlock()) {
1119 llvm::Instruction *potentialTerminator =
1120 builder.GetInsertBlock()->empty() ? nullptr
1121 : &builder.GetInsertBlock()->back();
1122
1123 if (potentialTerminator && potentialTerminator->isTerminator())
1124 potentialTerminator->removeFromParent();
1125 moduleTranslation.mapBlock(&region.front(), builder.GetInsertBlock());
1126
1127 if (failed(moduleTranslation.convertBlock(
1128 region.front(), /*ignoreArguments=*/true, builder)))
1129 return failure();
1130
1131 // The continuation arguments are simply the translated terminator operands.
1132 if (continuationBlockArgs)
1133 llvm::append_range(
1134 *continuationBlockArgs,
1135 moduleTranslation.lookupValues(region.front().back().getOperands()));
1136
1137 // Drop the mapping that is no longer necessary so that the same region can
1138 // be processed multiple times.
1139 moduleTranslation.forgetMapping(region);
1140
1141 if (potentialTerminator && potentialTerminator->isTerminator()) {
1142 llvm::BasicBlock *block = builder.GetInsertBlock();
1143 if (block->empty()) {
1144 // this can happen for really simple reduction init regions e.g.
1145 // %0 = llvm.mlir.constant(0 : i32) : i32
1146 // omp.yield(%0 : i32)
1147 // because the llvm.mlir.constant (MLIR op) isn't converted into any
1148 // llvm op
1149 potentialTerminator->insertInto(block, block->begin());
1150 } else {
1151 potentialTerminator->insertAfter(&block->back());
1152 }
1153 }
1154
1155 return success();
1156 }
1157
1159 llvm::Expected<llvm::BasicBlock *> continuationBlock =
1160 convertOmpOpRegions(region, blockName, builder, moduleTranslation, &phis);
1161
1162 if (failed(handleError(continuationBlock, *region.getParentOp())))
1163 return failure();
1164
1165 if (continuationBlockArgs)
1166 llvm::append_range(*continuationBlockArgs, phis);
1167 builder.SetInsertPoint(*continuationBlock,
1168 (*continuationBlock)->getFirstInsertionPt());
1169 return success();
1170}
1171
1172namespace {
1173/// Owning equivalents of OpenMPIRBuilder::(Atomic)ReductionGen that are used to
1174/// store lambdas with capture.
1175using OwningReductionGen =
1176 std::function<llvm::OpenMPIRBuilder::InsertPointOrErrorTy(
1177 llvm::OpenMPIRBuilder::InsertPointTy, llvm::Value *, llvm::Value *,
1178 llvm::Value *&)>;
1179using OwningAtomicReductionGen =
1180 std::function<llvm::OpenMPIRBuilder::InsertPointOrErrorTy(
1181 llvm::OpenMPIRBuilder::InsertPointTy, llvm::Type *, llvm::Value *,
1182 llvm::Value *)>;
1183using OwningDataPtrPtrReductionGen =
1184 std::function<llvm::OpenMPIRBuilder::InsertPointOrErrorTy(
1185 llvm::OpenMPIRBuilder::InsertPointTy, llvm::Value *, llvm::Value *&)>;
1186} // namespace
1187
1188/// Create an OpenMPIRBuilder-compatible reduction generator for the given
1189/// reduction declaration. The generator uses `builder` but ignores its
1190/// insertion point.
1191static OwningReductionGen
1192makeReductionGen(omp::DeclareReductionOp decl, llvm::IRBuilderBase &builder,
1193 LLVM::ModuleTranslation &moduleTranslation) {
1194 // The lambda is mutable because we need access to non-const methods of decl
1195 // (which aren't actually mutating it), and we must capture decl by-value to
1196 // avoid the dangling reference after the parent function returns.
1197 OwningReductionGen gen =
1198 [&, decl](llvm::OpenMPIRBuilder::InsertPointTy insertPoint,
1199 llvm::Value *lhs, llvm::Value *rhs,
1200 llvm::Value *&result) mutable
1201 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
1202 moduleTranslation.mapValue(decl.getReductionLhsArg(), lhs);
1203 moduleTranslation.mapValue(decl.getReductionRhsArg(), rhs);
1204 builder.restoreIP(insertPoint);
1206 if (failed(inlineConvertOmpRegions(decl.getReductionRegion(),
1207 "omp.reduction.nonatomic.body", builder,
1208 moduleTranslation, &phis)))
1209 return llvm::createStringError(
1210 "failed to inline `combiner` region of `omp.declare_reduction`");
1211 result = llvm::getSingleElement(phis);
1212 return builder.saveIP();
1213 };
1214 return gen;
1215}
1216
1217/// Create an OpenMPIRBuilder-compatible atomic reduction generator for the
1218/// given reduction declaration. The generator uses `builder` but ignores its
1219/// insertion point. Returns null if there is no atomic region available in the
1220/// reduction declaration.
1221static OwningAtomicReductionGen
1222makeAtomicReductionGen(omp::DeclareReductionOp decl,
1223 llvm::IRBuilderBase &builder,
1224 LLVM::ModuleTranslation &moduleTranslation) {
1225 if (decl.getAtomicReductionRegion().empty())
1226 return OwningAtomicReductionGen();
1227
1228 // The lambda is mutable because we need access to non-const methods of decl
1229 // (which aren't actually mutating it), and we must capture decl by-value to
1230 // avoid the dangling reference after the parent function returns.
1231 OwningAtomicReductionGen atomicGen =
1232 [&, decl](llvm::OpenMPIRBuilder::InsertPointTy insertPoint, llvm::Type *,
1233 llvm::Value *lhs, llvm::Value *rhs) mutable
1234 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
1235 moduleTranslation.mapValue(decl.getAtomicReductionLhsArg(), lhs);
1236 moduleTranslation.mapValue(decl.getAtomicReductionRhsArg(), rhs);
1237 builder.restoreIP(insertPoint);
1239 if (failed(inlineConvertOmpRegions(decl.getAtomicReductionRegion(),
1240 "omp.reduction.atomic.body", builder,
1241 moduleTranslation, &phis)))
1242 return llvm::createStringError(
1243 "failed to inline `atomic` region of `omp.declare_reduction`");
1244 assert(phis.empty());
1245 return builder.saveIP();
1246 };
1247 return atomicGen;
1248}
1249
1250/// Create an OpenMPIRBuilder-compatible `data_ptr_ptr` reduction generator for
1251/// the given reduction declaration. The generator uses `builder` but ignores
1252/// its insertion point. Returns null if there is no `data_ptr_ptr` region
1253/// available in the reduction declaration.
1254static OwningDataPtrPtrReductionGen
1255makeRefDataPtrGen(omp::DeclareReductionOp decl, llvm::IRBuilderBase &builder,
1256 LLVM::ModuleTranslation &moduleTranslation, bool isByRef) {
1257 if (!isByRef || decl.getDataPtrPtrRegion().empty())
1258 return OwningDataPtrPtrReductionGen();
1259
1260 OwningDataPtrPtrReductionGen refDataPtrGen =
1261 [&, decl](llvm::OpenMPIRBuilder::InsertPointTy insertPoint,
1262 llvm::Value *byRefVal, llvm::Value *&result) mutable
1263 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
1264 moduleTranslation.mapValue(decl.getDataPtrPtrRegionArg(), byRefVal);
1265 builder.restoreIP(insertPoint);
1267 if (failed(inlineConvertOmpRegions(decl.getDataPtrPtrRegion(),
1268 "omp.data_ptr_ptr.body", builder,
1269 moduleTranslation, &phis)))
1270 return llvm::createStringError(
1271 "failed to inline `data_ptr_ptr` region of `omp.declare_reduction`");
1272 result = llvm::getSingleElement(phis);
1273 return builder.saveIP();
1274 };
1275
1276 return refDataPtrGen;
1277}
1278
1279/// Converts an OpenMP 'ordered' operation into LLVM IR using OpenMPIRBuilder.
1280static LogicalResult
1281convertOmpOrdered(Operation &opInst, llvm::IRBuilderBase &builder,
1282 LLVM::ModuleTranslation &moduleTranslation) {
1283 auto orderedOp = cast<omp::OrderedOp>(opInst);
1284
1285 if (failed(checkImplementationStatus(opInst)))
1286 return failure();
1287
1288 omp::ClauseDepend dependType = *orderedOp.getDoacrossDependType();
1289 bool isDependSource = dependType == omp::ClauseDepend::dependsource;
1290 unsigned numLoops = *orderedOp.getDoacrossNumLoops();
1291 SmallVector<llvm::Value *> vecValues =
1292 moduleTranslation.lookupValues(orderedOp.getDoacrossDependVars());
1293
1294 size_t indexVecValues = 0;
1295 while (indexVecValues < vecValues.size()) {
1296 SmallVector<llvm::Value *> storeValues;
1297 storeValues.reserve(numLoops);
1298 for (unsigned i = 0; i < numLoops; i++) {
1299 storeValues.push_back(vecValues[indexVecValues]);
1300 indexVecValues++;
1301 }
1302 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
1303 findAllocInsertPoints(builder, moduleTranslation);
1304 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
1305 builder.restoreIP(moduleTranslation.getOpenMPBuilder()->createOrderedDepend(
1306 ompLoc, allocaIP, numLoops, storeValues, ".cnt.addr", isDependSource));
1307 }
1308 return success();
1309}
1310
1311/// Converts an OpenMP 'ordered_region' operation into LLVM IR using
1312/// OpenMPIRBuilder.
1313static LogicalResult
1314convertOmpOrderedRegion(Operation &opInst, llvm::IRBuilderBase &builder,
1315 LLVM::ModuleTranslation &moduleTranslation) {
1316 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
1317 auto orderedRegionOp = cast<omp::OrderedRegionOp>(opInst);
1318
1319 if (failed(checkImplementationStatus(opInst)))
1320 return failure();
1321
1322 auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
1323 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) {
1324 // OrderedOp has only one region associated with it.
1325 auto &region = cast<omp::OrderedRegionOp>(opInst).getRegion();
1326 builder.restoreIP(codeGenIP);
1327 return convertOmpOpRegions(region, "omp.ordered.region", builder,
1328 moduleTranslation)
1329 .takeError();
1330 };
1331
1332 // TODO: Perform finalization actions for variables. This has to be
1333 // called for variables which have destructors/finalizers.
1334 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
1335
1336 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
1337 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
1338 moduleTranslation.getOpenMPBuilder()->createOrderedThreadsSimd(
1339 ompLoc, bodyGenCB, finiCB, !orderedRegionOp.getParLevelSimd());
1340
1341 if (failed(handleError(afterIP, opInst)))
1342 return failure();
1343
1344 builder.restoreIP(*afterIP);
1345 return success();
1346}
1347
1348namespace {
1349/// Contains the arguments for an LLVM store operation
1350struct DeferredStore {
1351 DeferredStore(llvm::Value *value, llvm::Value *address)
1352 : value(value), address(address) {}
1353
1354 llvm::Value *value;
1355 llvm::Value *address;
1356};
1357} // namespace
1358
1359/// Allocate space for privatized reduction variables.
1360/// `deferredStores` contains information to create store operations which needs
1361/// to be inserted after all allocas
1362template <typename T>
1363static LogicalResult
1365 llvm::IRBuilderBase &builder,
1366 LLVM::ModuleTranslation &moduleTranslation,
1367 const llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
1369 SmallVectorImpl<llvm::Value *> &privateReductionVariables,
1370 DenseMap<Value, llvm::Value *> &reductionVariableMap,
1371 SmallVectorImpl<DeferredStore> &deferredStores,
1372 llvm::ArrayRef<bool> isByRefs) {
1373 llvm::IRBuilderBase::InsertPointGuard guard(builder);
1374 builder.SetInsertPoint(allocaIP.getBlock()->getTerminator());
1375
1376 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
1377 bool useDeviceSharedMem = omp::opInSharedDeviceContext(*op);
1378
1379 // delay creating stores until after all allocas
1380 deferredStores.reserve(op.getNumReductionVars());
1381
1382 for (std::size_t i = 0; i < op.getNumReductionVars(); ++i) {
1383 Region &allocRegion = reductionDecls[i].getAllocRegion();
1384 if (isByRefs[i]) {
1385 if (allocRegion.empty())
1386 continue;
1387
1389 if (failed(inlineConvertOmpRegions(allocRegion, "omp.reduction.alloc",
1390 builder, moduleTranslation, &phis)))
1391 return op.emitError(
1392 "failed to inline `alloc` region of `omp.declare_reduction`");
1393
1394 assert(phis.size() == 1 && "expected one allocation to be yielded");
1395 builder.SetInsertPoint(allocaIP.getBlock()->getTerminator());
1396
1397 // Allocate reduction variable (which is a pointer to the real reduction
1398 // variable allocated in the inlined region)
1399 llvm::Type *ptrTy = builder.getPtrTy();
1400 llvm::Type *varTy =
1401 moduleTranslation.convertType(reductionDecls[i].getType());
1402 llvm::Value *var;
1403 if (useDeviceSharedMem) {
1404 var = ompBuilder->createOMPAllocShared(builder, varTy);
1405 } else {
1406 var = builder.CreateAlloca(varTy);
1407 var = builder.CreatePointerBitCastOrAddrSpaceCast(var, ptrTy);
1408 }
1409
1410 llvm::Value *castPhi =
1411 builder.CreatePointerBitCastOrAddrSpaceCast(phis[0], ptrTy);
1412
1413 deferredStores.emplace_back(castPhi, var);
1414
1415 privateReductionVariables[i] = var;
1416 moduleTranslation.mapValue(reductionArgs[i], castPhi);
1417 reductionVariableMap.try_emplace(op.getReductionVars()[i], castPhi);
1418 } else {
1419 assert(allocRegion.empty() &&
1420 "allocaction is implicit for by-val reduction");
1421
1422 llvm::Type *ptrTy = builder.getPtrTy();
1423 llvm::Type *varTy =
1424 moduleTranslation.convertType(reductionDecls[i].getType());
1425 llvm::Value *var;
1426 if (useDeviceSharedMem) {
1427 var = ompBuilder->createOMPAllocShared(builder, varTy);
1428 } else {
1429 var = builder.CreateAlloca(varTy);
1430 var = builder.CreatePointerBitCastOrAddrSpaceCast(var, ptrTy);
1431 }
1432
1433 moduleTranslation.mapValue(reductionArgs[i], var);
1434 privateReductionVariables[i] = var;
1435 reductionVariableMap.try_emplace(op.getReductionVars()[i], var);
1436 }
1437 }
1438
1439 return success();
1440}
1441
1442/// Map input arguments to reduction initialization region
1443template <typename T>
1444static void
1446 llvm::IRBuilderBase &builder,
1448 DenseMap<Value, llvm::Value *> &reductionVariableMap,
1449 unsigned i) {
1450 // map input argument to the initialization region
1451 mlir::omp::DeclareReductionOp &reduction = reductionDecls[i];
1452 Region &initializerRegion = reduction.getInitializerRegion();
1453 Block &entry = initializerRegion.front();
1454
1455 mlir::Value mlirSource = loop.getReductionVars()[i];
1456 llvm::Value *llvmSource = moduleTranslation.lookupValue(mlirSource);
1457 llvm::Value *origVal = llvmSource;
1458 // If a non-pointer value is expected, load the value from the source pointer.
1459 if (!isa<LLVM::LLVMPointerType>(
1460 reduction.getInitializerMoldArg().getType()) &&
1461 isa<LLVM::LLVMPointerType>(mlirSource.getType())) {
1462 origVal =
1463 builder.CreateLoad(moduleTranslation.convertType(
1464 reduction.getInitializerMoldArg().getType()),
1465 llvmSource, "omp_orig");
1466 }
1467 moduleTranslation.mapValue(reduction.getInitializerMoldArg(), origVal);
1468
1469 if (entry.getNumArguments() > 1) {
1470 llvm::Value *allocation =
1471 reductionVariableMap.lookup(loop.getReductionVars()[i]);
1472 moduleTranslation.mapValue(reduction.getInitializerAllocArg(), allocation);
1473 }
1474}
1475
1476static void
1477setInsertPointForPossiblyEmptyBlock(llvm::IRBuilderBase &builder,
1478 llvm::BasicBlock *block = nullptr) {
1479 if (block == nullptr)
1480 block = builder.GetInsertBlock();
1481
1482 if (!block->hasTerminator())
1483 builder.SetInsertPoint(block);
1484 else
1485 builder.SetInsertPoint(block->getTerminator());
1486}
1487
1488/// Inline reductions' `init` regions. This functions assumes that the
1489/// `builder`'s insertion point is where the user wants the `init` regions to be
1490/// inlined; i.e. it does not try to find a proper insertion location for the
1491/// `init` regions. It also leaves the `builder's insertions point in a state
1492/// where the user can continue the code-gen directly afterwards.
1493template <typename OP>
1494static LogicalResult
1495initReductionVars(OP op, ArrayRef<BlockArgument> reductionArgs,
1496 llvm::IRBuilderBase &builder,
1497 LLVM::ModuleTranslation &moduleTranslation,
1498 llvm::BasicBlock *latestAllocaBlock,
1500 SmallVectorImpl<llvm::Value *> &privateReductionVariables,
1501 DenseMap<Value, llvm::Value *> &reductionVariableMap,
1502 llvm::ArrayRef<bool> isByRef,
1503 SmallVectorImpl<DeferredStore> &deferredStores) {
1504 if (op.getNumReductionVars() == 0)
1505 return success();
1506
1507 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
1508 bool useDeviceSharedMem = omp::opInSharedDeviceContext(*op);
1509
1510 llvm::BasicBlock *initBlock = splitBB(builder, true, "omp.reduction.init");
1511 auto allocaIP = llvm::IRBuilderBase::InsertPoint(
1512 latestAllocaBlock, latestAllocaBlock->getTerminator()->getIterator());
1513 builder.restoreIP(allocaIP);
1514 SmallVector<llvm::Value *> byRefVars(op.getNumReductionVars());
1515
1516 for (unsigned i = 0; i < op.getNumReductionVars(); ++i) {
1517 if (isByRef[i]) {
1518 if (!reductionDecls[i].getAllocRegion().empty())
1519 continue;
1520
1521 // TODO: remove after all users of by-ref are updated to use the alloc
1522 // region: Allocate reduction variable (which is a pointer to the real
1523 // reduciton variable allocated in the inlined region)
1524 llvm::Type *varTy =
1525 moduleTranslation.convertType(reductionDecls[i].getType());
1526 if (useDeviceSharedMem)
1527 byRefVars[i] = ompBuilder->createOMPAllocShared(builder, varTy);
1528 else
1529 byRefVars[i] = builder.CreateAlloca(varTy);
1530 }
1531 }
1532
1533 setInsertPointForPossiblyEmptyBlock(builder, initBlock);
1534
1535 // store result of the alloc region to the allocated pointer to the real
1536 // reduction variable
1537 for (auto [data, addr] : deferredStores)
1538 builder.CreateStore(data, addr);
1539
1540 // Before the loop, store the initial values of reductions into reduction
1541 // variables. Although this could be done after allocas, we don't want to mess
1542 // up with the alloca insertion point.
1543 for (unsigned i = 0; i < op.getNumReductionVars(); ++i) {
1545
1546 // map block argument to initializer region
1547 mapInitializationArgs(op, moduleTranslation, builder, reductionDecls,
1548 reductionVariableMap, i);
1549
1550 // TODO In some cases (specially on the GPU), the init regions may
1551 // contains stack alloctaions. If the region is inlined in a loop, this is
1552 // problematic. Instead of just inlining the region, handle allocations by
1553 // hoisting fixed length allocations to the function entry and using
1554 // stacksave and restore for variable length ones.
1555 if (failed(inlineConvertOmpRegions(reductionDecls[i].getInitializerRegion(),
1556 "omp.reduction.neutral", builder,
1557 moduleTranslation, &phis)))
1558 return failure();
1559
1560 assert(phis.size() == 1 && "expected one value to be yielded from the "
1561 "reduction neutral element declaration region");
1562
1564
1565 if (isByRef[i]) {
1566 if (!reductionDecls[i].getAllocRegion().empty())
1567 // done in allocReductionVars
1568 continue;
1569
1570 // TODO: this path can be removed once all users of by-ref are updated to
1571 // use an alloc region
1572
1573 // Store the result of the inlined region to the allocated reduction var
1574 // ptr
1575 builder.CreateStore(phis[0], byRefVars[i]);
1576
1577 privateReductionVariables[i] = byRefVars[i];
1578 moduleTranslation.mapValue(reductionArgs[i], phis[0]);
1579 reductionVariableMap.try_emplace(op.getReductionVars()[i], phis[0]);
1580 } else {
1581 // for by-ref case the store is inside of the reduction region
1582 builder.CreateStore(phis[0], privateReductionVariables[i]);
1583 // the rest was handled in allocByValReductionVars
1584 }
1585
1586 // forget the mapping for the initializer region because we might need a
1587 // different mapping if this reduction declaration is re-used for a
1588 // different variable
1589 moduleTranslation.forgetMapping(reductionDecls[i].getInitializerRegion());
1590 }
1591
1592 return success();
1593}
1594
1595/// Collect reduction info
1596template <typename T>
1597static void collectReductionInfo(
1598 T loop, llvm::IRBuilderBase &builder,
1599 LLVM::ModuleTranslation &moduleTranslation,
1602 SmallVectorImpl<OwningAtomicReductionGen> &owningAtomicReductionGens,
1604 const ArrayRef<llvm::Value *> privateReductionVariables,
1606 ArrayRef<bool> isByRef) {
1607 unsigned numReductions = loop.getNumReductionVars();
1608
1609 for (unsigned i = 0; i < numReductions; ++i) {
1610 owningReductionGens.push_back(
1611 makeReductionGen(reductionDecls[i], builder, moduleTranslation));
1612 owningAtomicReductionGens.push_back(
1613 makeAtomicReductionGen(reductionDecls[i], builder, moduleTranslation));
1615 reductionDecls[i], builder, moduleTranslation, isByRef[i]));
1616 }
1617
1618 // Collect the reduction information.
1619 reductionInfos.reserve(numReductions);
1620 for (unsigned i = 0; i < numReductions; ++i) {
1621 llvm::OpenMPIRBuilder::ReductionGenAtomicCBTy atomicGen = nullptr;
1622 if (owningAtomicReductionGens[i])
1623 atomicGen = owningAtomicReductionGens[i];
1624 llvm::Value *variable =
1625 moduleTranslation.lookupValue(loop.getReductionVars()[i]);
1626 mlir::Type allocatedType;
1627 reductionDecls[i].getAllocRegion().walk([&](mlir::Operation *op) {
1628 if (auto alloca = mlir::dyn_cast<LLVM::AllocaOp>(op)) {
1629 allocatedType = alloca.getElemType();
1631 }
1632
1634 });
1635
1636 reductionInfos.push_back(
1637 {moduleTranslation.convertType(reductionDecls[i].getType()), variable,
1638 privateReductionVariables[i],
1639 /*EvaluationKind=*/llvm::OpenMPIRBuilder::EvalKind::Scalar,
1641 /*ReductionGenClang=*/nullptr, atomicGen,
1643 allocatedType ? moduleTranslation.convertType(allocatedType) : nullptr,
1644 reductionDecls[i].getByrefElementType()
1645 ? moduleTranslation.convertType(
1646 *reductionDecls[i].getByrefElementType())
1647 : nullptr});
1648 }
1649}
1650
1651/// handling of DeclareReductionOp's cleanup region
1652static LogicalResult
1654 llvm::ArrayRef<llvm::Value *> privateVariables,
1655 LLVM::ModuleTranslation &moduleTranslation,
1656 llvm::IRBuilderBase &builder, StringRef regionName,
1657 bool shouldLoadCleanupRegionArg = true) {
1658 for (auto [i, cleanupRegion] : llvm::enumerate(cleanupRegions)) {
1659 if (cleanupRegion->empty())
1660 continue;
1661
1662 // map the argument to the cleanup region
1663 Block &entry = cleanupRegion->front();
1664
1665 llvm::Instruction *potentialTerminator =
1666 builder.GetInsertBlock()->empty() ? nullptr
1667 : &builder.GetInsertBlock()->back();
1668 if (potentialTerminator && potentialTerminator->isTerminator())
1669 builder.SetInsertPoint(potentialTerminator);
1670 llvm::Value *privateVarValue =
1671 shouldLoadCleanupRegionArg
1672 ? builder.CreateLoad(
1673 moduleTranslation.convertType(entry.getArgument(0).getType()),
1674 privateVariables[i])
1675 : privateVariables[i];
1676
1677 moduleTranslation.mapValue(entry.getArgument(0), privateVarValue);
1678
1679 if (failed(inlineConvertOmpRegions(*cleanupRegion, regionName, builder,
1680 moduleTranslation)))
1681 return failure();
1682
1683 // clear block argument mapping in case it needs to be re-created with a
1684 // different source for another use of the same reduction decl
1685 moduleTranslation.forgetMapping(*cleanupRegion);
1686 }
1687 return success();
1688}
1689
1690// TODO: not used by ParallelOp
1691template <class OP>
1692static LogicalResult createReductionsAndCleanup(
1693 OP op, llvm::IRBuilderBase &builder,
1694 LLVM::ModuleTranslation &moduleTranslation,
1695 llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
1697 ArrayRef<llvm::Value *> privateReductionVariables, ArrayRef<bool> isByRef,
1698 bool isNowait = false, bool isTeamsReduction = false) {
1699 // Process the reductions if required.
1700 if (op.getNumReductionVars() == 0)
1701 return success();
1702
1704 SmallVector<OwningAtomicReductionGen> owningAtomicReductionGens;
1705 SmallVector<OwningDataPtrPtrReductionGen> owningReductionGenRefDataPtrGens;
1707
1708 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
1709
1710 // Create the reduction generators. We need to own them here because
1711 // ReductionInfo only accepts references to the generators.
1712 collectReductionInfo(op, builder, moduleTranslation, reductionDecls,
1713 owningReductionGens, owningAtomicReductionGens,
1714 owningReductionGenRefDataPtrGens,
1715 privateReductionVariables, reductionInfos, isByRef);
1716
1717 // The call to createReductions below expects the block to have a
1718 // terminator. Create an unreachable instruction to serve as terminator
1719 // and remove it later.
1720 llvm::UnreachableInst *tempTerminator = builder.CreateUnreachable();
1721 builder.SetInsertPoint(tempTerminator);
1722 llvm::OpenMPIRBuilder::InsertPointOrErrorTy contInsertPoint =
1723 ompBuilder->createReductions(builder, allocaIP, reductionInfos, isByRef,
1724 isNowait, isTeamsReduction);
1725
1726 if (failed(handleError(contInsertPoint, *op)))
1727 return failure();
1728
1729 if (!contInsertPoint->getBlock())
1730 return op->emitOpError() << "failed to convert reductions";
1731
1732 llvm::OpenMPIRBuilder::InsertPointTy afterIP = *contInsertPoint;
1733 if (!isTeamsReduction) {
1734 llvm::OpenMPIRBuilder::InsertPointOrErrorTy barrierIP =
1735 ompBuilder->createBarrier(*contInsertPoint, llvm::omp::OMPD_for);
1736
1737 if (failed(handleError(barrierIP, *op)))
1738 return failure();
1739 afterIP = *barrierIP;
1740 }
1741
1742 tempTerminator->eraseFromParent();
1743 builder.restoreIP(afterIP);
1744
1745 // after the construct, deallocate private reduction variables
1746 SmallVector<Region *> reductionRegions;
1747 llvm::transform(reductionDecls, std::back_inserter(reductionRegions),
1748 [](omp::DeclareReductionOp reductionDecl) {
1749 return &reductionDecl.getCleanupRegion();
1750 });
1751 LogicalResult result = inlineOmpRegionCleanup(
1752 reductionRegions, privateReductionVariables, moduleTranslation, builder,
1753 "omp.reduction.cleanup");
1754
1755 bool useDeviceSharedMem = omp::opInSharedDeviceContext(*op);
1756 if (useDeviceSharedMem) {
1757 for (auto [var, reductionDecl] :
1758 llvm::zip_equal(privateReductionVariables, reductionDecls))
1759 ompBuilder->createOMPFreeShared(
1760 builder, var, moduleTranslation.convertType(reductionDecl.getType()));
1761 }
1762
1763 return result;
1764}
1765
1766static ArrayRef<bool> getIsByRef(std::optional<ArrayRef<bool>> attr) {
1767 if (!attr)
1768 return {};
1769 return *attr;
1770}
1771
1772// TODO: not used by omp.parallel
1773template <typename OP>
1775 OP op, ArrayRef<BlockArgument> reductionArgs, llvm::IRBuilderBase &builder,
1776 LLVM::ModuleTranslation &moduleTranslation,
1777 llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
1779 SmallVectorImpl<llvm::Value *> &privateReductionVariables,
1780 DenseMap<Value, llvm::Value *> &reductionVariableMap,
1781 llvm::ArrayRef<bool> isByRef) {
1782 if (op.getNumReductionVars() == 0)
1783 return success();
1784
1785 SmallVector<DeferredStore> deferredStores;
1786
1787 if (failed(allocReductionVars(op, reductionArgs, builder, moduleTranslation,
1788 allocaIP, reductionDecls,
1789 privateReductionVariables, reductionVariableMap,
1790 deferredStores, isByRef)))
1791 return failure();
1792
1793 return initReductionVars(op, reductionArgs, builder, moduleTranslation,
1794 allocaIP.getBlock(), reductionDecls,
1795 privateReductionVariables, reductionVariableMap,
1796 isByRef, deferredStores);
1797}
1798
1799/// Return the llvm::Value * corresponding to the `privateVar` that
1800/// is being privatized. It isn't always as simple as looking up
1801/// moduleTranslation with privateVar. For instance, in case of
1802/// an allocatable, the descriptor for the allocatable is privatized.
1803/// This descriptor is mapped using an MapInfoOp. So, this function
1804/// will return a pointer to the llvm::Value corresponding to the
1805/// block argument for the mapped descriptor.
1806static llvm::Value *
1807findAssociatedValue(Value privateVar, llvm::IRBuilderBase &builder,
1808 LLVM::ModuleTranslation &moduleTranslation,
1809 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1810 if (mappedPrivateVars == nullptr || !mappedPrivateVars->contains(privateVar))
1811 return moduleTranslation.lookupValue(privateVar);
1812
1813 Value blockArg = (*mappedPrivateVars)[privateVar];
1814 Type privVarType = privateVar.getType();
1815 Type blockArgType = blockArg.getType();
1816 assert(isa<LLVM::LLVMPointerType>(blockArgType) &&
1817 "A block argument corresponding to a mapped var should have "
1818 "!llvm.ptr type");
1819
1820 if (privVarType == blockArgType)
1821 return moduleTranslation.lookupValue(blockArg);
1822
1823 // This typically happens when the privatized type is lowered from
1824 // boxchar<KIND> and gets lowered to !llvm.struct<(ptr, i64)>. That is the
1825 // struct/pair is passed by value. But, mapped values are passed only as
1826 // pointers, so before we privatize, we must load the pointer.
1827 if (!isa<LLVM::LLVMPointerType>(privVarType))
1828 return builder.CreateLoad(moduleTranslation.convertType(privVarType),
1829 moduleTranslation.lookupValue(blockArg));
1830
1831 return moduleTranslation.lookupValue(privateVar);
1832}
1833
1834// Privatizer region arguments may be by-value even when the available LLVM
1835// value is storage for that value, e.g. lowered Fortran boxchar descriptors in
1836// task context structs. Materialize the value expected by the region argument
1837// while preserving the existing pointer mapping for pointer arguments.
1838static llvm::Value *
1839materializeRegionArgValue(llvm::IRBuilderBase &builder,
1840 LLVM::ModuleTranslation &moduleTranslation,
1841 BlockArgument regionArg, llvm::Value *value) {
1842 if (!regionArg)
1843 return value;
1844
1845 llvm::Type *regionArgType =
1846 moduleTranslation.convertType(regionArg.getType());
1847 if (regionArgType->isPointerTy() || !value->getType()->isPointerTy())
1848 return value;
1849
1850 return builder.CreateLoad(regionArgType, value);
1851}
1852
1853/// Initialize a single (first)private variable. You probably want to use
1854/// allocateAndInitPrivateVars instead of this.
1855/// This returns the private variable which has been initialized. This
1856/// variable should be mapped before constructing the body of the Op.
1858initPrivateVar(llvm::IRBuilderBase &builder,
1859 LLVM::ModuleTranslation &moduleTranslation,
1860 omp::PrivateClauseOp &privDecl, llvm::Value *nonPrivateVar,
1861 BlockArgument &blockArg, llvm::Value *llvmPrivateVar,
1862 llvm::BasicBlock *privInitBlock,
1863 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1864 Region &initRegion = privDecl.getInitRegion();
1865 if (initRegion.empty())
1866 return llvmPrivateVar;
1867
1868 assert(nonPrivateVar);
1869 moduleTranslation.mapValue(privDecl.getInitMoldArg(), nonPrivateVar);
1870 moduleTranslation.mapValue(privDecl.getInitPrivateArg(), llvmPrivateVar);
1871
1872 // in-place convert the private initialization region
1874 if (failed(inlineConvertOmpRegions(initRegion, "omp.private.init", builder,
1875 moduleTranslation, &phis)))
1876 return llvm::createStringError(
1877 "failed to inline `init` region of `omp.private`");
1878
1879 assert(phis.size() == 1 && "expected one allocation to be yielded");
1880
1881 // clear init region block argument mapping in case it needs to be
1882 // re-created with a different source for another use of the same
1883 // reduction decl
1884 moduleTranslation.forgetMapping(initRegion);
1885
1886 // Prefer the value yielded from the init region to the allocated private
1887 // variable in case the region is operating on arguments by-value (e.g.
1888 // Fortran character boxes).
1889 return phis[0];
1890}
1891
1892/// Version of initPrivateVar which looks up the nonPrivateVar from mlirPrivVar.
1894 llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation,
1895 omp::PrivateClauseOp &privDecl, Value mlirPrivVar, BlockArgument &blockArg,
1896 llvm::Value *llvmPrivateVar, llvm::BasicBlock *privInitBlock,
1897 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1898 return initPrivateVar(
1899 builder, moduleTranslation, privDecl,
1900 findAssociatedValue(mlirPrivVar, builder, moduleTranslation,
1901 mappedPrivateVars),
1902 blockArg, llvmPrivateVar, privInitBlock, mappedPrivateVars);
1903}
1904
1905static llvm::Error
1906initPrivateVars(llvm::IRBuilderBase &builder,
1907 LLVM::ModuleTranslation &moduleTranslation,
1908 PrivateVarsInfo &privateVarsInfo,
1909 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1910 if (privateVarsInfo.blockArgs.empty())
1911 return llvm::Error::success();
1912
1913 llvm::BasicBlock *privInitBlock = splitBB(builder, true, "omp.private.init");
1914 setInsertPointForPossiblyEmptyBlock(builder, privInitBlock);
1915
1916 for (auto [idx, zip] : llvm::enumerate(llvm::zip_equal(
1917 privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
1918 privateVarsInfo.blockArgs, privateVarsInfo.llvmVars))) {
1919 auto [privDecl, mlirPrivVar, blockArg, llvmPrivateVar] = zip;
1921 builder, moduleTranslation, privDecl, mlirPrivVar, blockArg,
1922 llvmPrivateVar, privInitBlock, mappedPrivateVars);
1923
1924 if (!privVarOrErr)
1925 return privVarOrErr.takeError();
1926
1927 llvmPrivateVar = privVarOrErr.get();
1928 moduleTranslation.mapValue(blockArg, llvmPrivateVar);
1929
1931 }
1932
1933 return llvm::Error::success();
1934}
1935
1936/// Allocate and initialize delayed private variables. Returns the basic block
1937/// which comes after all of these allocations. llvm::Value * for each of these
1938/// private variables are populated in llvmPrivateVars.
1939template <typename T>
1941allocatePrivateVars(T op, llvm::IRBuilderBase &builder,
1942 LLVM::ModuleTranslation &moduleTranslation,
1943 PrivateVarsInfo &privateVarsInfo,
1944 const llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
1945 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1946 // Allocate private vars
1947 llvm::Instruction *allocaTerminator = allocaIP.getBlock()->getTerminator();
1948 splitBB(llvm::OpenMPIRBuilder::InsertPointTy(allocaIP.getBlock(),
1949 allocaTerminator->getIterator()),
1950 true, allocaTerminator->getStableDebugLoc(),
1951 "omp.region.after_alloca");
1952
1953 llvm::IRBuilderBase::InsertPointGuard guard(builder);
1954 // Update the allocaTerminator since the alloca block was split above.
1955 allocaTerminator = allocaIP.getBlock()->getTerminator();
1956 builder.SetInsertPoint(allocaTerminator);
1957 // The new terminator is an uncondition branch created by the splitBB above.
1958 assert(allocaTerminator->getNumSuccessors() == 1 &&
1959 "This is an unconditional branch created by splitBB");
1960
1961 llvm::DataLayout dataLayout = builder.GetInsertBlock()->getDataLayout();
1962 llvm::BasicBlock *afterAllocas = allocaTerminator->getSuccessor(0);
1963
1964 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
1965 bool mightUseDeviceSharedMem = omp::opInSharedDeviceContext(*op);
1966 unsigned int allocaAS =
1967 moduleTranslation.getLLVMModule()->getDataLayout().getAllocaAddrSpace();
1968 unsigned int defaultAS = moduleTranslation.getLLVMModule()
1969 ->getDataLayout()
1970 .getProgramAddressSpace();
1971
1972 SmallVector<int64_t> allocateItemForPrivate(privateVarsInfo.blockArgs.size(),
1973 -1);
1974 ValueRange allocatorVars;
1975 DenseI64ArrayAttr allocateAlignments;
1976 if constexpr (std::is_same_v<T, omp::ParallelOp>) {
1977 allocatorVars = op.getAllocatorVars();
1978 allocateAlignments = op.getAllocateAlignmentsAttr();
1979 if (auto privateIndices = op.getAllocatePrivateIndicesAttr())
1980 for (auto [allocateIndex, privateIndex] :
1981 llvm::enumerate(privateIndices.asArrayRef()))
1982 allocateItemForPrivate[privateIndex] = allocateIndex;
1983 }
1984
1985 for (auto [privateIndex, tuple] : llvm::enumerate(llvm::zip_equal(
1986 privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
1987 privateVarsInfo.blockArgs))) {
1988 auto [privDecl, mlirPrivVar, blockArg] = tuple;
1989 llvm::Type *llvmAllocType =
1990 moduleTranslation.convertType(privDecl.getType());
1991 builder.SetInsertPoint(allocaIP.getBlock()->getTerminator());
1992 llvm::Value *llvmPrivateVar = nullptr;
1993 int64_t allocateIndex = allocateItemForPrivate[privateIndex];
1994 if (allocateIndex >= 0) {
1995 if (mightUseDeviceSharedMem ||
1996 op->template getParentOfType<omp::TargetOp>())
1997 return llvm::createStringError(
1998 "allocate clause on a device parallel region is not supported");
1999 if (!llvmAllocType->isSized())
2000 return llvm::createStringError(
2001 "allocate clause private type must have a fixed size");
2002 llvm::TypeSize size = dataLayout.getTypeAllocSize(llvmAllocType);
2003 if (size.isScalable())
2004 return llvm::createStringError(
2005 "allocate clause private type must have a fixed size");
2006 llvm::IntegerType *sizeTy =
2007 moduleTranslation.getLLVMModule()->getDataLayout().getIntPtrType(
2008 moduleTranslation.getLLVMModule()->getContext());
2009 if (!llvm::isUIntN(sizeTy->getBitWidth(), size.getFixedValue()))
2010 return llvm::createStringError(
2011 "OpenMP allocation size cannot be represented by the target size "
2012 "type");
2013 llvm::Value *sizeValue =
2014 llvm::ConstantInt::get(sizeTy, size.getFixedValue());
2015
2016 Value allocatorVar = allocatorVars[allocateIndex];
2017 auto allocator = privateVarsInfo.convertedAllocators.find(allocatorVar);
2018 if (allocator == privateVarsInfo.convertedAllocators.end())
2019 return llvm::createStringError(
2020 "failed to find converted OpenMP allocator operand");
2021 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2022 int64_t alignment =
2023 allocateAlignments ? allocateAlignments[allocateIndex] : 0;
2024 if (alignment != 0) {
2025 // The allocation must be aligned to at least the maximum of the
2026 // requested alignment and the alignment the base language requires
2027 // for the type being allocated.
2028 uint64_t alignmentValue = std::max<uint64_t>(
2029 static_cast<uint64_t>(alignment),
2030 dataLayout.getABITypeAlign(llvmAllocType).value());
2031 if (!llvm::isUIntN(sizeTy->getBitWidth(), alignmentValue))
2032 return llvm::createStringError(
2033 "OpenMP allocation alignment cannot be represented by the "
2034 "target size type");
2035 llvmPrivateVar = ompBuilder->createOMPAlignedAlloc(
2036 ompLoc, llvm::ConstantInt::get(sizeTy, alignmentValue), sizeValue,
2037 allocator->second, "omp.private.alloc");
2038 } else {
2039 llvmPrivateVar = ompBuilder->createOMPAlloc(
2040 ompLoc, sizeValue, allocator->second, "omp.private.alloc");
2041 }
2042 if (!llvmPrivateVar)
2043 return llvm::createStringError(
2044 "failed to create OpenMP private allocation");
2045 privateVarsInfo.allocatorPrivates.push_back(
2046 {llvmPrivateVar, allocator->second});
2047 } else if (mightUseDeviceSharedMem &&
2049 llvmPrivateVar = ompBuilder->createOMPAllocShared(builder, llvmAllocType);
2050 } else {
2051 llvmPrivateVar = builder.CreateAlloca(
2052 llvmAllocType, /*ArraySize=*/nullptr, "omp.private.alloc");
2053 if (allocaAS != defaultAS)
2054 llvmPrivateVar = builder.CreateAddrSpaceCast(
2055 llvmPrivateVar, builder.getPtrTy(defaultAS));
2056 }
2057
2058 privateVarsInfo.llvmVars.push_back(llvmPrivateVar);
2059 }
2060
2061 return afterAllocas;
2062}
2063
2064/// This can't always be determined statically, but when we can, it is good to
2065/// avoid generating compiler-added barriers which will deadlock the program.
2067 for (mlir::Operation *parent = op->getParentOp(); parent != nullptr;
2068 parent = parent->getParentOp()) {
2069 if (mlir::isa<omp::SingleOp, omp::CriticalOp>(parent))
2070 return true;
2071
2072 // e.g.
2073 // omp.single {
2074 // omp.parallel {
2075 // op
2076 // }
2077 // }
2078 if (mlir::isa<omp::ParallelOp>(parent))
2079 return false;
2080 }
2081 return false;
2082}
2083
2084static LogicalResult copyFirstPrivateVars(
2085 mlir::Operation *op, llvm::IRBuilderBase &builder,
2086 LLVM::ModuleTranslation &moduleTranslation,
2088 ArrayRef<llvm::Value *> llvmPrivateVars,
2089 SmallVectorImpl<omp::PrivateClauseOp> &privateDecls, bool insertBarrier,
2090 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
2091 // Apply copy region for firstprivate.
2092 bool needsFirstprivate =
2093 llvm::any_of(privateDecls, [](omp::PrivateClauseOp &privOp) {
2094 return privOp.getDataSharingType() ==
2095 omp::DataSharingClauseType::FirstPrivate;
2096 });
2097
2098 if (!needsFirstprivate)
2099 return success();
2100
2101 llvm::BasicBlock *copyBlock =
2102 splitBB(builder, /*CreateBranch=*/true, "omp.private.copy");
2103 setInsertPointForPossiblyEmptyBlock(builder, copyBlock);
2104
2105 for (auto [decl, moldVar, llvmVar] :
2106 llvm::zip_equal(privateDecls, moldVars, llvmPrivateVars)) {
2107 if (decl.getDataSharingType() != omp::DataSharingClauseType::FirstPrivate)
2108 continue;
2109
2110 // copyRegion implements `lhs = rhs`
2111 Region &copyRegion = decl.getCopyRegion();
2112
2113 llvm::Value *copyMoldVar = materializeRegionArgValue(
2114 builder, moduleTranslation, decl.getCopyMoldArg(), moldVar);
2115 llvm::Value *copyPrivateVar = materializeRegionArgValue(
2116 builder, moduleTranslation, decl.getCopyPrivateArg(), llvmVar);
2117
2118 moduleTranslation.mapValue(decl.getCopyMoldArg(), copyMoldVar);
2119
2120 // map copyRegion lhs arg
2121 moduleTranslation.mapValue(decl.getCopyPrivateArg(), copyPrivateVar);
2122
2123 // in-place convert copy region
2124 if (failed(inlineConvertOmpRegions(copyRegion, "omp.private.copy", builder,
2125 moduleTranslation)))
2126 return decl.emitError("failed to inline `copy` region of `omp.private`");
2127
2129
2130 // ignore unused value yielded from copy region
2131
2132 // clear copy region block argument mapping in case it needs to be
2133 // re-created with different sources for reuse of the same reduction
2134 // decl
2135 moduleTranslation.forgetMapping(copyRegion);
2136 }
2137
2138 if (insertBarrier && !opIsInSingleThread(op)) {
2139 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
2140 llvm::OpenMPIRBuilder::InsertPointOrErrorTy res =
2141 ompBuilder->createBarrier(builder, llvm::omp::OMPD_barrier);
2142 if (failed(handleError(res, *op)))
2143 return failure();
2144 }
2145
2146 return success();
2147}
2148
2149static LogicalResult copyFirstPrivateVars(
2150 mlir::Operation *op, llvm::IRBuilderBase &builder,
2151 LLVM::ModuleTranslation &moduleTranslation,
2152 SmallVectorImpl<mlir::Value> &mlirPrivateVars,
2153 ArrayRef<llvm::Value *> llvmPrivateVars,
2154 SmallVectorImpl<omp::PrivateClauseOp> &privateDecls, bool insertBarrier,
2155 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
2156 llvm::SmallVector<llvm::Value *> moldVars(mlirPrivateVars.size());
2157 llvm::transform(mlirPrivateVars, moldVars.begin(), [&](mlir::Value mlirVar) {
2158 // map copyRegion rhs arg
2159 llvm::Value *moldVar = findAssociatedValue(
2160 mlirVar, builder, moduleTranslation, mappedPrivateVars);
2161 assert(moldVar);
2162 return moldVar;
2163 });
2164 return copyFirstPrivateVars(op, builder, moduleTranslation, moldVars,
2165 llvmPrivateVars, privateDecls, insertBarrier,
2166 mappedPrivateVars);
2167}
2168
2169template <typename T>
2170static LogicalResult
2171cleanupPrivateVars(T op, llvm::IRBuilderBase &builder,
2172 LLVM::ModuleTranslation &moduleTranslation, Location loc,
2173 PrivateVarsInfo &privateVarsInfo) {
2174 // private variable deallocation
2175 SmallVector<Region *> privateCleanupRegions;
2176 llvm::transform(privateVarsInfo.privatizers,
2177 std::back_inserter(privateCleanupRegions),
2178 [](omp::PrivateClauseOp privatizer) {
2179 return &privatizer.getDeallocRegion();
2180 });
2181
2182 if (failed(inlineOmpRegionCleanup(privateCleanupRegions,
2183 privateVarsInfo.llvmVars, moduleTranslation,
2184 builder, "omp.private.dealloc",
2185 /*shouldLoadCleanupRegionArg=*/false)))
2186 return mlir::emitError(loc, "failed to inline `dealloc` region of an "
2187 "`omp.private` op in");
2189
2190 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
2191 bool mightUseDeviceSharedMem = omp::opInSharedDeviceContext(*op);
2192 for (auto [privDecl, llvmPrivVar, blockArg] :
2193 llvm::zip_equal(privateVarsInfo.privatizers, privateVarsInfo.llvmVars,
2194 privateVarsInfo.blockArgs)) {
2195 if (mightUseDeviceSharedMem && omp::allocaUsesRequireSharedMem(blockArg)) {
2196 ompBuilder->createOMPFreeShared(
2197 builder, llvmPrivVar,
2198 moduleTranslation.convertType(privDecl.getType()));
2199 }
2200 }
2201
2202 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2203 for (const PrivateVarsInfo::AllocatorPrivateInfo &allocation :
2204 llvm::reverse(privateVarsInfo.allocatorPrivates))
2205 ompBuilder->createOMPFree(ompLoc, allocation.allocatedPtr,
2206 allocation.allocator);
2207
2208 return success();
2209}
2210
2211/// Returns true if the construct contains omp.cancel or omp.cancellation_point
2213 // omp.cancel and omp.cancellation_point must be "closely nested" so they will
2214 // be visible and not inside of function calls. This is enforced by the
2215 // verifier.
2216 return op
2217 ->walk([](Operation *child) {
2218 if (mlir::isa<omp::CancelOp, omp::CancellationPointOp>(child))
2219 return WalkResult::interrupt();
2220 return WalkResult::advance();
2221 })
2222 .wasInterrupted();
2223}
2224
2225// Forward declarations for the task-reduction helpers defined alongside the
2226// omp.taskgroup lowering further down in this file. These are shared by the
2227// `reduction(task, ...)` modifier lowering on the parallel/worksharing
2228// constructs and by the omp.taskgroup / omp.taskloop.context task_reduction
2229// lowering. When \p isModifier is set, `__kmpc_taskred_modifier_init` is
2230// emitted (opening a task-reduction scope) instead of `__kmpc_taskred_init`,
2231// with \p isWorksharing selecting the runtime `is_ws` argument.
2232static llvm::Value *emitTaskReductionInitCall(
2234 ArrayRef<llvm::Value *> origPtrs, StringRef helperNamePrefix,
2235 llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder::InsertPointTy allocaIP,
2236 LLVM::ModuleTranslation &moduleTranslation, bool isModifier = false,
2237 bool isWorksharing = false);
2238static void
2239emitTaskReductionModifierFini(bool isWorksharing, llvm::IRBuilderBase &builder,
2240 LLVM::ModuleTranslation &moduleTranslation);
2241
2242static LogicalResult
2243convertOmpSections(Operation &opInst, llvm::IRBuilderBase &builder,
2244 LLVM::ModuleTranslation &moduleTranslation) {
2245 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2246 using StorableBodyGenCallbackTy =
2247 llvm::OpenMPIRBuilder::StorableBodyGenCallbackTy;
2248
2249 auto sectionsOp = cast<omp::SectionsOp>(opInst);
2250
2251 if (failed(checkImplementationStatus(opInst)))
2252 return failure();
2253
2254 llvm::ArrayRef<bool> isByRef = getIsByRef(sectionsOp.getReductionByref());
2255 assert(isByRef.size() == sectionsOp.getNumReductionVars());
2256
2258 collectReductionDecls(sectionsOp, reductionDecls);
2259 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
2260 findAllocInsertPoints(builder, moduleTranslation);
2261
2262 SmallVector<llvm::Value *> privateReductionVariables(
2263 sectionsOp.getNumReductionVars());
2264 DenseMap<Value, llvm::Value *> reductionVariableMap;
2265
2266 MutableArrayRef<BlockArgument> reductionArgs =
2267 cast<omp::BlockArgOpenMPOpInterface>(opInst).getReductionBlockArgs();
2268
2270 sectionsOp, reductionArgs, builder, moduleTranslation, allocaIP,
2271 reductionDecls, privateReductionVariables, reductionVariableMap,
2272 isByRef)))
2273 return failure();
2274
2275 bool isTaskReductionMod =
2276 sectionsOp.getReductionMod() == omp::ReductionModifier::task &&
2277 sectionsOp.getNumReductionVars() > 0;
2278
2280
2281 for (Operation &op : *sectionsOp.getRegion().begin()) {
2282 auto sectionOp = dyn_cast<omp::SectionOp>(op);
2283 if (!sectionOp) // omp.terminator
2284 continue;
2285
2286 Region &region = sectionOp.getRegion();
2287 auto sectionCB = [&sectionsOp, &region, &builder, &moduleTranslation](
2288 InsertPointTy allocaIP, InsertPointTy codeGenIP,
2289 ArrayRef<llvm::BasicBlock *> deallocBlocks) {
2290 builder.restoreIP(codeGenIP);
2291
2292 // map the omp.section reduction block argument to the omp.sections block
2293 // arguments
2294 // TODO: this assumes that the only block arguments are reduction
2295 // variables
2296 assert(region.getNumArguments() ==
2297 sectionsOp.getRegion().getNumArguments());
2298 for (auto [sectionsArg, sectionArg] : llvm::zip_equal(
2299 sectionsOp.getRegion().getArguments(), region.getArguments())) {
2300 llvm::Value *llvmVal = moduleTranslation.lookupValue(sectionsArg);
2301 assert(llvmVal);
2302 moduleTranslation.mapValue(sectionArg, llvmVal);
2303 }
2304
2305 return convertOmpOpRegions(region, "omp.section.region", builder,
2306 moduleTranslation)
2307 .takeError();
2308 };
2309 sectionCBs.push_back(sectionCB);
2310 }
2311
2312 // No sections within omp.sections operation - skip generation. This situation
2313 // is only possible if there is only a terminator operation inside the
2314 // sections operation
2315 if (sectionCBs.empty())
2316 return success();
2317
2318 // For `reduction(task, ...)` open a task-reduction scope for the worksharing
2319 // region. Participating explicit tasks accumulate into the per-thread private
2320 // copies, which the worksharing reduction then combines across threads. This
2321 // is emitted only after the empty-sections early return above, so it stays
2322 // balanced with the matching fini emitted after the sections region.
2323 if (isTaskReductionMod &&
2324 !emitTaskReductionInitCall(reductionDecls, privateReductionVariables,
2325 "__omp_taskred_mod_", builder, allocaIP,
2326 moduleTranslation, /*isModifier=*/true,
2327 /*isWorksharing=*/true))
2328 return sectionsOp.emitError(
2329 "failed to emit task reduction modifier initialization");
2330
2331 assert(isa<omp::SectionOp>(*sectionsOp.getRegion().op_begin()));
2332
2333 // TODO: Perform appropriate actions according to the data-sharing
2334 // attribute (shared, private, firstprivate, ...) of variables.
2335 // Currently defaults to shared.
2336 auto privCB = [&](InsertPointTy, InsertPointTy codeGenIP, llvm::Value &,
2337 llvm::Value &vPtr, llvm::Value *&replacementValue)
2338 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
2339 replacementValue = &vPtr;
2340 return codeGenIP;
2341 };
2342
2343 // TODO: Perform finalization actions for variables. This has to be
2344 // called for variables which have destructors/finalizers.
2345 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
2346
2347 allocaIP = findAllocInsertPoints(builder, moduleTranslation);
2348 bool isCancellable = constructIsCancellable(sectionsOp);
2349 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2350 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2351 moduleTranslation.getOpenMPBuilder()->createSections(
2352 ompLoc, allocaIP, sectionCBs, privCB, finiCB, isCancellable,
2353 sectionsOp.getNowait());
2354
2355 if (failed(handleError(afterIP, opInst)))
2356 return failure();
2357
2358 builder.restoreIP(*afterIP);
2359
2360 // Close the task-reduction scope before combining the worksharing copies.
2361 if (isTaskReductionMod)
2362 emitTaskReductionModifierFini(/*isWorksharing=*/true, builder,
2363 moduleTranslation);
2364
2365 // Process the reductions if required.
2367 sectionsOp, builder, moduleTranslation, allocaIP, reductionDecls,
2368 privateReductionVariables, isByRef, sectionsOp.getNowait());
2369}
2370
2371/// Converts an OpenMP scope construct into LLVM IR.
2372static LogicalResult
2373convertOmpScope(omp::ScopeOp &scopeOp, llvm::IRBuilderBase &builder,
2374 LLVM::ModuleTranslation &moduleTranslation) {
2375 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2376 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
2377
2378 if (failed(checkImplementationStatus(*scopeOp)))
2379 return failure();
2380
2381 llvm::ArrayRef<bool> isByRef = getIsByRef(scopeOp.getReductionByref());
2382 assert(isByRef.size() == scopeOp.getNumReductionVars());
2383
2384 PrivateVarsInfo privateVarsInfo(scopeOp);
2385
2387 collectReductionDecls(scopeOp, reductionDecls);
2388 InsertPointTy allocaIP = findAllocInsertPoints(builder, moduleTranslation);
2389
2390 SmallVector<llvm::Value *> privateReductionVariables(
2391 scopeOp.getNumReductionVars());
2392 DenseMap<Value, llvm::Value *> reductionVariableMap;
2393
2394 MutableArrayRef<BlockArgument> reductionArgs =
2395 cast<omp::BlockArgOpenMPOpInterface>(*scopeOp).getReductionBlockArgs();
2396
2397 // Allocate private vars before the scope body
2399 scopeOp, builder, moduleTranslation, privateVarsInfo, allocaIP);
2400 if (failed(handleError(afterAllocas, *scopeOp)))
2401 return failure();
2402
2404 scopeOp, reductionArgs, builder, moduleTranslation, allocaIP,
2405 reductionDecls, privateReductionVariables, reductionVariableMap,
2406 isByRef)))
2407 return failure();
2408
2409 auto bodyCB =
2410 [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
2411 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
2412 builder.restoreIP(codeGenIP);
2413
2414 if (handleError(
2415 initPrivateVars(builder, moduleTranslation, privateVarsInfo),
2416 *scopeOp)
2417 .failed())
2418 return llvm::make_error<PreviouslyReportedError>();
2419
2420 if (failed(copyFirstPrivateVars(
2421 scopeOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
2422 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
2423 scopeOp.getPrivateNeedsBarrier())))
2424 return llvm::make_error<PreviouslyReportedError>();
2425
2426 return convertOmpOpRegions(scopeOp.getRegion(), "omp.scope.region", builder,
2427 moduleTranslation)
2428 .takeError();
2429 };
2430
2431 auto finiCB = [&](InsertPointTy codeGenIP) -> llvm::Error {
2432 InsertPointTy oldIP = builder.saveIP();
2433 builder.restoreIP(codeGenIP);
2434 if (failed(cleanupPrivateVars(scopeOp, builder, moduleTranslation,
2435 scopeOp.getLoc(), privateVarsInfo)))
2436 return llvm::make_error<PreviouslyReportedError>();
2437 builder.restoreIP(oldIP);
2438 return llvm::Error::success();
2439 };
2440
2441 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2442 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2443 ompBuilder->createScope(ompLoc, bodyCB, finiCB, scopeOp.getNowait());
2444
2445 if (failed(handleError(afterIP, *scopeOp)))
2446 return failure();
2447
2448 builder.restoreIP(*afterIP);
2449
2450 // Process the reductions if required.
2452 scopeOp, builder, moduleTranslation, allocaIP, reductionDecls,
2453 privateReductionVariables, isByRef, scopeOp.getNowait(),
2454 /*isTeamsReduction=*/false);
2455}
2456
2457/// Converts an OpenMP single construct into LLVM IR using OpenMPIRBuilder.
2458static LogicalResult
2459convertOmpSingle(omp::SingleOp &singleOp, llvm::IRBuilderBase &builder,
2460 LLVM::ModuleTranslation &moduleTranslation) {
2461 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2462 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2463
2464 if (failed(checkImplementationStatus(*singleOp)))
2465 return failure();
2466
2467 auto bodyCB = [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
2468 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) {
2469 builder.restoreIP(codegenIP);
2470 return convertOmpOpRegions(singleOp.getRegion(), "omp.single.region",
2471 builder, moduleTranslation)
2472 .takeError();
2473 };
2474 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
2475
2476 // Handle copyprivate
2477 Operation::operand_range cpVars = singleOp.getCopyprivateVars();
2478 std::optional<ArrayAttr> cpFuncs = singleOp.getCopyprivateSyms();
2481 for (size_t i = 0, e = cpVars.size(); i < e; ++i) {
2482 llvmCPVars.push_back(moduleTranslation.lookupValue(cpVars[i]));
2484 singleOp, cast<SymbolRefAttr>((*cpFuncs)[i]));
2485 llvmCPFuncs.push_back(
2486 moduleTranslation.lookupFunction(llvmFuncOp.getName()));
2487 }
2488
2489 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2490 moduleTranslation.getOpenMPBuilder()->createSingle(
2491 ompLoc, bodyCB, finiCB, singleOp.getNowait(), llvmCPVars,
2492 llvmCPFuncs);
2493
2494 if (failed(handleError(afterIP, *singleOp)))
2495 return failure();
2496
2497 builder.restoreIP(*afterIP);
2498 return success();
2499}
2500
2501static omp::DistributeOp
2503 // Early return if we found more than one distribute op or if we can't find
2504 // any distribute op in the teams region.
2505 omp::DistributeOp distOp;
2506 WalkResult walk = teamsOp.getRegion().walk([&](omp::DistributeOp op) {
2507 if (distOp)
2508 return WalkResult::interrupt();
2509 distOp = op;
2510 return WalkResult::skip();
2511 });
2512 if (walk.wasInterrupted() || !distOp)
2513 return {};
2514
2515 auto iface =
2516 llvm::cast<mlir::omp::BlockArgOpenMPOpInterface>(teamsOp.getOperation());
2517 // Check that all uses of the reduction block arg has the same distribute op
2518 // parent.
2520 for (auto ra : iface.getReductionBlockArgs())
2521 for (auto &use : ra.getUses()) {
2522 auto *useOp = use.getOwner();
2523 // Ignore debug uses.
2524 if (mlir::isa<LLVM::DbgDeclareOp, LLVM::DbgValueOp>(useOp)) {
2525 debugUses.push_back(useOp);
2526 continue;
2527 }
2528 if (!distOp->isProperAncestor(useOp))
2529 return {};
2530 }
2531
2532 // If we are going to use distribute reduction then remove any debug uses of
2533 // the reduction parameters in teamsOp. Otherwise they will be left without
2534 // any mapped value in moduleTranslation and will eventually error out.
2535 for (auto *use : debugUses)
2536 use->erase();
2537 return distOp;
2538}
2539
2540// Convert an OpenMP Teams construct to LLVM IR using OpenMPIRBuilder
2541static LogicalResult
2542convertOmpTeams(omp::TeamsOp op, llvm::IRBuilderBase &builder,
2543 LLVM::ModuleTranslation &moduleTranslation) {
2544 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2545 if (failed(checkImplementationStatus(*op)))
2546 return failure();
2547
2548 DenseMap<Value, llvm::Value *> reductionVariableMap;
2549 unsigned numReductionVars = op.getNumReductionVars();
2551 SmallVector<llvm::Value *> privateReductionVariables(numReductionVars);
2552 llvm::ArrayRef<bool> isByRef;
2553 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
2554 findAllocInsertPoints(builder, moduleTranslation);
2555
2556 // Only do teams reduction if there is no distribute op that captures the
2557 // reduction instead.
2558 bool doTeamsReduction = !getDistributeCapturingTeamsReduction(op);
2559 if (doTeamsReduction) {
2560 isByRef = getIsByRef(op.getReductionByref());
2561
2562 assert(isByRef.size() == op.getNumReductionVars());
2563
2564 MutableArrayRef<BlockArgument> reductionArgs =
2565 llvm::cast<omp::BlockArgOpenMPOpInterface>(*op).getReductionBlockArgs();
2566
2567 collectReductionDecls(op, reductionDecls);
2568
2570 op, reductionArgs, builder, moduleTranslation, allocaIP,
2571 reductionDecls, privateReductionVariables, reductionVariableMap,
2572 isByRef)))
2573 return failure();
2574 }
2575
2576 auto bodyCB = [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
2577 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) {
2579 moduleTranslation, allocaIP, deallocBlocks);
2580 builder.restoreIP(codegenIP);
2581 return convertOmpOpRegions(op.getRegion(), "omp.teams.region", builder,
2582 moduleTranslation)
2583 .takeError();
2584 };
2585
2586 llvm::Value *numTeamsLower = nullptr;
2587 if (Value numTeamsLowerVar = op.getNumTeamsLower())
2588 numTeamsLower = moduleTranslation.lookupValue(numTeamsLowerVar);
2589
2590 llvm::Value *numTeamsUpper = nullptr;
2591 if (!op.getNumTeamsUpperVars().empty())
2592 numTeamsUpper = moduleTranslation.lookupValue(op.getNumTeams(0));
2593
2594 llvm::Value *threadLimit = nullptr;
2595 if (!op.getThreadLimitVars().empty())
2596 threadLimit = moduleTranslation.lookupValue(op.getThreadLimit(0));
2597
2598 llvm::Value *ifExpr = nullptr;
2599 if (Value ifVar = op.getIfExpr())
2600 ifExpr = moduleTranslation.lookupValue(ifVar);
2601
2602 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2603 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2604 moduleTranslation.getOpenMPBuilder()->createTeams(
2605 ompLoc, bodyCB, numTeamsLower, numTeamsUpper, threadLimit, ifExpr);
2606
2607 if (failed(handleError(afterIP, *op)))
2608 return failure();
2609
2610 builder.restoreIP(*afterIP);
2611 if (doTeamsReduction) {
2612 // Process the reductions if required.
2614 op, builder, moduleTranslation, allocaIP, reductionDecls,
2615 privateReductionVariables, isByRef,
2616 /*isNoWait*/ false, /*isTeamsReduction*/ true);
2617 }
2618 return success();
2619}
2620
2621static llvm::omp::RTLDependenceKindTy
2622convertDependKind(mlir::omp::ClauseTaskDepend kind) {
2623 switch (kind) {
2624 case mlir::omp::ClauseTaskDepend::taskdependin:
2625 return llvm::omp::RTLDependenceKindTy::DepIn;
2626 // The OpenMP runtime requires that the codegen for 'depend' clause for
2627 // 'out' dependency kind must be the same as codegen for 'depend' clause
2628 // with 'inout' dependency.
2629 case mlir::omp::ClauseTaskDepend::taskdependout:
2630 case mlir::omp::ClauseTaskDepend::taskdependinout:
2631 return llvm::omp::RTLDependenceKindTy::DepInOut;
2632 case mlir::omp::ClauseTaskDepend::taskdependmutexinoutset:
2633 return llvm::omp::RTLDependenceKindTy::DepMutexInOutSet;
2634 case mlir::omp::ClauseTaskDepend::taskdependinoutset:
2635 return llvm::omp::RTLDependenceKindTy::DepInOutSet;
2636 }
2637 llvm_unreachable("unhandled depend kind");
2638}
2639
2641 std::optional<ArrayAttr> dependKinds, OperandRange dependVars,
2642 LLVM::ModuleTranslation &moduleTranslation,
2644 if (dependVars.empty())
2645 return;
2646 for (auto dep : llvm::zip(dependVars, dependKinds->getValue())) {
2647 auto kind =
2648 cast<mlir::omp::ClauseTaskDependAttr>(std::get<1>(dep)).getValue();
2649 llvm::omp::RTLDependenceKindTy type = convertDependKind(kind);
2650 llvm::Value *depVal = moduleTranslation.lookupValue(std::get<0>(dep));
2651 llvm::OpenMPIRBuilder::DependData dd(type, depVal->getType(), depVal);
2652 dds.emplace_back(dd);
2653 }
2654}
2655
2656/// Shared implementation of a callback which adds a termiator for the new block
2657/// created for the branch taken when an openmp construct is cancelled. The
2658/// terminator is saved in \p cancelTerminators. This callback is invoked only
2659/// if there is cancellation inside of the taskgroup body.
2660/// The terminator will need to be fixed to branch to the correct block to
2661/// cleanup the construct.
2663 SmallVectorImpl<llvm::UncondBrInst *> &cancelTerminators,
2664 llvm::IRBuilderBase &llvmBuilder, llvm::OpenMPIRBuilder &ompBuilder,
2665 mlir::Operation *op, llvm::omp::Directive cancelDirective) {
2666 auto finiCB = [&](llvm::OpenMPIRBuilder::InsertPointTy ip) -> llvm::Error {
2667 llvm::IRBuilderBase::InsertPointGuard guard(llvmBuilder);
2668
2669 // ip is currently in the block branched to if cancellation occurred.
2670 // We need to create a branch to terminate that block.
2671 llvmBuilder.restoreIP(ip);
2672
2673 // We must still clean up the construct after cancelling it, so we need to
2674 // branch to the block that finalizes the taskgroup.
2675 // That block has not been created yet so use this block as a dummy for now
2676 // and fix this after creating the operation.
2677 cancelTerminators.push_back(llvmBuilder.CreateBr(ip.getBlock()));
2678 return llvm::Error::success();
2679 };
2680 // We have to add the cleanup to the OpenMPIRBuilder before the body gets
2681 // created in case the body contains omp.cancel (which will then expect to be
2682 // able to find this cleanup callback).
2683 ompBuilder.pushFinalizationCB(
2684 {finiCB, cancelDirective, constructIsCancellable(op)});
2685}
2686
2687/// If we cancelled the construct, we should branch to the finalization block of
2688/// that construct. OMPIRBuilder structures the CFG such that the cleanup block
2689/// is immediately before the continuation block. Now this finalization has
2690/// been created we can fix the branch.
2691static void
2693 llvm::OpenMPIRBuilder &ompBuilder,
2694 const llvm::OpenMPIRBuilder::InsertPointTy &afterIP) {
2695 ompBuilder.popFinalizationCB();
2696 llvm::BasicBlock *constructFini = afterIP.getBlock()->getSinglePredecessor();
2697 for (llvm::UncondBrInst *cancelBranch : cancelTerminators)
2698 cancelBranch->setSuccessor(constructFini);
2699}
2700
2701namespace {
2702/// TaskContextStructManager takes care of creating and freeing a structure
2703/// containing information needed by the task body to execute.
2704class TaskContextStructManager {
2705public:
2706 TaskContextStructManager(llvm::IRBuilderBase &builder,
2707 LLVM::ModuleTranslation &moduleTranslation,
2708 MutableArrayRef<omp::PrivateClauseOp> privateDecls)
2709 : builder{builder}, moduleTranslation{moduleTranslation},
2710 privateDecls{privateDecls} {}
2711
2712 /// Creates a heap allocated struct containing space for each private
2713 /// variable. Invariant: privateVarTypes, privateDecls, and the elements of
2714 /// the structure should all have the same order (although privateDecls which
2715 /// do not read from the mold argument are skipped).
2716 void generateTaskContextStruct();
2717
2718 /// Create GEPs to access each member of the structure representing a private
2719 /// variable, adding them to llvmPrivateVars. Null values are added where
2720 /// private decls were skipped so that the ordering continues to match the
2721 /// private decls.
2722 void createGEPsToPrivateVars();
2723
2724 /// Given the address of the structure, return a GEP for each private variable
2725 /// in the structure. Null values are added where private decls were skipped
2726 /// so that the ordering continues to match the private decls.
2727 /// Must be called after generateTaskContextStruct().
2728 SmallVector<llvm::Value *>
2729 createGEPsToPrivateVars(llvm::Value *altStructPtr) const;
2730
2731 /// De-allocate the task context structure.
2732 void freeStructPtr();
2733
2734 MutableArrayRef<llvm::Value *> getLLVMPrivateVarGEPs() {
2735 return llvmPrivateVarGEPs;
2736 }
2737
2738 llvm::Value *getStructPtr() { return structPtr; }
2739
2740private:
2741 llvm::IRBuilderBase &builder;
2742 LLVM::ModuleTranslation &moduleTranslation;
2743 MutableArrayRef<omp::PrivateClauseOp> privateDecls;
2744
2745 /// The type of each member of the structure, in order.
2746 SmallVector<llvm::Type *> privateVarTypes;
2747
2748 /// LLVM values for each private variable, or null if that private variable is
2749 /// not included in the task context structure
2750 SmallVector<llvm::Value *> llvmPrivateVarGEPs;
2751
2752 /// A pointer to the structure containing context for this task.
2753 llvm::Value *structPtr = nullptr;
2754 /// The type of the structure
2755 llvm::Type *structTy = nullptr;
2756};
2757
2758/// IteratorInfo extracts and prepares loop bounds information from an
2759/// mlir::omp::IteratorOp for lowering to LLVM IR.
2760///
2761/// It computes the per-dimension trip counts and the total linearized trip
2762/// count, casted to i64. These are used to build a canonical loop and to
2763/// reconstruct the physical induction variables inside the loop body.
2764class IteratorInfo {
2765private:
2766 llvm::SmallVector<llvm::Value *> lowerBounds;
2767 llvm::SmallVector<llvm::Value *> upperBounds;
2768 llvm::SmallVector<llvm::Value *> steps;
2769 llvm::SmallVector<llvm::Value *> trips;
2770 unsigned dims;
2771 llvm::Value *totalTrips;
2772
2773 llvm::Value *lookUpAsI64(mlir::Value val, const LLVM::ModuleTranslation &mt,
2774 llvm::IRBuilderBase &builder) {
2775 llvm::Value *v = mt.lookupValue(val);
2776 if (!v)
2777 return nullptr;
2778 if (v->getType()->isIntegerTy(64))
2779 return v;
2780 if (v->getType()->isIntegerTy())
2781 return builder.CreateSExtOrTrunc(v, builder.getInt64Ty());
2782 return nullptr;
2783 }
2784
2785public:
2786 IteratorInfo(mlir::omp::IteratorOp itersOp,
2787 mlir::LLVM::ModuleTranslation &moduleTranslation,
2788 llvm::IRBuilderBase &builder) {
2789 dims = itersOp.getLoopLowerBounds().size();
2790 lowerBounds.resize(dims);
2791 upperBounds.resize(dims);
2792 steps.resize(dims);
2793 trips.resize(dims);
2794
2795 for (unsigned d = 0; d < dims; ++d) {
2796 llvm::Value *lb = lookUpAsI64(itersOp.getLoopLowerBounds()[d],
2797 moduleTranslation, builder);
2798 llvm::Value *ub = lookUpAsI64(itersOp.getLoopUpperBounds()[d],
2799 moduleTranslation, builder);
2800 llvm::Value *st =
2801 lookUpAsI64(itersOp.getLoopSteps()[d], moduleTranslation, builder);
2802 assert(lb && ub && st &&
2803 "Expect lowerBounds, upperBounds, and steps in IteratorOp");
2804 assert((!llvm::isa<llvm::ConstantInt>(st) ||
2805 !llvm::cast<llvm::ConstantInt>(st)->isZero()) &&
2806 "Expect non-zero step in IteratorOp");
2807
2808 lowerBounds[d] = lb;
2809 upperBounds[d] = ub;
2810 steps[d] = st;
2811
2812 // trips = ((ub - lb) / step) + 1 (inclusive ub, assume positive step)
2813 llvm::Value *diff = builder.CreateSub(ub, lb);
2814 llvm::Value *div = builder.CreateSDiv(diff, st);
2815 trips[d] = builder.CreateAdd(
2816 div, llvm::ConstantInt::get(builder.getInt64Ty(), 1));
2817 }
2818
2819 totalTrips = llvm::ConstantInt::get(builder.getInt64Ty(), 1);
2820 for (unsigned d = 0; d < dims; ++d)
2821 totalTrips = builder.CreateMul(totalTrips, trips[d]);
2822 }
2823
2824 unsigned getDims() const { return dims; }
2825 llvm::ArrayRef<llvm::Value *> getLowerBounds() const { return lowerBounds; }
2826 llvm::ArrayRef<llvm::Value *> getUpperBounds() const { return upperBounds; }
2827 llvm::ArrayRef<llvm::Value *> getSteps() const { return steps; }
2828 llvm::ArrayRef<llvm::Value *> getTrips() const { return trips; }
2829 llvm::Value *getTotalTrips() const { return totalTrips; }
2830};
2831
2832} // namespace
2833
2834void TaskContextStructManager::generateTaskContextStruct() {
2835 if (privateDecls.empty())
2836 return;
2837 privateVarTypes.reserve(privateDecls.size());
2838
2839 for (omp::PrivateClauseOp &privOp : privateDecls) {
2840 // Skip private variables which can safely be allocated and initialised
2841 // inside of the task
2842 if (!privOp.readsFromMold())
2843 continue;
2844 Type mlirType = privOp.getType();
2845 privateVarTypes.push_back(moduleTranslation.convertType(mlirType));
2846 }
2847
2848 if (privateVarTypes.empty())
2849 return;
2850
2851 structTy = llvm::StructType::get(moduleTranslation.getLLVMContext(),
2852 privateVarTypes);
2853
2854 llvm::DataLayout dataLayout =
2855 builder.GetInsertBlock()->getModule()->getDataLayout();
2856 llvm::Type *intPtrTy = builder.getIntPtrTy(dataLayout);
2857 llvm::Constant *allocSize = llvm::ConstantExpr::getSizeOf(structTy);
2858
2859 // Heap allocate the structure
2860 structPtr = builder.CreateMalloc(intPtrTy, structTy, allocSize,
2861 /*ArraySize=*/nullptr, /*MallocF=*/nullptr,
2862 "omp.task.context_ptr");
2863}
2864
2865SmallVector<llvm::Value *> TaskContextStructManager::createGEPsToPrivateVars(
2866 llvm::Value *altStructPtr) const {
2867 SmallVector<llvm::Value *> ret;
2868
2869 // Create GEPs for each struct member
2870 ret.reserve(privateDecls.size());
2871 llvm::Value *zero = builder.getInt32(0);
2872 unsigned i = 0;
2873 for (auto privDecl : privateDecls) {
2874 if (!privDecl.readsFromMold()) {
2875 // Handle this inside of the task so we don't pass unnessecary vars in
2876 ret.push_back(nullptr);
2877 continue;
2878 }
2879 llvm::Value *iVal = builder.getInt32(i);
2880 llvm::Value *gep = builder.CreateGEP(structTy, altStructPtr, {zero, iVal});
2881 ret.push_back(gep);
2882 i += 1;
2883 }
2884 return ret;
2885}
2886
2887void TaskContextStructManager::createGEPsToPrivateVars() {
2888 if (!structPtr)
2889 assert(privateVarTypes.empty());
2890 // Still need to run createGEPsToPrivateVars to populate llvmPrivateVarGEPs
2891 // with null values for skipped private decls
2892
2893 llvmPrivateVarGEPs = createGEPsToPrivateVars(structPtr);
2894}
2895
2896void TaskContextStructManager::freeStructPtr() {
2897 if (!structPtr)
2898 return;
2899
2900 llvm::IRBuilderBase::InsertPointGuard guard{builder};
2901 // Ensure we don't put the call to free() after the terminator
2902 builder.SetInsertPoint(builder.GetInsertBlock()->getTerminator());
2903 builder.CreateFree(structPtr);
2904}
2905
2906static void storeAffinityEntry(llvm::IRBuilderBase &builder,
2907 llvm::OpenMPIRBuilder &ompBuilder,
2908 llvm::Value *affinityList, llvm::Value *index,
2909 llvm::Value *addr, llvm::Value *len) {
2910 llvm::StructType *kmpTaskAffinityInfoTy =
2911 ompBuilder.getKmpTaskAffinityInfoTy();
2912 llvm::Value *entry = builder.CreateInBoundsGEP(
2913 kmpTaskAffinityInfoTy, affinityList, index, "omp.affinity.entry");
2914
2915 addr = builder.CreatePtrToInt(addr, kmpTaskAffinityInfoTy->getElementType(0));
2916 len = builder.CreateIntCast(len, kmpTaskAffinityInfoTy->getElementType(1),
2917 /*isSigned=*/false);
2918 llvm::Value *flags = builder.getInt32(0);
2919
2920 builder.CreateStore(addr,
2921 builder.CreateStructGEP(kmpTaskAffinityInfoTy, entry, 0));
2922 builder.CreateStore(len,
2923 builder.CreateStructGEP(kmpTaskAffinityInfoTy, entry, 1));
2924 builder.CreateStore(flags,
2925 builder.CreateStructGEP(kmpTaskAffinityInfoTy, entry, 2));
2926}
2927
2929 llvm::IRBuilderBase &builder,
2930 LLVM::ModuleTranslation &moduleTranslation,
2931 llvm::Value *affinityList) {
2932 for (auto [i, affinityVar] : llvm::enumerate(affinityVars)) {
2933 auto entryOp = affinityVar.getDefiningOp<mlir::omp::AffinityEntryOp>();
2934 assert(entryOp && "affinity item must be omp.affinity_entry");
2935
2936 llvm::Value *addr = moduleTranslation.lookupValue(entryOp.getAddr());
2937 llvm::Value *len = moduleTranslation.lookupValue(entryOp.getLen());
2938 assert(addr && len && "expect affinity addr and len to be non-null");
2939 storeAffinityEntry(builder, *moduleTranslation.getOpenMPBuilder(),
2940 affinityList, builder.getInt64(i), addr, len);
2941 }
2942}
2943
2944static mlir::LogicalResult
2945convertIteratorRegion(llvm::Value *linearIV, IteratorInfo &iterInfo,
2946 mlir::Block &iteratorRegionBlock,
2947 llvm::IRBuilderBase &builder,
2948 LLVM::ModuleTranslation &moduleTranslation) {
2949 llvm::Value *tmp = linearIV;
2950 for (int d = (int)iterInfo.getDims() - 1; d >= 0; --d) {
2951 llvm::Value *trip = iterInfo.getTrips()[d];
2952 // idx_d = tmp % trip_d
2953 llvm::Value *idx = builder.CreateURem(tmp, trip);
2954 // tmp = tmp / trip_d
2955 tmp = builder.CreateUDiv(tmp, trip);
2956
2957 // physIV_d = lb_d + idx_d * step_d
2958 llvm::Value *physIV = builder.CreateAdd(
2959 iterInfo.getLowerBounds()[d],
2960 builder.CreateMul(idx, iterInfo.getSteps()[d]), "omp.it.phys_iv");
2961
2962 moduleTranslation.mapValue(iteratorRegionBlock.getArgument(d), physIV);
2963 }
2964
2965 // Translate the iterator region into the loop body.
2966 moduleTranslation.mapBlock(&iteratorRegionBlock, builder.GetInsertBlock());
2967 if (mlir::failed(moduleTranslation.convertBlock(iteratorRegionBlock,
2968 /*ignoreArguments=*/true,
2969 builder))) {
2970 return mlir::failure();
2971 }
2972 return mlir::success();
2973}
2974
2976 llvm::function_ref<void(llvm::Value *linearIV, mlir::omp::YieldOp yield)>;
2977
2978static mlir::LogicalResult
2979fillIteratorLoop(mlir::omp::IteratorOp itersOp, llvm::IRBuilderBase &builder,
2980 mlir::LLVM::ModuleTranslation &moduleTranslation,
2981 IteratorInfo &iterInfo, llvm::StringRef loopName,
2982 IteratorStoreEntryTy genStoreEntry) {
2983 mlir::Region &itersRegion = itersOp.getRegion();
2984 mlir::Block &iteratorRegionBlock = itersRegion.front();
2985
2986 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
2987
2988 auto bodyGen = [&](llvm::OpenMPIRBuilder::InsertPointTy bodyIP,
2989 llvm::Value *linearIV) -> llvm::Error {
2990 llvm::IRBuilderBase::InsertPointGuard guard(builder);
2991 builder.restoreIP(bodyIP);
2992
2993 if (failed(convertIteratorRegion(linearIV, iterInfo, iteratorRegionBlock,
2994 builder, moduleTranslation))) {
2995 return llvm::make_error<llvm::StringError>(
2996 "failed to convert iterator region", llvm::inconvertibleErrorCode());
2997 }
2998
2999 auto yield =
3000 mlir::dyn_cast<mlir::omp::YieldOp>(iteratorRegionBlock.getTerminator());
3001 assert(yield && yield.getResults().size() == 1 &&
3002 "expect omp.yield in iterator region to have one result");
3003
3004 genStoreEntry(linearIV, yield);
3005
3006 // Iterator-region block/value mappings are temporary for this conversion,
3007 // clear them to avoid stale entries in ModuleTranslation.
3008 moduleTranslation.forgetMapping(itersRegion);
3009
3010 return llvm::Error::success();
3011 };
3012
3013 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
3014 moduleTranslation.getOpenMPBuilder()->createIteratorLoop(
3015 loc, iterInfo.getTotalTrips(), bodyGen, loopName);
3016 if (failed(handleError(afterIP, *itersOp)))
3017 return failure();
3018
3019 builder.restoreIP(*afterIP);
3020
3021 return mlir::success();
3022}
3023
3024static mlir::LogicalResult
3025buildAffinityData(mlir::omp::TaskOp &taskOp, llvm::IRBuilderBase &builder,
3026 mlir::LLVM::ModuleTranslation &moduleTranslation,
3027 llvm::OpenMPIRBuilder::AffinityData &ad) {
3028
3029 if (taskOp.getAffinityVars().empty() && taskOp.getIterated().empty()) {
3030 ad.Count = nullptr;
3031 ad.Info = nullptr;
3032 return mlir::success();
3033 }
3034
3036 llvm::StructType *kmpTaskAffinityInfoTy =
3037 moduleTranslation.getOpenMPBuilder()->getKmpTaskAffinityInfoTy();
3038
3039 auto allocateAffinityList = [&](llvm::Value *count) -> llvm::Value * {
3040 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3041 if (llvm::isa<llvm::Constant>(count) || llvm::isa<llvm::Argument>(count))
3042 builder.restoreIP(findAllocInsertPoints(builder, moduleTranslation));
3043 return builder.CreateAlloca(kmpTaskAffinityInfoTy, count,
3044 "omp.affinity_list");
3045 };
3046
3047 auto createAffinity =
3048 [&](llvm::Value *count,
3049 llvm::Value *info) -> llvm::OpenMPIRBuilder::AffinityData {
3050 llvm::OpenMPIRBuilder::AffinityData ad{};
3051 ad.Count = builder.CreateTrunc(count, builder.getInt32Ty());
3052 ad.Info =
3053 builder.CreatePointerBitCastOrAddrSpaceCast(info, builder.getPtrTy(0));
3054 return ad;
3055 };
3056
3057 if (!taskOp.getAffinityVars().empty()) {
3058 llvm::Value *count = llvm::ConstantInt::get(
3059 builder.getInt64Ty(), taskOp.getAffinityVars().size());
3060 llvm::Value *list = allocateAffinityList(count);
3061 fillAffinityLocators(taskOp.getAffinityVars(), builder, moduleTranslation,
3062 list);
3063 ads.emplace_back(createAffinity(count, list));
3064 }
3065
3066 if (!taskOp.getIterated().empty()) {
3067 for (auto [i, iter] : llvm::enumerate(taskOp.getIterated())) {
3068 auto itersOp = iter.getDefiningOp<omp::IteratorOp>();
3069 assert(itersOp && "iterated value must be defined by omp.iterator");
3070 IteratorInfo iterInfo(itersOp, moduleTranslation, builder);
3071 llvm::Value *affList = allocateAffinityList(iterInfo.getTotalTrips());
3072 if (failed(fillIteratorLoop(
3073 itersOp, builder, moduleTranslation, iterInfo, "iterator",
3074 [&](llvm::Value *linearIV, mlir::omp::YieldOp yield) {
3075 auto entryOp = yield.getResults()[0]
3076 .getDefiningOp<mlir::omp::AffinityEntryOp>();
3077 assert(entryOp && "expect yield produce an affinity entry");
3078 llvm::Value *addr =
3079 moduleTranslation.lookupValue(entryOp.getAddr());
3080 llvm::Value *len =
3081 moduleTranslation.lookupValue(entryOp.getLen());
3082 storeAffinityEntry(builder,
3083 *moduleTranslation.getOpenMPBuilder(),
3084 affList, linearIV, addr, len);
3085 })))
3086 return llvm::failure();
3087 ads.emplace_back(createAffinity(iterInfo.getTotalTrips(), affList));
3088 }
3089 }
3090
3091 llvm::Value *totalAffinityCount = builder.getInt32(0);
3092 for (const auto &affinity : ads)
3093 totalAffinityCount = builder.CreateAdd(
3094 totalAffinityCount,
3095 builder.CreateIntCast(affinity.Count, builder.getInt32Ty(),
3096 /*isSigned=*/false));
3097
3098 llvm::Value *affinityInfo = ads.front().Info;
3099 if (ads.size() > 1) {
3100 llvm::StructType *kmpTaskAffinityInfoTy =
3101 moduleTranslation.getOpenMPBuilder()->getKmpTaskAffinityInfoTy();
3102 llvm::Value *affinityInfoElemSize = builder.getInt64(
3103 moduleTranslation.getLLVMModule()->getDataLayout().getTypeAllocSize(
3104 kmpTaskAffinityInfoTy));
3105
3106 llvm::Value *packedAffinityInfo = allocateAffinityList(totalAffinityCount);
3107 llvm::Value *packedAffinityInfoOffset = builder.getInt32(0);
3108 for (const auto &affinity : ads) {
3109 llvm::Value *affinityCount = builder.CreateIntCast(
3110 affinity.Count, builder.getInt32Ty(), /*isSigned=*/false);
3111 llvm::Value *affinityCountInt64 = builder.CreateIntCast(
3112 affinityCount, builder.getInt64Ty(), /*isSigned=*/false);
3113 llvm::Value *affinityInfoSize =
3114 builder.CreateMul(affinityCountInt64, affinityInfoElemSize);
3115
3116 llvm::Value *packedAffinityInfoIndex = builder.CreateIntCast(
3117 packedAffinityInfoOffset, kmpTaskAffinityInfoTy->getElementType(0),
3118 /*isSigned=*/false);
3119 packedAffinityInfoIndex = builder.CreateInBoundsGEP(
3120 kmpTaskAffinityInfoTy, packedAffinityInfo, packedAffinityInfoIndex);
3121
3122 builder.CreateMemCpy(
3123 packedAffinityInfoIndex, llvm::Align(1),
3124 builder.CreatePointerBitCastOrAddrSpaceCast(
3125 affinity.Info, builder.getPtrTy(packedAffinityInfoIndex->getType()
3126 ->getPointerAddressSpace())),
3127 llvm::Align(1), affinityInfoSize);
3128
3129 packedAffinityInfoOffset =
3130 builder.CreateAdd(packedAffinityInfoOffset, affinityCount);
3131 }
3132
3133 affinityInfo = packedAffinityInfo;
3134 }
3135
3136 ad.Count = totalAffinityCount;
3137 ad.Info = affinityInfo;
3138
3139 return mlir::success();
3140}
3141
3142// Allocates a single kmp_dep_info array sized to hold both locator
3143// (non-iterated) and iterated entries, fills the locator entries first, then
3144// runs an iterator loop for each iterator modifier object.
3145static mlir::LogicalResult
3146buildDependData(OperandRange dependVars, std::optional<ArrayAttr> dependKinds,
3147 OperandRange dependIterated,
3148 std::optional<ArrayAttr> dependIteratedKinds,
3149 llvm::IRBuilderBase &builder,
3150 mlir::LLVM::ModuleTranslation &moduleTranslation,
3151 llvm::OpenMPIRBuilder::DependenciesInfo &taskDeps) {
3152 if (dependIterated.empty()) {
3153 buildDependDataLocator(dependKinds, dependVars, moduleTranslation,
3154 taskDeps.Deps);
3155 return mlir::success();
3156 }
3157
3158 llvm::OpenMPIRBuilder &ompBuilder = *moduleTranslation.getOpenMPBuilder();
3159 llvm::Type *dependInfoTy = ompBuilder.DependInfo;
3160 unsigned numLocator = dependVars.size();
3161
3162 // Compute total count: locator deps + sum of iterator trip counts.
3163 llvm::Value *totalCount =
3164 llvm::ConstantInt::get(builder.getInt64Ty(), numLocator);
3165
3167 for (auto iter : dependIterated) {
3168 auto itersOp = iter.getDefiningOp<mlir::omp::IteratorOp>();
3169 assert(itersOp && "depend_iterated value must be defined by omp.iterator");
3170 iterInfos.emplace_back(itersOp, moduleTranslation, builder);
3171 totalCount =
3172 builder.CreateAdd(totalCount, iterInfos.back().getTotalTrips());
3173 }
3174
3175 // Heap-allocate the kmp_depend_info array so we don't risk
3176 // dynamic-sized alloca outside the entry block (e.g. inside loops).
3177 llvm::Constant *allocSize = llvm::ConstantExpr::getSizeOf(dependInfoTy);
3178 llvm::Value *depArray =
3179 builder.CreateMalloc(ompBuilder.SizeTy, dependInfoTy, allocSize,
3180 totalCount, /*MallocF=*/nullptr, ".dep.arr.addr");
3181
3182 // Fill non-iterated entries at indices [0, numLocator).
3183 if (numLocator > 0) {
3185 buildDependDataLocator(dependKinds, dependVars, moduleTranslation, dds);
3186 for (auto [i, dd] : llvm::enumerate(dds)) {
3187 llvm::Value *idx = llvm::ConstantInt::get(builder.getInt64Ty(), i);
3188 llvm::Value *entry =
3189 builder.CreateInBoundsGEP(dependInfoTy, depArray, idx);
3190 ompBuilder.emitTaskDependency(builder, entry, dd);
3191 }
3192 }
3193
3194 // Fill iterated entries starting at index numLocator.
3195 llvm::Value *offset =
3196 llvm::ConstantInt::get(builder.getInt64Ty(), numLocator);
3197 for (auto [i, iterInfo] : llvm::enumerate(iterInfos)) {
3198 auto kindAttr = cast<mlir::omp::ClauseTaskDependAttr>(
3199 dependIteratedKinds->getValue()[i]);
3200 llvm::omp::RTLDependenceKindTy rtlKind =
3201 convertDependKind(kindAttr.getValue());
3202
3203 auto itersOp = dependIterated[i].getDefiningOp<mlir::omp::IteratorOp>();
3204 if (failed(fillIteratorLoop(
3205 itersOp, builder, moduleTranslation, iterInfo, "dep_iterator",
3206 [&](llvm::Value *linearIV, mlir::omp::YieldOp yield) {
3207 llvm::Value *addr =
3208 moduleTranslation.lookupValue(yield.getResults()[0]);
3209 llvm::Value *idx = builder.CreateAdd(offset, linearIV);
3210 llvm::Value *entry =
3211 builder.CreateInBoundsGEP(dependInfoTy, depArray, idx);
3212 ompBuilder.emitTaskDependency(
3213 builder, entry,
3214 llvm::OpenMPIRBuilder::DependData{rtlKind, addr->getType(),
3215 addr});
3216 })))
3217 return mlir::failure();
3218
3219 // Advance offset by the trip count of this iterator.
3220 offset = builder.CreateAdd(offset, iterInfo.getTotalTrips());
3221 }
3222
3223 taskDeps.DepArray = depArray;
3224 taskDeps.NumDeps = builder.CreateTrunc(totalCount, builder.getInt32Ty());
3225 return mlir::success();
3226}
3227
3228/// Converts an OpenMP task construct into LLVM IR using OpenMPIRBuilder.
3229static LogicalResult
3230convertOmpTaskOp(omp::TaskOp taskOp, llvm::IRBuilderBase &builder,
3231 LLVM::ModuleTranslation &moduleTranslation) {
3232 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
3233 if (failed(checkImplementationStatus(*taskOp)))
3234 return failure();
3235
3236 PrivateVarsInfo privateVarsInfo(taskOp);
3237 TaskContextStructManager taskStructMgr{builder, moduleTranslation,
3238 privateVarsInfo.privatizers};
3239
3240 // Allocate and copy private variables before creating the task. This avoids
3241 // accessing invalid memory if (after this scope ends) the private variables
3242 // are initialized from host variables or if the variables are copied into
3243 // from host variables (firstprivate). The insertion point is just before
3244 // where the code for creating and scheduling the task will go. That puts this
3245 // code outside of the outlined task region, which is what we want because
3246 // this way the initialization and copy regions are executed immediately while
3247 // the host variable data are still live.
3249 InsertPointTy allocaIP =
3250 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
3251
3252 // Not using splitBB() because that requires the current block to have a
3253 // terminator.
3254 assert(builder.GetInsertPoint() == builder.GetInsertBlock()->end());
3255 llvm::BasicBlock *taskStartBlock = llvm::BasicBlock::Create(
3256 builder.getContext(), "omp.task.start",
3257 /*Parent=*/builder.GetInsertBlock()->getParent());
3258 llvm::Instruction *branchToTaskStartBlock = builder.CreateBr(taskStartBlock);
3259 builder.SetInsertPoint(branchToTaskStartBlock);
3260
3261 // Now do this again to make the initialization and copy blocks
3262 llvm::BasicBlock *copyBlock =
3263 splitBB(builder, /*CreateBranch=*/true, "omp.private.copy");
3264 llvm::BasicBlock *initBlock =
3265 splitBB(builder, /*CreateBranch=*/true, "omp.private.init");
3266
3267 // Now the control flow graph should look like
3268 // starter_block:
3269 // <---- where we started when convertOmpTaskOp was called
3270 // br %omp.private.init
3271 // omp.private.init:
3272 // br %omp.private.copy
3273 // omp.private.copy:
3274 // br %omp.task.start
3275 // omp.task.start:
3276 // <---- where we want the insertion point to be when we call createTask()
3277
3278 // Save the alloca insertion point on ModuleTranslation stack for use in
3279 // nested regions.
3281 moduleTranslation, allocaIP, deallocBlocks);
3282
3283 // Allocate and initialize private variables
3284 builder.SetInsertPoint(initBlock->getTerminator());
3285
3286 // Create task variable structure
3287 taskStructMgr.generateTaskContextStruct();
3288 // GEPs so that we can initialize the variables. Don't use these GEPs inside
3289 // of the body otherwise it will be the GEP not the struct which is fowarded
3290 // to the outlined function. GEPs forwarded in this way are passed in a
3291 // stack-allocated (by OpenMPIRBuilder) structure which is not safe for tasks
3292 // which may not be executed until after the current stack frame goes out of
3293 // scope.
3294 taskStructMgr.createGEPsToPrivateVars();
3295
3296 for (auto [privDecl, mlirPrivVar, blockArg, llvmPrivateVarAlloc] :
3297 llvm::zip_equal(privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
3298 privateVarsInfo.blockArgs,
3299 taskStructMgr.getLLVMPrivateVarGEPs())) {
3300 // To be handled inside the task.
3301 if (!privDecl.readsFromMold())
3302 continue;
3303 assert(llvmPrivateVarAlloc &&
3304 "reads from mold so shouldn't have been skipped");
3305
3306 llvm::Expected<llvm::Value *> privateVarOrErr =
3307 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
3308 blockArg, llvmPrivateVarAlloc, initBlock);
3309 if (!privateVarOrErr)
3310 return handleError(privateVarOrErr, *taskOp.getOperation());
3311
3313
3314 // TODO: this is a bit of a hack for Fortran character boxes.
3315 // Character boxes are passed by value into the init region and then the
3316 // initialized character box is yielded by value. Here we need to store the
3317 // yielded value into the private allocation, and load the private
3318 // allocation to match the type expected by region block arguments.
3319 [[maybe_unused]] llvm::Value *llvmPrivateVar = llvmPrivateVarAlloc;
3320 if ((privateVarOrErr.get() != llvmPrivateVarAlloc) &&
3321 !mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3322 builder.CreateStore(privateVarOrErr.get(), llvmPrivateVarAlloc);
3323 // Load it so we have the value pointed to by the GEP
3324 llvmPrivateVar = builder.CreateLoad(privateVarOrErr.get()->getType(),
3325 llvmPrivateVarAlloc);
3326 }
3327 assert(llvmPrivateVar->getType() ==
3328 moduleTranslation.convertType(blockArg.getType()));
3329
3330 // Mapping blockArg -> llvmPrivateVarAlloc is done inside the body callback
3331 // so that OpenMPIRBuilder doesn't try to pass each GEP address through a
3332 // stack allocated structure.
3333 }
3334
3335 // firstprivate copy region
3336 setInsertPointForPossiblyEmptyBlock(builder, copyBlock);
3337 if (failed(copyFirstPrivateVars(
3338 taskOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
3339 taskStructMgr.getLLVMPrivateVarGEPs(), privateVarsInfo.privatizers,
3340 taskOp.getPrivateNeedsBarrier())))
3341 return llvm::failure();
3342
3343 llvm::OpenMPIRBuilder::AffinityData ad;
3344 if (failed(buildAffinityData(taskOp, builder, moduleTranslation, ad)))
3345 return llvm::failure();
3346
3347 // Resolve and validate in_reduction declarations. Byref in_reduction has
3348 // already been rejected by checkImplementationStatus; the helper rejects the
3349 // remaining richer declare_reduction shapes (two-argument initializer,
3350 // cleanup region, missing combiner). This is pure MLIR symbol-table work and
3351 // emits no IR. The matching task_reduction descriptor is registered by an
3352 // enclosing taskgroup; here we only look the per-task storage up at runtime.
3355 taskOp.getOperation(), taskOp.getInReductionSyms(), "omp.task",
3356 "in_reduction", inRedDecls)))
3357 return failure();
3358 SmallVector<llvm::Value *> inRedOrigPtrs;
3359 inRedOrigPtrs.reserve(inRedDecls.size());
3360 for (Value v : taskOp.getInReductionVars())
3361 inRedOrigPtrs.push_back(moduleTranslation.lookupValue(v));
3362
3363 // Set up for call to createTask()
3364 builder.SetInsertPoint(taskStartBlock);
3365
3366 auto bodyCB =
3367 [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
3368 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
3369 // Save the alloca insertion point on ModuleTranslation stack for use in
3370 // nested regions.
3372 moduleTranslation, allocaIP, deallocBlocks);
3373
3374 // translate the body of the task:
3375 builder.restoreIP(codegenIP);
3376
3377 llvm::BasicBlock *privInitBlock = nullptr;
3378 privateVarsInfo.llvmVars.resize(privateVarsInfo.blockArgs.size());
3379 for (auto [i, zip] : llvm::enumerate(llvm::zip_equal(
3380 privateVarsInfo.blockArgs, privateVarsInfo.privatizers,
3381 privateVarsInfo.mlirVars))) {
3382 auto [blockArg, privDecl, mlirPrivVar] = zip;
3383 // This is handled before the task executes
3384 if (privDecl.readsFromMold())
3385 continue;
3386
3387 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3388 llvm::Type *llvmAllocType =
3389 moduleTranslation.convertType(privDecl.getType());
3390 builder.SetInsertPoint(allocaIP.getBlock()->getTerminator());
3391 llvm::Value *llvmPrivateVar = builder.CreateAlloca(
3392 llvmAllocType, /*ArraySize=*/nullptr, "omp.private.alloc");
3393
3394 llvm::Expected<llvm::Value *> privateVarOrError =
3395 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
3396 blockArg, llvmPrivateVar, privInitBlock);
3397 if (!privateVarOrError)
3398 return privateVarOrError.takeError();
3399 moduleTranslation.mapValue(blockArg, privateVarOrError.get());
3400 privateVarsInfo.llvmVars[i] = privateVarOrError.get();
3401 }
3402
3403 taskStructMgr.createGEPsToPrivateVars();
3404 for (auto [i, llvmPrivVar] :
3405 llvm::enumerate(taskStructMgr.getLLVMPrivateVarGEPs())) {
3406 if (!llvmPrivVar) {
3407 assert(privateVarsInfo.llvmVars[i] &&
3408 "This is added in the loop above");
3409 continue;
3410 }
3411 privateVarsInfo.llvmVars[i] = llvmPrivVar;
3412 }
3413
3414 // Find and map the addresses of each variable within the task context
3415 // structure
3416 for (auto [blockArg, llvmPrivateVar, privateDecl] :
3417 llvm::zip_equal(privateVarsInfo.blockArgs, privateVarsInfo.llvmVars,
3418 privateVarsInfo.privatizers)) {
3419 // This was handled above.
3420 if (!privateDecl.readsFromMold())
3421 continue;
3422 // Fix broken pass-by-value case for Fortran character boxes
3423 if (!mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3424 llvmPrivateVar = builder.CreateLoad(
3425 moduleTranslation.convertType(blockArg.getType()), llvmPrivateVar);
3426 }
3427 assert(llvmPrivateVar->getType() ==
3428 moduleTranslation.convertType(blockArg.getType()));
3429 moduleTranslation.mapValue(blockArg, llvmPrivateVar);
3430 }
3431
3432 // Map in_reduction block arguments to the per-task private storage returned
3433 // by __kmpc_task_reduction_get_th_data. This call must be emitted inside
3434 // the to-be-outlined task body so that it returns the *executing* thread's
3435 // gtid (not the encountering thread's). The descriptor is NULL: the runtime
3436 // walks up enclosing taskgroups to find the matching task_reduction
3437 // registration for `origPtr`. The original pointers are auto-captured into
3438 // the task shareds aggregate by CodeExtractor during
3439 // OpenMPIRBuilder::finalize.
3440 if (!inRedDecls.empty()) {
3441 auto iface = cast<omp::BlockArgOpenMPOpInterface>(taskOp.getOperation());
3442 llvm::OpenMPIRBuilder &ompB = *moduleTranslation.getOpenMPBuilder();
3443 llvm::Module *m = moduleTranslation.getLLVMModule();
3444 llvm::LLVMContext &llvmCtx = m->getContext();
3445 llvm::OpenMPIRBuilder::LocationDescription bodyLoc(builder);
3446 uint32_t srcLocSize;
3447 llvm::Constant *srcLocStr =
3448 ompB.getOrCreateSrcLocStr(bodyLoc, srcLocSize);
3449 llvm::Value *bodyIdent = ompB.getOrCreateIdent(srcLocStr, srcLocSize);
3450 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the gtid
3451 // call lands inside the to-be-outlined task body.
3452 ompB.updateToLocation(bodyLoc);
3453 llvm::Value *bodyGtid = ompB.getOrCreateThreadID(bodyIdent);
3454 llvm::FunctionCallee getThData = ompB.getOrCreateRuntimeFunction(
3455 *m, llvm::omp::OMPRTL___kmpc_task_reduction_get_th_data);
3456 llvm::Type *ptrTy = llvm::PointerType::getUnqual(llvmCtx);
3457 llvm::Value *nullDesc = llvm::ConstantPointerNull::get(ptrTy);
3458 ArrayRef<BlockArgument> inRedBlockArgs = iface.getInReductionBlockArgs();
3459 for (auto [blockArg, origPtr] :
3460 llvm::zip_equal(inRedBlockArgs, inRedOrigPtrs)) {
3461 // __kmpc_task_reduction_get_th_data takes and returns a generic,
3462 // default-address-space `ptr`. Normalize a non-default-address-space
3463 // original pointer to the generic address space before the call, and
3464 // cast the returned private pointer back to the block argument's
3465 // address space when it differs (mirrors the taskloop reduction
3466 // remapping in convertOmpTaskloopContextOp).
3467 llvm::Value *lookupPtr = origPtr;
3468 if (auto *origPtrTy =
3469 llvm::dyn_cast<llvm::PointerType>(lookupPtr->getType());
3470 origPtrTy && origPtrTy->getAddressSpace() != 0)
3471 lookupPtr = builder.CreateAddrSpaceCast(lookupPtr, ptrTy);
3472 llvm::Value *priv = builder.CreateCall(
3473 getThData, {bodyGtid, nullDesc, lookupPtr}, "omp.inred.priv");
3474 if (auto *argPtrTy = llvm::dyn_cast<llvm::PointerType>(
3475 moduleTranslation.convertType(blockArg.getType()));
3476 argPtrTy && argPtrTy->getAddressSpace() != 0)
3477 priv = builder.CreateAddrSpaceCast(priv, argPtrTy);
3478 moduleTranslation.mapValue(blockArg, priv);
3479 }
3480 }
3481
3482 auto continuationBlockOrError = convertOmpOpRegions(
3483 taskOp.getRegion(), "omp.task.region", builder, moduleTranslation);
3484 if (failed(handleError(continuationBlockOrError, *taskOp)))
3485 return llvm::make_error<PreviouslyReportedError>();
3486
3487 builder.SetInsertPoint(continuationBlockOrError.get()->getTerminator());
3488
3489 if (failed(cleanupPrivateVars(taskOp, builder, moduleTranslation,
3490 taskOp.getLoc(), privateVarsInfo)))
3491 return llvm::make_error<PreviouslyReportedError>();
3492
3493 // Free heap allocated task context structure at the end of the task.
3494 taskStructMgr.freeStructPtr();
3495
3496 return llvm::Error::success();
3497 };
3498
3499 llvm::OpenMPIRBuilder &ompBuilder = *moduleTranslation.getOpenMPBuilder();
3500 SmallVector<llvm::UncondBrInst *> cancelTerminators;
3501 // The directive to match here is OMPD_taskgroup because it is the taskgroup
3502 // which is canceled. This is handled here because it is the task's cleanup
3503 // block which should be branched to.
3504 pushCancelFinalizationCB(cancelTerminators, builder, ompBuilder, taskOp,
3505 llvm::omp::Directive::OMPD_taskgroup);
3506
3507 llvm::OpenMPIRBuilder::DependenciesInfo dependencies;
3508 if (failed(buildDependData(taskOp.getDependVars(), taskOp.getDependKinds(),
3509 taskOp.getDependIterated(),
3510 taskOp.getDependIteratedKinds(), builder,
3511 moduleTranslation, dependencies)))
3512 return failure();
3513
3514 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
3515 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
3516 moduleTranslation.getOpenMPBuilder()->createTask(
3517 ompLoc, allocaIP, deallocBlocks, bodyCB, !taskOp.getUntied(),
3518 moduleTranslation.lookupValue(taskOp.getFinal()),
3519 moduleTranslation.lookupValue(taskOp.getIfExpr()), dependencies, ad,
3520 taskOp.getMergeable(),
3521 moduleTranslation.lookupValue(taskOp.getEventHandle()),
3522 moduleTranslation.lookupValue(taskOp.getPriority()));
3523
3524 if (failed(handleError(afterIP, *taskOp)))
3525 return failure();
3526
3527 // Set the correct branch target for task cancellation
3528 popCancelFinalizationCB(cancelTerminators, ompBuilder, afterIP.get());
3529
3530 builder.restoreIP(*afterIP);
3531
3532 if (dependencies.DepArray)
3533 builder.CreateFree(dependencies.DepArray);
3534
3535 return success();
3536}
3537
3538/// The correct entry point is convertOmpTaskloopContextOp. This gets called
3539/// whilst lowering the body of the taskloop context (i.e. the task function).
3540static LogicalResult
3541convertOmpTaskloopWrapperOp(omp::TaskloopWrapperOp loopWrapperOp,
3542 llvm::IRBuilderBase &builder,
3543 LLVM::ModuleTranslation &moduleTranslation) {
3544 mlir::Operation &opInst = *loopWrapperOp.getOperation();
3545 if (failed(checkImplementationStatus(opInst)))
3546 return failure();
3547
3548 // Recurse into the loop body.
3549 auto continuationBlockOrError = convertOmpOpRegions(
3550 loopWrapperOp.getRegion(), "omp.taskloop.wrapper.region", builder,
3551 moduleTranslation);
3552
3553 if (failed(handleError(continuationBlockOrError, opInst)))
3554 return failure();
3555
3556 builder.SetInsertPoint(continuationBlockOrError.get());
3557 return success();
3558}
3559
3560/// Look up the given value in the mapping, and if it's not there, translate its
3561/// defining operation at the current builder insertion point. Only pure,
3562/// regionless operations are supported because the same operation will later be
3563/// translated again when the taskloop body itself is lowered.
3564static llvm::Expected<llvm::Value *>
3566 LLVM::ModuleTranslation &moduleTranslation,
3567 llvm::IRBuilderBase &builder) {
3568 if (llvm::Value *mapped = moduleTranslation.lookupValue(value))
3569 return mapped;
3570
3571 Operation *defOp = value.getDefiningOp();
3572 if (!defOp)
3573 return llvm::make_error<llvm::StringError>(
3574 "value is a block argument and is not mapped",
3575 llvm::inconvertibleErrorCode());
3576 if (defOp->getNumRegions() != 0 || !isPure(defOp))
3577 return llvm::make_error<llvm::StringError>(
3578 "unsupported op defining taskloop loop bound",
3579 llvm::inconvertibleErrorCode());
3580
3581 SmallVector<Value> mappingsToRemove;
3582 mappingsToRemove.reserve(defOp->getNumOperands() + defOp->getNumResults());
3583 for (Value operand : defOp->getOperands()) {
3584 if (moduleTranslation.lookupValue(operand))
3585 continue;
3586
3587 llvm::Expected<llvm::Value *> operandOrError =
3588 lookupOrTranslatePureValue(operand, moduleTranslation, builder);
3589 if (!operandOrError)
3590 return operandOrError.takeError();
3591 moduleTranslation.mapValue(operand, *operandOrError);
3592 mappingsToRemove.push_back(operand);
3593 }
3594
3595 if (failed(moduleTranslation.convertOperation(*defOp, builder)))
3596 return llvm::make_error<llvm::StringError>(
3597 "failed to convert op defining taskloop loop bound",
3598 llvm::inconvertibleErrorCode());
3599
3600 llvm::Value *result = moduleTranslation.lookupValue(value);
3601 assert(result && "expected conversion of loop bound op to produce a value");
3602
3603 for (Value resultValue : defOp->getResults()) {
3604 if (moduleTranslation.lookupValue(resultValue))
3605 mappingsToRemove.push_back(resultValue);
3606 }
3607 for (Value mappedValue : mappingsToRemove)
3608 moduleTranslation.forgetMapping(mappedValue);
3609
3610 return result;
3611}
3612
3613static llvm::Error
3614computeTaskloopBounds(omp::LoopNestOp loopOp, llvm::IRBuilderBase &builder,
3615 LLVM::ModuleTranslation &moduleTranslation,
3616 llvm::Value *&lbVal, llvm::Value *&ubVal,
3617 llvm::Value *&stepVal) {
3618 Operation::operand_range lowerBounds = loopOp.getLoopLowerBounds();
3619 Operation::operand_range upperBounds = loopOp.getLoopUpperBounds();
3620 Operation::operand_range steps = loopOp.getLoopSteps();
3621
3622 llvm::Expected<llvm::Value *> firstLbOrErr =
3623 lookupOrTranslatePureValue(lowerBounds[0], moduleTranslation, builder);
3624 if (!firstLbOrErr)
3625 return firstLbOrErr.takeError();
3626
3627 llvm::Type *boundType = (*firstLbOrErr)->getType();
3628 ubVal = builder.getIntN(boundType->getIntegerBitWidth(), 1);
3629 if (loopOp.getCollapseNumLoops() > 1) {
3630 // In cases where Collapse is used with Taskloop, the upper bound of the
3631 // iteration space needs to be recalculated to cater for the collapsed loop.
3632 // The Collapsed Loop UpperBound is the product of all collapsed
3633 // loop's tripcount.
3634 // The LowerBound for collapsed loops is always 1. When the loops are
3635 // collapsed, it will reset the bounds and introduce processing to ensure
3636 // the index's are presented as expected. As this happens after creating
3637 // Taskloop, these bounds need predicting. Example:
3638 // !$omp taskloop collapse(2)
3639 // do i = 1, 10
3640 // do j = 1, 5
3641 // ..
3642 // end do
3643 // end do
3644 // This loop above has a total of 50 iterations, so the lb will be 1, and
3645 // the ub will be 50. collapseLoops in OMPIRBuilder then handles ensuring
3646 // that i and j are properly presented when used in the loop.
3647 for (uint64_t i = 0; i < loopOp.getCollapseNumLoops(); i++) {
3649 i == 0 ? std::move(firstLbOrErr)
3650 : lookupOrTranslatePureValue(lowerBounds[i], moduleTranslation,
3651 builder);
3652 if (!lbOrErr)
3653 return lbOrErr.takeError();
3655 upperBounds[i], moduleTranslation, builder);
3656 if (!ubOrErr)
3657 return ubOrErr.takeError();
3659 lookupOrTranslatePureValue(steps[i], moduleTranslation, builder);
3660 if (!stepOrErr)
3661 return stepOrErr.takeError();
3662
3663 llvm::Value *loopLb = *lbOrErr;
3664 llvm::Value *loopUb = *ubOrErr;
3665 llvm::Value *loopStep = *stepOrErr;
3666 // In some cases, such as where the ub is less than the lb so the loop
3667 // steps down, the calculation for the loopTripCount is swapped. To ensure
3668 // the correct value is found, calculate both UB - LB and LB - UB then
3669 // select which value to use depending on how the loop has been
3670 // configured.
3671 llvm::Value *loopLbMinusOne = builder.CreateSub(
3672 loopLb, builder.getIntN(boundType->getIntegerBitWidth(), 1));
3673 llvm::Value *loopUbMinusOne = builder.CreateSub(
3674 loopUb, builder.getIntN(boundType->getIntegerBitWidth(), 1));
3675 llvm::Value *boundsCmp = builder.CreateICmpSLT(loopLb, loopUb);
3676 llvm::Value *ubMinusLb = builder.CreateSub(loopUb, loopLbMinusOne);
3677 llvm::Value *lbMinusUb = builder.CreateSub(loopLb, loopUbMinusOne);
3678 llvm::Value *loopTripCount =
3679 builder.CreateSelect(boundsCmp, ubMinusLb, lbMinusUb);
3680 loopTripCount = builder.CreateBinaryIntrinsic(
3681 llvm::Intrinsic::abs, loopTripCount, builder.getFalse());
3682 // For loops that have a step value not equal to 1, we need to adjust the
3683 // trip count to ensure the correct number of iterations for the loop is
3684 // captured.
3685 llvm::Value *loopTripCountDivStep =
3686 builder.CreateSDiv(loopTripCount, loopStep);
3687 loopTripCountDivStep = builder.CreateBinaryIntrinsic(
3688 llvm::Intrinsic::abs, loopTripCountDivStep, builder.getFalse());
3689 llvm::Value *loopTripCountRem =
3690 builder.CreateSRem(loopTripCount, loopStep);
3691 loopTripCountRem = builder.CreateBinaryIntrinsic(
3692 llvm::Intrinsic::abs, loopTripCountRem, builder.getFalse());
3693 llvm::Value *needsRoundUp = builder.CreateICmpNE(
3694 loopTripCountRem,
3695 builder.getIntN(loopTripCountRem->getType()->getIntegerBitWidth(),
3696 0));
3697 loopTripCount =
3698 builder.CreateAdd(loopTripCountDivStep,
3699 builder.CreateZExtOrTrunc(
3700 needsRoundUp, loopTripCountDivStep->getType()));
3701 ubVal = builder.CreateMul(ubVal, loopTripCount);
3702 }
3703 lbVal = builder.getIntN(boundType->getIntegerBitWidth(), 1);
3704 stepVal = builder.getIntN(boundType->getIntegerBitWidth(), 1);
3705 } else {
3707 lookupOrTranslatePureValue(upperBounds[0], moduleTranslation, builder);
3708 if (!ubOrErr)
3709 return ubOrErr.takeError();
3711 lookupOrTranslatePureValue(steps[0], moduleTranslation, builder);
3712 if (!stepOrErr)
3713 return stepOrErr.takeError();
3714 lbVal = *firstLbOrErr;
3715 ubVal = *ubOrErr;
3716 stepVal = *stepOrErr;
3717 }
3718
3719 assert(lbVal != nullptr && "Expected value for lbVal");
3720 assert(ubVal != nullptr && "Expected value for ubVal");
3721 assert(stepVal != nullptr && "Expected value for stepVal");
3722 return llvm::Error::success();
3723}
3724
3725// Converts an OpenMP taskloop construct into LLVM IR using OpenMPIRBuilder.
3726static LogicalResult
3727convertOmpTaskloopContextOp(omp::TaskloopContextOp contextOp,
3728 llvm::IRBuilderBase &builder,
3729 LLVM::ModuleTranslation &moduleTranslation) {
3730 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
3731 mlir::Operation &opInst = *contextOp.getOperation();
3732 omp::TaskloopWrapperOp loopWrapperOp = contextOp.getLoopOp();
3733 if (failed(checkImplementationStatus(opInst)))
3734 return failure();
3735
3736 // It stores the pointer of allocated firstprivate copies,
3737 // which can be used later for freeing the allocated space.
3738 SmallVector<llvm::Value *> llvmFirstPrivateVars;
3739 PrivateVarsInfo privateVarsInfo(contextOp);
3740 TaskContextStructManager taskStructMgr{builder, moduleTranslation,
3741 privateVarsInfo.privatizers};
3742
3744 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
3745 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
3746
3747 assert(builder.GetInsertPoint() == builder.GetInsertBlock()->end());
3748 llvm::BasicBlock *taskloopStartBlock = llvm::BasicBlock::Create(
3749 builder.getContext(), "omp.taskloop.wrapper.start",
3750 /*Parent=*/builder.GetInsertBlock()->getParent());
3751 llvm::Instruction *branchToTaskloopStartBlock =
3752 builder.CreateBr(taskloopStartBlock);
3753 builder.SetInsertPoint(branchToTaskloopStartBlock);
3754
3755 llvm::BasicBlock *copyBlock =
3756 splitBB(builder, /*CreateBranch=*/true, "omp.private.copy");
3757 llvm::BasicBlock *initBlock =
3758 splitBB(builder, /*CreateBranch=*/true, "omp.private.init");
3759
3761 moduleTranslation, allocaIP, deallocBlocks);
3762
3763 // Allocate and initialize private variables
3764 builder.SetInsertPoint(initBlock->getTerminator());
3765
3766 // TODO: don't allocate if the loop has zero iterations.
3767 taskStructMgr.generateTaskContextStruct();
3768 taskStructMgr.createGEPsToPrivateVars();
3769
3770 llvmFirstPrivateVars.resize(privateVarsInfo.blockArgs.size());
3771
3772 for (auto [i, zip] : llvm::enumerate(llvm::zip_equal(
3773 privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
3774 privateVarsInfo.blockArgs, taskStructMgr.getLLVMPrivateVarGEPs()))) {
3775 auto [privDecl, mlirPrivVar, blockArg, llvmPrivateVarAlloc] = zip;
3776 // To be handled inside the taskloop.
3777 if (!privDecl.readsFromMold())
3778 continue;
3779 assert(llvmPrivateVarAlloc &&
3780 "reads from mold so shouldn't have been skipped");
3781
3782 llvm::Expected<llvm::Value *> privateVarOrErr =
3783 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
3784 blockArg, llvmPrivateVarAlloc, initBlock);
3785 if (!privateVarOrErr)
3786 return handleError(privateVarOrErr, *contextOp.getOperation());
3787
3788 llvmFirstPrivateVars[i] = privateVarOrErr.get();
3789
3790 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3791 builder.SetInsertPoint(builder.GetInsertBlock()->getTerminator());
3792
3793 [[maybe_unused]] llvm::Value *llvmPrivateVar = llvmPrivateVarAlloc;
3794 if ((privateVarOrErr.get() != llvmPrivateVarAlloc) &&
3795 !mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3796 builder.CreateStore(privateVarOrErr.get(), llvmPrivateVarAlloc);
3797 // Load it so we have the value pointed to by the GEP
3798 llvmPrivateVar = builder.CreateLoad(privateVarOrErr.get()->getType(),
3799 llvmPrivateVarAlloc);
3800 }
3801 assert(llvmPrivateVar->getType() ==
3802 moduleTranslation.convertType(blockArg.getType()));
3803 }
3804
3805 // firstprivate copy region
3806 setInsertPointForPossiblyEmptyBlock(builder, copyBlock);
3807 if (failed(copyFirstPrivateVars(
3808 contextOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
3809 taskStructMgr.getLLVMPrivateVarGEPs(), privateVarsInfo.privatizers,
3810 contextOp.getPrivateNeedsBarrier())))
3811 return llvm::failure();
3812
3813 // Resolve and validate reduction / in_reduction declarations up front.
3814 // This is pure MLIR symbol-table work and does not emit IR, so do it
3815 // before moving the builder to the taskloop start block. Richer
3816 // declare_reduction shapes (byref) have been rejected already by
3817 // checkImplementationStatus; the rest (two-argument initializer, cleanup
3818 // region, missing combiner) are rejected by the helper.
3821 contextOp.getOperation(), contextOp.getReductionSyms(),
3822 "omp.taskloop.context", "reduction", redDecls)))
3823 return failure();
3826 contextOp.getOperation(), contextOp.getInReductionSyms(),
3827 "omp.taskloop.context", "in_reduction", inRedDecls)))
3828 return failure();
3829
3830 // The op verifier rejects nogroup + reduction, so no check is needed here.
3831
3832 SmallVector<llvm::Value *> redOrigPtrs;
3833 redOrigPtrs.reserve(redDecls.size());
3834 for (Value v : contextOp.getReductionVars())
3835 redOrigPtrs.push_back(moduleTranslation.lookupValue(v));
3836 SmallVector<llvm::Value *> inRedOrigPtrs;
3837 inRedOrigPtrs.reserve(inRedDecls.size());
3838 for (Value v : contextOp.getInReductionVars())
3839 inRedOrigPtrs.push_back(moduleTranslation.lookupValue(v));
3840
3841 // Set up insertion point for emitting the implicit-taskgroup reduction
3842 // setup (if any) and for the subsequent call to createTaskloop().
3843 builder.SetInsertPoint(taskloopStartBlock);
3844
3845 llvm::OpenMPIRBuilder &ompBuilderRef = *moduleTranslation.getOpenMPBuilder();
3846 llvm::Module *module = moduleTranslation.getLLVMModule();
3847
3848 // If we have task_reduction items, we must emit our own implicit
3849 // __kmpc_taskgroup so that the descriptor returned by __kmpc_taskred_init
3850 // is associated with that taskgroup. We then force NoGroup=true so that
3851 // OpenMPIRBuilder::createTaskloop does not emit a second taskgroup.
3852 bool implicitTaskgroup = !redDecls.empty();
3853 llvm::Value *redDesc = nullptr;
3854 if (implicitTaskgroup) {
3855 llvm::OpenMPIRBuilder::LocationDescription redLoc(builder);
3856 uint32_t srcLocSize;
3857 llvm::Constant *srcLocStr =
3858 ompBuilderRef.getOrCreateSrcLocStr(redLoc, srcLocSize);
3859 llvm::Value *ident = ompBuilderRef.getOrCreateIdent(srcLocStr, srcLocSize);
3860 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the
3861 // gtid call lands at our insertion point.
3862 ompBuilderRef.updateToLocation(redLoc);
3863 llvm::Value *outerGtid = ompBuilderRef.getOrCreateThreadID(ident);
3864 llvm::FunctionCallee taskgroupFn = ompBuilderRef.getOrCreateRuntimeFunction(
3865 *module, llvm::omp::OMPRTL___kmpc_taskgroup);
3866 builder.CreateCall(taskgroupFn, {ident, outerGtid});
3867
3868 redDesc = emitTaskReductionInitCall(redDecls, redOrigPtrs,
3869 "__omp_taskloop_taskred_", builder,
3870 allocaIP, moduleTranslation);
3871 if (!redDesc)
3872 return failure();
3873 }
3874
3875 auto loopOp = cast<omp::LoopNestOp>(loopWrapperOp.getWrappedLoop());
3876 llvm::Value *lbVal = nullptr;
3877 llvm::Value *ubVal = nullptr;
3878 llvm::Value *stepVal = nullptr;
3879 if (llvm::Error err = computeTaskloopBounds(
3880 loopOp, builder, moduleTranslation, lbVal, ubVal, stepVal))
3881 return handleError(std::move(err), opInst);
3882
3883 auto bodyCB =
3884 [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
3885 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
3886 // Save the alloca insertion point on ModuleTranslation stack for use in
3887 // nested regions.
3889 moduleTranslation, allocaIP, deallocBlocks);
3890
3891 // translate the body of the taskloop:
3892 builder.restoreIP(codegenIP);
3893
3894 llvm::BasicBlock *privInitBlock = nullptr;
3895 privateVarsInfo.llvmVars.resize(privateVarsInfo.blockArgs.size());
3896 for (auto [i, zip] : llvm::enumerate(llvm::zip_equal(
3897 privateVarsInfo.blockArgs, privateVarsInfo.privatizers,
3898 privateVarsInfo.mlirVars))) {
3899 auto [blockArg, privDecl, mlirPrivVar] = zip;
3900 // This is handled before the task executes
3901 if (privDecl.readsFromMold())
3902 continue;
3903
3904 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3905 llvm::Type *llvmAllocType =
3906 moduleTranslation.convertType(privDecl.getType());
3907 builder.SetInsertPoint(allocaIP.getBlock()->getTerminator());
3908 llvm::Value *llvmPrivateVar = builder.CreateAlloca(
3909 llvmAllocType, /*ArraySize=*/nullptr, "omp.private.alloc");
3910
3911 llvm::Expected<llvm::Value *> privateVarOrError =
3912 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
3913 blockArg, llvmPrivateVar, privInitBlock);
3914 if (!privateVarOrError)
3915 return privateVarOrError.takeError();
3916 moduleTranslation.mapValue(blockArg, privateVarOrError.get());
3917 privateVarsInfo.llvmVars[i] = privateVarOrError.get();
3918 }
3919
3920 taskStructMgr.createGEPsToPrivateVars();
3921 for (auto [i, llvmPrivVar] :
3922 llvm::enumerate(taskStructMgr.getLLVMPrivateVarGEPs())) {
3923 if (!llvmPrivVar) {
3924 assert(privateVarsInfo.llvmVars[i] &&
3925 "This is added in the loop above");
3926 continue;
3927 }
3928 privateVarsInfo.llvmVars[i] = llvmPrivVar;
3929 }
3930
3931 // Find and map the addresses of each variable within the taskloop context
3932 // structure
3933 for (auto [blockArg, llvmPrivateVar, privateDecl] :
3934 llvm::zip_equal(privateVarsInfo.blockArgs, privateVarsInfo.llvmVars,
3935 privateVarsInfo.privatizers)) {
3936 // This was handled above.
3937 if (!privateDecl.readsFromMold())
3938 continue;
3939 // Fix broken pass-by-value case for Fortran character boxes
3940 if (!mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3941 llvmPrivateVar = builder.CreateLoad(
3942 moduleTranslation.convertType(blockArg.getType()), llvmPrivateVar);
3943 }
3944 assert(llvmPrivateVar->getType() ==
3945 moduleTranslation.convertType(blockArg.getType()));
3946 moduleTranslation.mapValue(blockArg, llvmPrivateVar);
3947 }
3948
3949 // Map reduction and in_reduction block arguments to the per-task private
3950 // storage returned by __kmpc_task_reduction_get_th_data. This call must
3951 // be emitted inside the to-be-outlined task body so that it returns the
3952 // *executing* thread's gtid (not the encountering thread's). The
3953 // taskgroup descriptor `redDesc` is computed in the outer scope and is
3954 // auto-captured into the task shareds aggregate by CodeExtractor during
3955 // OpenMPIRBuilder::finalize. For in_reduction the descriptor is NULL:
3956 // the runtime walks up enclosing taskgroups to find the matching
3957 // task_reduction registration for `origPtr`.
3958 if (!redDecls.empty() || !inRedDecls.empty()) {
3959 auto iface =
3960 cast<omp::BlockArgOpenMPOpInterface>(contextOp.getOperation());
3961 llvm::OpenMPIRBuilder &ompB = *moduleTranslation.getOpenMPBuilder();
3962 llvm::Module *m = moduleTranslation.getLLVMModule();
3963 llvm::LLVMContext &llvmCtx = m->getContext();
3964 llvm::OpenMPIRBuilder::LocationDescription bodyLoc(builder);
3965 uint32_t srcLocSize;
3966 llvm::Constant *srcLocStr =
3967 ompB.getOrCreateSrcLocStr(bodyLoc, srcLocSize);
3968 llvm::Value *bodyIdent = ompB.getOrCreateIdent(srcLocStr, srcLocSize);
3969 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the
3970 // gtid call lands inside the to-be-outlined task body.
3971 ompB.updateToLocation(bodyLoc);
3972 llvm::Value *bodyGtid = ompB.getOrCreateThreadID(bodyIdent);
3973 llvm::FunctionCallee getThData = ompB.getOrCreateRuntimeFunction(
3974 *m, llvm::omp::OMPRTL___kmpc_task_reduction_get_th_data);
3975 llvm::Type *ptrTy = llvm::PointerType::getUnqual(llvmCtx);
3976
3977 // Emit one __kmpc_task_reduction_get_th_data lookup for a reduction /
3978 // in_reduction item and map its block argument to the per-task private
3979 // storage the runtime returns. The runtime entry point takes (and
3980 // returns) a generic, default-address-space `ptr`, so normalize a
3981 // non-default-address-space original pointer to the generic address
3982 // space before the call (mirroring the descriptor setup in
3983 // emitTaskReductionInitCall), and cast the returned private pointer back
3984 // to the block argument's address space when that differs.
3985 auto remapReductionArg = [&](BlockArgument blockArg, llvm::Value *desc,
3986 llvm::Value *origPtr,
3987 const llvm::Twine &name) {
3988 if (auto *origPtrTy =
3989 llvm::dyn_cast<llvm::PointerType>(origPtr->getType());
3990 origPtrTy && origPtrTy->getAddressSpace() != 0)
3991 origPtr = builder.CreateAddrSpaceCast(origPtr, ptrTy);
3992 llvm::Value *priv =
3993 builder.CreateCall(getThData, {bodyGtid, desc, origPtr}, name);
3994 if (auto *argPtrTy = llvm::dyn_cast<llvm::PointerType>(
3995 moduleTranslation.convertType(blockArg.getType()));
3996 argPtrTy && argPtrTy->getAddressSpace() != 0)
3997 priv = builder.CreateAddrSpaceCast(priv, argPtrTy);
3998 moduleTranslation.mapValue(blockArg, priv);
3999 };
4000
4001 ArrayRef<BlockArgument> redBlockArgs = iface.getReductionBlockArgs();
4002 for (auto [blockArg, origPtr] :
4003 llvm::zip_equal(redBlockArgs, redOrigPtrs))
4004 remapReductionArg(blockArg, redDesc, origPtr, "omp.taskred.priv");
4005 ArrayRef<BlockArgument> inRedBlockArgs = iface.getInReductionBlockArgs();
4006 llvm::Value *nullDesc = llvm::ConstantPointerNull::get(ptrTy);
4007 for (auto [blockArg, origPtr] :
4008 llvm::zip_equal(inRedBlockArgs, inRedOrigPtrs))
4009 remapReductionArg(blockArg, nullDesc, origPtr, "omp.inred.priv");
4010 }
4011
4012 // Lower the contents of the taskloop context region: this is the body of
4013 // the generated task, not the loop.
4014 auto continuationBlockOrError = convertOmpOpRegions(
4015 contextOp.getRegion(), "omp.taskloop.context.region", builder,
4016 moduleTranslation);
4017
4018 if (failed(handleError(continuationBlockOrError, opInst)))
4019 return llvm::make_error<PreviouslyReportedError>();
4020
4021 builder.SetInsertPoint(continuationBlockOrError.get()->getTerminator());
4022
4023 // This is freeing the private variables as mapped inside of the task: these
4024 // will be per-task private copies possibly after task duplication. This is
4025 // handled transparently by how these are passed to the structure passed
4026 // into the outlined function. When the task is duplicated, that structure
4027 // is duplicated too.
4028 if (failed(cleanupPrivateVars(contextOp, builder, moduleTranslation,
4029 contextOp.getLoc(), privateVarsInfo)))
4030 return llvm::make_error<PreviouslyReportedError>();
4031 // Similarly, the task context structure freed inside the task is the
4032 // per-task copy after task duplication.
4033 taskStructMgr.freeStructPtr();
4034
4035 return llvm::Error::success();
4036 };
4037
4038 // Taskloop divides into an appropriate number of tasks by repeatedly
4039 // duplicating the original task. Each time this is done, the task context
4040 // structure must be duplicated too.
4041 auto taskDupCB = [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
4042 llvm::Value *destPtr, llvm::Value *srcPtr)
4044 llvm::IRBuilderBase::InsertPointGuard guard(builder);
4045 builder.restoreIP(codegenIP);
4046
4047 llvm::Type *ptrTy =
4048 builder.getPtrTy(srcPtr->getType()->getPointerAddressSpace());
4049 llvm::Value *src =
4050 builder.CreateLoad(ptrTy, srcPtr, "omp.taskloop.context.src");
4051
4052 TaskContextStructManager &srcStructMgr = taskStructMgr;
4053 TaskContextStructManager destStructMgr(builder, moduleTranslation,
4054 privateVarsInfo.privatizers);
4055 destStructMgr.generateTaskContextStruct();
4056 llvm::Value *dest = destStructMgr.getStructPtr();
4057 dest->setName("omp.taskloop.context.dest");
4058 builder.CreateStore(dest, destPtr);
4059
4061 srcStructMgr.createGEPsToPrivateVars(src);
4063 destStructMgr.createGEPsToPrivateVars(dest);
4064
4065 // Inline init regions.
4066 for (auto [privDecl, mold, blockArg, llvmPrivateVarAlloc] :
4067 llvm::zip_equal(privateVarsInfo.privatizers, srcGEPs,
4068 privateVarsInfo.blockArgs, destGEPs)) {
4069 // To be handled inside task body.
4070 if (!privDecl.readsFromMold())
4071 continue;
4072 assert(llvmPrivateVarAlloc &&
4073 "reads from mold so shouldn't have been skipped");
4074
4075 llvm::Value *moldArg = materializeRegionArgValue(
4076 builder, moduleTranslation, privDecl.getInitMoldArg(), mold);
4078 builder, moduleTranslation, privDecl, moldArg, blockArg,
4079 llvmPrivateVarAlloc, builder.GetInsertBlock());
4080 if (!privateVarOrErr)
4081 return privateVarOrErr.takeError();
4082
4084
4085 // TODO: this is a bit of a hack for Fortran character boxes.
4086 // Character boxes are passed by value into the init region and then the
4087 // initialized character box is yielded by value. Here we need to store
4088 // the yielded value into the private allocation, and load the private
4089 // allocation to match the type expected by region block arguments.
4090 [[maybe_unused]] llvm::Value *llvmPrivateVar = llvmPrivateVarAlloc;
4091 if ((privateVarOrErr.get() != llvmPrivateVarAlloc) &&
4092 !mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
4093 builder.CreateStore(privateVarOrErr.get(), llvmPrivateVarAlloc);
4094 // Load it so we have the value pointed to by the GEP
4095 llvmPrivateVar = builder.CreateLoad(privateVarOrErr.get()->getType(),
4096 llvmPrivateVarAlloc);
4097 }
4098 assert(llvmPrivateVar->getType() ==
4099 moduleTranslation.convertType(blockArg.getType()));
4100
4101 // Mapping blockArg -> llvmPrivateVarAlloc is done inside the body
4102 // callback so that OpenMPIRBuilder doesn't try to pass each GEP address
4103 // through a stack allocated structure.
4104 }
4105
4106 if (failed(copyFirstPrivateVars(contextOp.getOperation(), builder,
4107 moduleTranslation, srcGEPs, destGEPs,
4108 privateVarsInfo.privatizers,
4109 contextOp.getPrivateNeedsBarrier())))
4110 return llvm::make_error<PreviouslyReportedError>();
4111
4112 return builder.saveIP();
4113 };
4114
4115 auto loopInfo = [&]() -> llvm::Expected<llvm::CanonicalLoopInfo *> {
4116 llvm::CanonicalLoopInfo *loopInfo = findCurrentLoopInfo(moduleTranslation);
4117 return loopInfo;
4118 };
4119
4120 llvm::Value *ifCond = nullptr;
4121 llvm::Value *grainsize = nullptr;
4122 int sched = 0; // default
4123 mlir::Value grainsizeVal = contextOp.getGrainsize();
4124 mlir::Value numTasksVal = contextOp.getNumTasks();
4125 if (Value ifVar = contextOp.getIfExpr())
4126 ifCond = moduleTranslation.lookupValue(ifVar);
4127 if (grainsizeVal) {
4128 grainsize = moduleTranslation.lookupValue(grainsizeVal);
4129 sched = 1; // grainsize
4130 } else if (numTasksVal) {
4131 grainsize = moduleTranslation.lookupValue(numTasksVal);
4132 sched = 2; // num_tasks
4133 }
4134
4135 llvm::OpenMPIRBuilder::TaskDupCallbackTy taskDupOrNull = nullptr;
4136 if (taskStructMgr.getStructPtr())
4137 taskDupOrNull = taskDupCB;
4138
4139 llvm::OpenMPIRBuilder &ompBuilder = *moduleTranslation.getOpenMPBuilder();
4140 SmallVector<llvm::UncondBrInst *> cancelTerminators;
4141 // The directive to match here is OMPD_taskgroup because it is the
4142 // taskgroup which is canceled. This is handled here because it is the
4143 // task's cleanup block which should be branched to. It doesn't depend upon
4144 // nogroup because even in that case the taskloop might still be inside an
4145 // explicit taskgroup.
4146 pushCancelFinalizationCB(cancelTerminators, builder, ompBuilder, contextOp,
4147 llvm::omp::Directive::OMPD_taskgroup);
4148
4149 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4150 bool effectiveNoGroup = contextOp.getNogroup() || implicitTaskgroup;
4151 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
4152 moduleTranslation.getOpenMPBuilder()->createTaskloop(
4153 ompLoc, allocaIP, deallocBlocks, bodyCB, loopInfo, lbVal, ubVal,
4154 stepVal, contextOp.getUntied(), ifCond, grainsize, effectiveNoGroup,
4155 sched, moduleTranslation.lookupValue(contextOp.getFinal()),
4156 contextOp.getMergeable(),
4157 moduleTranslation.lookupValue(contextOp.getPriority()),
4158 loopOp.getCollapseNumLoops(), taskDupOrNull,
4159 taskStructMgr.getStructPtr());
4160
4161 if (failed(handleError(afterIP, opInst)))
4162 return failure();
4163
4164 popCancelFinalizationCB(cancelTerminators, ompBuilder, afterIP.get());
4165
4166 builder.restoreIP(*afterIP);
4167
4168 // Close the implicit taskgroup we opened for task_reduction. The end call
4169 // must execute on the encountering thread, so use the outer-scope gtid.
4170 if (implicitTaskgroup) {
4171 llvm::OpenMPIRBuilder::LocationDescription endLoc(builder);
4172 uint32_t srcLocSize;
4173 llvm::Constant *srcLocStr =
4174 ompBuilder.getOrCreateSrcLocStr(endLoc, srcLocSize);
4175 llvm::Value *ident = ompBuilder.getOrCreateIdent(srcLocStr, srcLocSize);
4176 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the
4177 // gtid call lands at our insertion point.
4178 ompBuilder.updateToLocation(endLoc);
4179 llvm::Value *outerGtid = ompBuilder.getOrCreateThreadID(ident);
4180 llvm::FunctionCallee endTgFn = ompBuilder.getOrCreateRuntimeFunction(
4181 *moduleTranslation.getLLVMModule(),
4182 llvm::omp::OMPRTL___kmpc_end_taskgroup);
4183 builder.CreateCall(endTgFn, {ident, outerGtid});
4184 }
4185 return success();
4186}
4187
4188/// Build an outlined init helper for a task_reduction declare_reduction op.
4189/// Signature: void(ptr %priv, ptr %orig). For non-byref reductions, the init
4190/// region's mold argument is mapped following the same rule as the regular
4191/// reduction path (`mapInitializationArgs`): a non-pointer mold loads the
4192/// value from %orig, while a pointer-typed mold receives %orig directly. The
4193/// yielded value is stored into %priv.
4194static llvm::Function *
4195emitTaskReductionInitFn(omp::DeclareReductionOp decl, StringRef baseName,
4196 LLVM::ModuleTranslation &moduleTranslation) {
4197 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4198 llvm::LLVMContext &ctx = llvmModule->getContext();
4199 llvm::Type *voidTy = llvm::Type::getVoidTy(ctx);
4200 llvm::Type *ptrTy = llvm::PointerType::getUnqual(ctx);
4201 llvm::FunctionType *fty =
4202 llvm::FunctionType::get(voidTy, {ptrTy, ptrTy}, false);
4203 llvm::Function *fn =
4204 llvm::Function::Create(fty, llvm::GlobalValue::InternalLinkage,
4205 baseName + ".red.init", llvmModule);
4206 fn->setDoesNotRecurse();
4207 fn->getArg(0)->setName("priv");
4208 fn->getArg(1)->setName("orig");
4209
4210 llvm::BasicBlock *entry = llvm::BasicBlock::Create(ctx, "entry", fn);
4211 llvm::IRBuilder<> b(entry);
4212
4213 // Map the initializer's mold argument the same way the regular reduction
4214 // path does in `mapInitializationArgs`: only load the original value when a
4215 // non-pointer mold is expected. For a pointer-typed mold the storage pointer
4216 // (%orig) is passed through directly, so a mold-yielding initializer lowers
4217 // to `store ptr %orig, ptr %priv` rather than emitting a spurious load.
4218 Value moldArg = decl.getInitializerMoldArg();
4219 llvm::Value *origVal = fn->getArg(1);
4220 if (!isa<LLVM::LLVMPointerType>(moldArg.getType()))
4221 origVal = b.CreateLoad(moduleTranslation.convertType(moldArg.getType()),
4222 fn->getArg(1), "omp.orig");
4223 moduleTranslation.mapValue(moldArg, origVal);
4225 if (failed(inlineConvertOmpRegions(decl.getInitializerRegion(),
4226 "omp.taskred.init", b, moduleTranslation,
4227 &phis))) {
4228 fn->eraseFromParent();
4229 return nullptr;
4230 }
4231 assert(phis.size() == 1 &&
4232 "expected one value yielded from reduction initializer");
4233 b.CreateStore(phis[0], fn->getArg(0));
4234 b.CreateRetVoid();
4235
4236 moduleTranslation.forgetMapping(decl.getInitializerRegion());
4237 return fn;
4238}
4239
4240/// Build an outlined combiner helper for a task_reduction declare_reduction op.
4241/// Signature: void(ptr %lhs, ptr %rhs). For non-byref reductions, the values
4242/// at *%lhs and *%rhs are loaded, fed into the combiner region, and the
4243/// yielded scalar is stored back into *%lhs.
4244static llvm::Function *
4245emitTaskReductionCombFn(omp::DeclareReductionOp decl, StringRef baseName,
4246 LLVM::ModuleTranslation &moduleTranslation) {
4247 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4248 llvm::LLVMContext &ctx = llvmModule->getContext();
4249 llvm::Type *voidTy = llvm::Type::getVoidTy(ctx);
4250 llvm::Type *ptrTy = llvm::PointerType::getUnqual(ctx);
4251 llvm::FunctionType *fty =
4252 llvm::FunctionType::get(voidTy, {ptrTy, ptrTy}, false);
4253 llvm::Function *fn =
4254 llvm::Function::Create(fty, llvm::GlobalValue::InternalLinkage,
4255 baseName + ".red.comb", llvmModule);
4256 fn->setDoesNotRecurse();
4257 fn->getArg(0)->setName("lhs");
4258 fn->getArg(1)->setName("rhs");
4259
4260 llvm::BasicBlock *entry = llvm::BasicBlock::Create(ctx, "entry", fn);
4261 llvm::IRBuilder<> b(entry);
4262
4263 llvm::Type *elemTy = moduleTranslation.convertType(decl.getType());
4264 Block &combBlock = decl.getReductionRegion().front();
4265 assert(combBlock.getNumArguments() == 2 &&
4266 "expected two arguments in declare_reduction combiner");
4267 llvm::Value *lhsVal = b.CreateLoad(elemTy, fn->getArg(0), "omp.lhs");
4268 llvm::Value *rhsVal = b.CreateLoad(elemTy, fn->getArg(1), "omp.rhs");
4269 moduleTranslation.mapValue(combBlock.getArgument(0), lhsVal);
4270 moduleTranslation.mapValue(combBlock.getArgument(1), rhsVal);
4271
4273 if (failed(inlineConvertOmpRegions(decl.getReductionRegion(),
4274 "omp.taskred.comb", b, moduleTranslation,
4275 &phis))) {
4276 fn->eraseFromParent();
4277 return nullptr;
4278 }
4279 assert(phis.size() == 1 &&
4280 "expected one value yielded from reduction combiner");
4281 b.CreateStore(phis[0], fn->getArg(0));
4282 b.CreateRetVoid();
4283
4284 moduleTranslation.forgetMapping(decl.getReductionRegion());
4285 return fn;
4286}
4287
4288/// Emit the per-taskgroup task_reduction descriptor array and the
4289/// `__kmpc_taskred_init` runtime call. \p origPtrs holds the LLVM values for
4290/// the original (shared) variables, one per declaration in \p redDecls.
4291/// `builder` must be set to the point at which the descriptor stores and the
4292/// init call should be emitted; the descriptor array itself is allocated at
4293/// \p allocaIP. \p helperNamePrefix is used to disambiguate the generated
4294/// init/combiner helper symbol names between taskgroup and taskloop callers.
4295///
4296/// When \p isModifier is false, emits `__kmpc_taskred_init` and returns the
4297/// `ptr` value it produces (the taskgroup reduction handle). When \p isModifier
4298/// is true, emits `__kmpc_taskred_modifier_init` instead to open a
4299/// task-reduction scope for a parallel or worksharing construct, passing
4300/// \p isWorksharing as the runtime `is_ws` argument. Returns null on failure.
4301///
4302/// Only the non-byref form is handled here. Byref reductions have already
4303/// been rejected by `checkImplementationStatus`.
4304static llvm::Value *emitTaskReductionInitCall(
4306 ArrayRef<llvm::Value *> origPtrs, StringRef helperNamePrefix,
4307 llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder::InsertPointTy allocaIP,
4308 LLVM::ModuleTranslation &moduleTranslation, bool isModifier,
4309 bool isWorksharing) {
4310 assert(redDecls.size() == origPtrs.size() &&
4311 "expected one orig pointer per reduction decl");
4312 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4313 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4314 llvm::LLVMContext &ctx = llvmModule->getContext();
4315 const llvm::DataLayout &dl = llvmModule->getDataLayout();
4316
4317 llvm::Type *ptrTy = llvm::PointerType::getUnqual(ctx);
4318 llvm::Type *i32Ty = llvm::Type::getInt32Ty(ctx);
4319 llvm::Type *sizeTy =
4320 llvm::Type::getIntNTy(ctx, dl.getPointerSizeInBits(/*AddrSpace=*/0));
4321
4322 // Identified `kmp_taskred_input_t` struct, matching the layout used by
4323 // Clang's CGOpenMPRuntime::emitTaskReductionInit.
4324 llvm::StructType *redInputTy =
4325 llvm::StructType::getTypeByName(ctx, "kmp_taskred_input_t");
4326 if (!redInputTy)
4327 redInputTy = llvm::StructType::create(
4328 ctx, {ptrTy, ptrTy, sizeTy, ptrTy, ptrTy, ptrTy, i32Ty},
4329 "kmp_taskred_input_t");
4330
4331 unsigned n = redDecls.size();
4332 llvm::ArrayType *arrTy = llvm::ArrayType::get(redInputTy, n);
4333
4334 // Allocate the descriptor array in the enclosing function's alloca block.
4335 llvm::AllocaInst *arrAlloca;
4336 {
4337 llvm::IRBuilderBase::InsertPointGuard guard(builder);
4338 builder.restoreIP(allocaIP);
4339 arrAlloca =
4340 builder.CreateAlloca(arrTy, /*ArraySize=*/nullptr, ".taskred.input");
4341 }
4342
4343 // Fill each descriptor entry at the current builder insertion point.
4344 llvm::Value *zero = builder.getInt32(0);
4345 for (unsigned i = 0; i < n; ++i) {
4346 omp::DeclareReductionOp decl = redDecls[i];
4347 llvm::Value *orig = origPtrs[i];
4348 if (auto *origPtrTy = llvm::dyn_cast<llvm::PointerType>(orig->getType());
4349 origPtrTy && origPtrTy->getAddressSpace() != 0)
4350 orig = builder.CreateAddrSpaceCast(orig, ptrTy);
4351 llvm::Type *elemTy = moduleTranslation.convertType(decl.getType());
4352 uint64_t size = dl.getTypeAllocSize(elemTy).getFixedValue();
4353
4354 std::string baseName =
4355 (llvm::Twine(helperNamePrefix) + decl.getSymName()).str();
4356 llvm::Function *initFn =
4357 emitTaskReductionInitFn(decl, baseName, moduleTranslation);
4358 llvm::Function *combFn =
4359 emitTaskReductionCombFn(decl, baseName, moduleTranslation);
4360 if (!initFn || !combFn)
4361 return nullptr;
4362 llvm::Value *elemPtr = builder.CreateInBoundsGEP(
4363 arrTy, arrAlloca, {zero, builder.getInt32(i)}, ".taskred.elem");
4364 auto storeField = [&](unsigned fieldIdx, llvm::Value *val) {
4365 llvm::Value *fieldPtr =
4366 builder.CreateStructGEP(redInputTy, elemPtr, fieldIdx);
4367 builder.CreateStore(val, fieldPtr);
4368 };
4369 storeField(0, orig); // reduce_shar
4370 storeField(1, orig); // reduce_orig
4371 storeField(2, llvm::ConstantInt::get(sizeTy, size)); // reduce_size
4372 storeField(3, initFn); // reduce_init
4373 storeField(4, llvm::ConstantPointerNull::get(ptrTy)); // reduce_fini
4374 storeField(5, combFn); // reduce_comb
4375 storeField(6, llvm::ConstantInt::get(i32Ty, 0)); // flags
4376 }
4377
4378 // Emit the runtime call that registers the task reduction data.
4379 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4380 uint32_t srcLocSize;
4381 llvm::Constant *srcLocStr =
4382 ompBuilder->getOrCreateSrcLocStr(ompLoc, srcLocSize);
4383 llvm::Value *ident = ompBuilder->getOrCreateIdent(srcLocStr, srcLocSize);
4384 ompBuilder->updateToLocation(ompLoc);
4385 llvm::Value *gtid = ompBuilder->getOrCreateThreadID(ident);
4386 if (isModifier) {
4387 // __kmpc_taskred_modifier_init(loc, gtid, is_ws, num, &arr) opens a
4388 // task-reduction scope for the enclosing parallel/worksharing region.
4389 llvm::FunctionCallee modInit = ompBuilder->getOrCreateRuntimeFunction(
4390 *llvmModule, llvm::omp::OMPRTL___kmpc_taskred_modifier_init);
4391 return builder.CreateCall(modInit,
4392 {ident, gtid,
4393 builder.getInt32(isWorksharing ? 1 : 0),
4394 builder.getInt32(n), arrAlloca},
4395 ".taskred.desc");
4396 }
4397 // __kmpc_taskred_init(gtid, num, &arr).
4398 llvm::FunctionCallee taskredInit = ompBuilder->getOrCreateRuntimeFunction(
4399 *llvmModule, llvm::omp::OMPRTL___kmpc_taskred_init);
4400 return builder.CreateCall(taskredInit, {gtid, builder.getInt32(n), arrAlloca},
4401 ".taskred.desc");
4402}
4403
4404/// Emits `__kmpc_task_reduction_modifier_fini(loc, gtid, is_ws)` at the current
4405/// builder insertion point, closing the task-reduction scope opened by the
4406/// `task` reduction modifier on a parallel or worksharing construct.
4407static void
4408emitTaskReductionModifierFini(bool isWorksharing, llvm::IRBuilderBase &builder,
4409 LLVM::ModuleTranslation &moduleTranslation) {
4410 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4411 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4412 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4413 uint32_t srcLocSize;
4414 llvm::Constant *srcLocStr =
4415 ompBuilder->getOrCreateSrcLocStr(ompLoc, srcLocSize);
4416 llvm::Value *ident = ompBuilder->getOrCreateIdent(srcLocStr, srcLocSize);
4417 ompBuilder->updateToLocation(ompLoc);
4418 llvm::Value *gtid = ompBuilder->getOrCreateThreadID(ident);
4419 llvm::FunctionCallee fini = ompBuilder->getOrCreateRuntimeFunction(
4420 *llvmModule, llvm::omp::OMPRTL___kmpc_task_reduction_modifier_fini);
4421 builder.CreateCall(fini,
4422 {ident, gtid, builder.getInt32(isWorksharing ? 1 : 0)});
4423}
4424
4425/// Converts an OpenMP taskgroup construct into LLVM IR using OpenMPIRBuilder.
4426static LogicalResult
4427convertOmpTaskgroupOp(omp::TaskgroupOp tgOp, llvm::IRBuilderBase &builder,
4428 LLVM::ModuleTranslation &moduleTranslation) {
4429 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
4430 if (failed(checkImplementationStatus(*tgOp)))
4431 return failure();
4432
4433 // Resolve and validate task_reduction declarations up front. We only handle
4434 // declare_reduction ops shaped like a non-byref scalar reduction in this
4435 // first cut; richer shapes (two-argument initializer, cleanup region,
4436 // missing combiner) require additional infrastructure.
4438 if (auto syms = tgOp.getTaskReductionSyms()) {
4439 redDecls.reserve(syms->size());
4440 for (auto sym : syms->getAsRange<SymbolRefAttr>()) {
4442 tgOp, sym);
4443 if (!decl)
4444 return tgOp.emitError()
4445 << "failed to resolve task_reduction declare_reduction symbol "
4446 << sym.getRootReference() << " in omp.taskgroup";
4447 if (decl.getInitializerRegion().front().getNumArguments() != 1)
4448 return tgOp.emitError("not yet implemented: task_reduction with "
4449 "two-argument initializer in omp.taskgroup");
4450 if (!decl.getCleanupRegion().empty())
4451 return tgOp.emitError("not yet implemented: task_reduction with "
4452 "cleanup region in omp.taskgroup");
4453 if (decl.getReductionRegion().empty())
4454 return tgOp.emitError("task_reduction declare_reduction is missing a "
4455 "combiner region");
4456 redDecls.push_back(decl);
4457 }
4458 }
4459
4460 auto bodyCB =
4461 [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
4462 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
4463 builder.restoreIP(codegenIP);
4464
4465 if (!redDecls.empty()) {
4467 origPtrs.reserve(redDecls.size());
4468 for (Value v : tgOp.getTaskReductionVars())
4469 origPtrs.push_back(moduleTranslation.lookupValue(v));
4470 if (!emitTaskReductionInitCall(redDecls, origPtrs, "__omp_taskred_",
4471 builder, allocaIP, moduleTranslation))
4472 return llvm::createStringError(
4473 llvm::inconvertibleErrorCode(),
4474 "failed to emit task_reduction initialization for omp.taskgroup");
4475 }
4476
4477 // Inside the taskgroup body, each task_reduction block argument refers to
4478 // the same shared/original storage that the runtime now knows about via
4479 // the descriptor array. Inner tasks that declare in_reduction look up
4480 // per-task private copies through the runtime; the taskgroup body itself
4481 // uses the original variable.
4482 for (auto [i, blockArg] :
4483 llvm::enumerate(tgOp.getRegion().getArguments())) {
4484 llvm::Value *orig =
4485 moduleTranslation.lookupValue(tgOp.getTaskReductionVars()[i]);
4486 moduleTranslation.mapValue(blockArg, orig);
4487 }
4488
4489 return convertOmpOpRegions(tgOp.getRegion(), "omp.taskgroup.region",
4490 builder, moduleTranslation)
4491 .takeError();
4492 };
4493
4495 InsertPointTy allocaIP =
4496 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
4497 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4498 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
4499 moduleTranslation.getOpenMPBuilder()->createTaskgroup(
4500 ompLoc, allocaIP, deallocBlocks, bodyCB);
4501
4502 if (failed(handleError(afterIP, *tgOp)))
4503 return failure();
4504
4505 builder.restoreIP(*afterIP);
4506 return success();
4507}
4508
4509static LogicalResult
4510convertOmpInteropInitOp(omp::InteropInitOp initOp, llvm::IRBuilderBase &builder,
4511 LLVM::ModuleTranslation &moduleTranslation) {
4512 if (!initOp.getDependVars().empty() || initOp.getDependKinds() ||
4513 !initOp.getDependIterated().empty() || initOp.getDependIteratedKinds())
4514 return initOp.emitError()
4515 << "not yet implemented: Unhandled clause depend in "
4516 << omp::InteropInitOp::getOperationName() << " operation";
4517
4518 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4519 llvm::Value *interopVar =
4520 moduleTranslation.lookupValue(initOp.getInteropVar());
4521 llvm::Value *device = initOp.getDevice()
4522 ? moduleTranslation.lookupValue(initOp.getDevice())
4523 : nullptr;
4524
4525 // TODO: Handle depend clauses when supported.
4526 llvm::Value *numDeps = llvm::ConstantInt::get(builder.getInt32Ty(), 0);
4527 llvm::Value *depArray = llvm::ConstantPointerNull::get(builder.getPtrTy());
4528 bool hasNowait = initOp.getNowait();
4529
4530 // A single `init` clause may list both `target` and `targetsync`, but the
4531 // runtime init call takes a single interop-type. Collapse the set to one
4532 // value, matching Clang: if `target` is present use Target, otherwise
4533 // TargetSync. The offload runtime object model supports only one type per
4534 // object; representing both would require a runtime change.
4535 bool hasTarget = false, hasTargetSync = false;
4536 for (mlir::Attribute typeAttr : initOp.getInteropTypes()) {
4537 switch (cast<omp::InteropTypeAttr>(typeAttr).getValue()) {
4538 case omp::InteropType::target:
4539 hasTarget = true;
4540 break;
4541 case omp::InteropType::targetsync:
4542 hasTargetSync = true;
4543 break;
4544 }
4545 }
4546 llvm::omp::OMPInteropType interopType =
4547 (!hasTarget && hasTargetSync) ? llvm::omp::OMPInteropType::TargetSync
4548 : llvm::omp::OMPInteropType::Target;
4549 ompBuilder->createOMPInteropInit(builder, interopVar, interopType, device,
4550 numDeps, depArray, hasNowait);
4551 return success();
4552}
4553
4554static LogicalResult
4555convertOmpInteropDestroyOp(omp::InteropDestroyOp destroyOp,
4556 llvm::IRBuilderBase &builder,
4557 LLVM::ModuleTranslation &moduleTranslation) {
4558 if (!destroyOp.getDependVars().empty() || destroyOp.getDependKinds() ||
4559 !destroyOp.getDependIterated().empty() ||
4560 destroyOp.getDependIteratedKinds())
4561 return destroyOp.emitError()
4562 << "not yet implemented: Unhandled clause depend in "
4563 << omp::InteropDestroyOp::getOperationName() << " operation";
4564
4565 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4566 llvm::Value *interopVar =
4567 moduleTranslation.lookupValue(destroyOp.getInteropVar());
4568 llvm::Value *device =
4569 destroyOp.getDevice()
4570 ? moduleTranslation.lookupValue(destroyOp.getDevice())
4571 : nullptr;
4572
4573 llvm::Value *numDeps = llvm::ConstantInt::get(builder.getInt32Ty(), 0);
4574 llvm::Value *depArray = llvm::ConstantPointerNull::get(builder.getPtrTy());
4575 bool hasNowait = destroyOp.getNowait();
4576
4577 ompBuilder->createOMPInteropDestroy(builder, interopVar, device, numDeps,
4578 depArray, hasNowait);
4579 return success();
4580}
4581
4582static LogicalResult
4583convertOmpInteropUseOp(omp::InteropUseOp useOp, llvm::IRBuilderBase &builder,
4584 LLVM::ModuleTranslation &moduleTranslation) {
4585 if (!useOp.getDependVars().empty() || useOp.getDependKinds() ||
4586 !useOp.getDependIterated().empty() || useOp.getDependIteratedKinds())
4587 return useOp.emitError()
4588 << "not yet implemented: Unhandled clause depend in "
4589 << omp::InteropUseOp::getOperationName() << " operation";
4590
4591 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4592 llvm::Value *interopVar =
4593 moduleTranslation.lookupValue(useOp.getInteropVar());
4594 llvm::Value *device = useOp.getDevice()
4595 ? moduleTranslation.lookupValue(useOp.getDevice())
4596 : nullptr;
4597
4598 llvm::Value *numDeps = llvm::ConstantInt::get(builder.getInt32Ty(), 0);
4599 llvm::Value *depArray = llvm::ConstantPointerNull::get(builder.getPtrTy());
4600 bool hasNowait = useOp.getNowait();
4601
4602 ompBuilder->createOMPInteropUse(builder, interopVar, device, numDeps,
4603 depArray, hasNowait);
4604 return success();
4605}
4606
4607static LogicalResult
4608convertOmpTaskwaitOp(omp::TaskwaitOp twOp, llvm::IRBuilderBase &builder,
4609 LLVM::ModuleTranslation &moduleTranslation) {
4610 if (failed(checkImplementationStatus(*twOp)))
4611 return failure();
4612
4613 llvm::OpenMPIRBuilder::DependenciesInfo dds;
4614 if (failed(buildDependData(
4615 twOp.getDependVars(), twOp.getDependKinds(), twOp.getDependIterated(),
4616 twOp.getDependIteratedKinds(), builder, moduleTranslation, dds))) {
4617 return failure();
4618 }
4619
4620 moduleTranslation.getOpenMPBuilder()->createTaskwait(builder, dds);
4621 if (dds.DepArray) {
4622 builder.CreateFree(dds.DepArray);
4623 }
4624
4625 return success();
4626}
4627
4628/// Converts an OpenMP workshare loop into LLVM IR using OpenMPIRBuilder.
4629static LogicalResult
4630convertOmpWsloop(Operation &opInst, llvm::IRBuilderBase &builder,
4631 LLVM::ModuleTranslation &moduleTranslation) {
4632 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4633 auto wsloopOp = cast<omp::WsloopOp>(opInst);
4634 if (failed(checkImplementationStatus(opInst)))
4635 return failure();
4636
4637 auto loopOp = cast<omp::LoopNestOp>(wsloopOp.getWrappedLoop());
4638 llvm::ArrayRef<bool> isByRef = getIsByRef(wsloopOp.getReductionByref());
4639 assert(isByRef.size() == wsloopOp.getNumReductionVars());
4640
4641 // Static is the default.
4642 auto schedule =
4643 wsloopOp.getScheduleKind().value_or(omp::ClauseScheduleKind::Static);
4644
4645 // Find the loop configuration.
4646 llvm::Value *step = moduleTranslation.lookupValue(loopOp.getLoopSteps()[0]);
4647 llvm::Type *ivType = step->getType();
4648 llvm::Value *chunk = nullptr;
4649 if (wsloopOp.getScheduleChunk()) {
4650 llvm::Value *chunkVar =
4651 moduleTranslation.lookupValue(wsloopOp.getScheduleChunk());
4652 chunk = builder.CreateSExtOrTrunc(chunkVar, ivType);
4653 }
4654
4655 omp::DistributeOp distributeOp = nullptr;
4656 llvm::Value *distScheduleChunk = nullptr;
4657 bool hasDistSchedule = false;
4658 if (llvm::isa_and_present<omp::DistributeOp>(opInst.getParentOp())) {
4659 distributeOp = cast<omp::DistributeOp>(opInst.getParentOp());
4660 hasDistSchedule = distributeOp.getDistScheduleStatic();
4661 if (distributeOp.getDistScheduleChunkSize()) {
4662 llvm::Value *chunkVar = moduleTranslation.lookupValue(
4663 distributeOp.getDistScheduleChunkSize());
4664 distScheduleChunk = builder.CreateSExtOrTrunc(chunkVar, ivType);
4665 }
4666 }
4667
4668 PrivateVarsInfo privateVarsInfo(wsloopOp);
4669
4671 collectReductionDecls(wsloopOp, reductionDecls);
4672
4673 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
4674 findAllocInsertPoints(builder, moduleTranslation);
4675
4676 SmallVector<llvm::Value *> privateReductionVariables(
4677 wsloopOp.getNumReductionVars());
4678
4680 wsloopOp, builder, moduleTranslation, privateVarsInfo, allocaIP);
4681 if (handleError(afterAllocas, opInst).failed())
4682 return failure();
4683
4684 DenseMap<Value, llvm::Value *> reductionVariableMap;
4685
4686 MutableArrayRef<BlockArgument> reductionArgs =
4687 cast<omp::BlockArgOpenMPOpInterface>(opInst).getReductionBlockArgs();
4688
4689 SmallVector<DeferredStore> deferredStores;
4690
4691 if (failed(allocReductionVars(wsloopOp, reductionArgs, builder,
4692 moduleTranslation, allocaIP, reductionDecls,
4693 privateReductionVariables, reductionVariableMap,
4694 deferredStores, isByRef)))
4695 return failure();
4696
4697 if (handleError(initPrivateVars(builder, moduleTranslation, privateVarsInfo),
4698 opInst)
4699 .failed())
4700 return failure();
4701
4702 if (failed(copyFirstPrivateVars(
4703 wsloopOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
4704 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
4705 wsloopOp.getPrivateNeedsBarrier())))
4706 return failure();
4707
4708 assert(afterAllocas.get()->getSinglePredecessor());
4709 if (failed(initReductionVars(wsloopOp, reductionArgs, builder,
4710 moduleTranslation,
4711 afterAllocas.get()->getSinglePredecessor(),
4712 reductionDecls, privateReductionVariables,
4713 reductionVariableMap, isByRef, deferredStores)))
4714 return failure();
4715
4716 // For `reduction(task, ...)` open a task-reduction scope for the worksharing
4717 // loop. Participating explicit tasks accumulate into the per-thread private
4718 // copies, which the worksharing reduction then combines across threads.
4719 bool isTaskReductionMod =
4720 wsloopOp.getReductionMod() == omp::ReductionModifier::task &&
4721 wsloopOp.getNumReductionVars() > 0;
4722 if (isTaskReductionMod &&
4723 !emitTaskReductionInitCall(reductionDecls, privateReductionVariables,
4724 "__omp_taskred_mod_", builder, allocaIP,
4725 moduleTranslation, /*isModifier=*/true,
4726 /*isWorksharing=*/true))
4727 return wsloopOp.emitError(
4728 "failed to emit task reduction modifier initialization");
4729
4730 // TODO: Handle doacross loops when the ordered clause has a parameter.
4731 bool isOrdered = wsloopOp.getOrdered().has_value();
4732 std::optional<omp::ScheduleModifier> scheduleMod = wsloopOp.getScheduleMod();
4733 bool isSimd = wsloopOp.getScheduleSimd();
4734 bool loopNeedsBarrier = !wsloopOp.getNowait();
4735
4736 // The only legal way for the direct parent to be omp.distribute is that this
4737 // represents 'distribute parallel do'. Otherwise, this is a regular
4738 // worksharing loop.
4739 llvm::omp::WorksharingLoopType workshareLoopType =
4740 llvm::isa_and_present<omp::DistributeOp>(opInst.getParentOp())
4741 ? llvm::omp::WorksharingLoopType::DistributeForStaticLoop
4742 : llvm::omp::WorksharingLoopType::ForStaticLoop;
4743
4744 SmallVector<llvm::UncondBrInst *> cancelTerminators;
4745 pushCancelFinalizationCB(cancelTerminators, builder, *ompBuilder, wsloopOp,
4746 llvm::omp::Directive::OMPD_for);
4747
4748 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4749
4750 // Initialize linear variables and linear step
4751 LinearClauseProcessor linearClauseProcessor;
4752
4753 if (!wsloopOp.getLinearVars().empty()) {
4754 auto linearVarTypes = wsloopOp.getLinearVarTypes().value();
4755 for (mlir::Attribute linearVarType : linearVarTypes)
4756 linearClauseProcessor.registerType(moduleTranslation, linearVarType);
4757
4758 for (auto [idx, linearVar] : llvm::enumerate(wsloopOp.getLinearVars()))
4759 linearClauseProcessor.createLinearVar(
4760 builder, moduleTranslation, moduleTranslation.lookupValue(linearVar),
4761 idx);
4762 for (mlir::Value linearStep : wsloopOp.getLinearStepVars())
4763 linearClauseProcessor.initLinearStep(moduleTranslation, linearStep);
4764 }
4765
4767 wsloopOp.getRegion(), "omp.wsloop.region", builder, moduleTranslation);
4768
4769 if (failed(handleError(regionBlock, opInst)))
4770 return failure();
4771
4772 llvm::CanonicalLoopInfo *loopInfo = findCurrentLoopInfo(moduleTranslation);
4773
4774 // Emit Initialization and Update IR for linear variables
4775 if (!wsloopOp.getLinearVars().empty()) {
4776 linearClauseProcessor.initLinearVar(builder, moduleTranslation,
4777 loopInfo->getPreheader());
4778 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterBarrierIP =
4779 moduleTranslation.getOpenMPBuilder()->createBarrier(
4780 builder, llvm::omp::OMPD_barrier);
4781 if (failed(handleError(afterBarrierIP, *loopOp)))
4782 return failure();
4783 builder.restoreIP(*afterBarrierIP);
4784 linearClauseProcessor.updateLinearVar(builder, loopInfo->getBody(),
4785 loopInfo->getIndVar());
4786 linearClauseProcessor.splitLinearFiniBB(builder, loopInfo->getExit());
4787 }
4788
4789 builder.SetInsertPoint(*regionBlock, (*regionBlock)->begin());
4790
4791 // Check if we can generate no-loop kernel
4792 bool noLoopMode = false;
4793 omp::TargetOp targetOp = wsloopOp->getParentOfType<mlir::omp::TargetOp>();
4794 if (targetOp &&
4795 targetOp.getKernelType() == omp::TargetExecMode::spmd_no_loop) {
4796 Operation *targetCapturedOp =
4797 cast<omp::ComposableOpInterface>(*targetOp).findCapturedOp();
4798 // We need this check because, without it, noLoopMode would be set to true
4799 // for every omp.wsloop nested inside a no-loop SPMD target region, even if
4800 // that loop is not the top-level SPMD one.
4801 if (loopOp == targetCapturedOp)
4802 noLoopMode = true;
4803 }
4804
4805 for (size_t index = 0; index < wsloopOp.getLinearVars().size(); index++)
4806 linearClauseProcessor.rewriteInPlace(builder, loopInfo->getBody(),
4807 loopInfo->getLatch(), index);
4808
4809 llvm::OpenMPIRBuilder::InsertPointOrErrorTy wsloopIP =
4810 ompBuilder->applyWorkshareLoop(
4811 ompLoc.DL, loopInfo, allocaIP, loopNeedsBarrier,
4812 convertToScheduleKind(schedule), chunk, isSimd,
4813 scheduleMod == omp::ScheduleModifier::monotonic,
4814 scheduleMod == omp::ScheduleModifier::nonmonotonic, isOrdered,
4815 workshareLoopType, noLoopMode, hasDistSchedule, distScheduleChunk);
4816
4817 if (failed(handleError(wsloopIP, opInst)))
4818 return failure();
4819
4820 // Emit finalization and in-place rewrites for linear vars.
4821 if (!wsloopOp.getLinearVars().empty()) {
4822 llvm::OpenMPIRBuilder::InsertPointTy oldIP = builder.saveIP();
4823 assert(loopInfo->getLastIter() &&
4824 "`lastiter` in CanonicalLoopInfo is nullptr");
4825 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterBarrierIP =
4826 linearClauseProcessor.finalizeLinearVar(builder, moduleTranslation,
4827 loopInfo->getLastIter());
4828 if (failed(handleError(afterBarrierIP, *loopOp)))
4829 return failure();
4830
4831 builder.restoreIP(oldIP);
4832 }
4833
4834 // Set the correct branch target for task cancellation
4835 popCancelFinalizationCB(cancelTerminators, *ompBuilder, wsloopIP.get());
4836
4837 // Close the task-reduction scope before the worksharing reduction combine.
4838 if (isTaskReductionMod)
4839 emitTaskReductionModifierFini(/*isWorksharing=*/true, builder,
4840 moduleTranslation);
4841
4842 // Process the reductions if required.
4843 if (failed(createReductionsAndCleanup(
4844 wsloopOp, builder, moduleTranslation, allocaIP, reductionDecls,
4845 privateReductionVariables, isByRef, wsloopOp.getNowait(),
4846 /*isTeamsReduction=*/false)))
4847 return failure();
4848
4849 return cleanupPrivateVars(wsloopOp, builder, moduleTranslation,
4850 wsloopOp.getLoc(), privateVarsInfo);
4851}
4852
4853/// Converts the OpenMP parallel operation to LLVM IR.
4854static LogicalResult
4855convertOmpParallel(omp::ParallelOp opInst, llvm::IRBuilderBase &builder,
4856 LLVM::ModuleTranslation &moduleTranslation) {
4857 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
4858 ArrayRef<bool> isByRef = getIsByRef(opInst.getReductionByref());
4859 assert(isByRef.size() == opInst.getNumReductionVars());
4860 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4861 bool isCancellable = constructIsCancellable(opInst);
4862
4863 if (failed(checkImplementationStatus(*opInst)))
4864 return failure();
4865
4866 PrivateVarsInfo privateVarsInfo(opInst);
4867 for (Value allocatorVar : opInst.getAllocatorVars()) {
4868 if (privateVarsInfo.convertedAllocators.contains(allocatorVar))
4869 continue;
4870
4871 llvm::Value *allocator = moduleTranslation.lookupValue(allocatorVar);
4872 if (!allocator)
4873 return opInst.emitError("failed to translate OpenMP allocator operand");
4874 if (allocator->getType()->isIntegerTy())
4875 allocator = builder.CreateIntToPtr(allocator, builder.getPtrTy());
4876 else if (allocator->getType()->isPointerTy())
4877 allocator = builder.CreatePointerBitCastOrAddrSpaceCast(
4878 allocator, builder.getPtrTy());
4879 else
4880 return opInst.emitError(
4881 "OpenMP allocator operand must have integer or pointer type");
4882
4883 privateVarsInfo.convertedAllocators.try_emplace(allocatorVar, allocator);
4884 }
4885
4886 // Collect reduction declarations
4888 collectReductionDecls(opInst, reductionDecls);
4889 SmallVector<llvm::Value *> privateReductionVariables(
4890 opInst.getNumReductionVars());
4891 SmallVector<DeferredStore> deferredStores;
4892 // Only open a task-reduction scope when the `task` modifier is present and
4893 // there are reduction variables to combine; otherwise the matching fini in
4894 // the reduction-combine path (guarded by getNumReductionVars() > 0) would be
4895 // skipped, leaving the modifier init unbalanced.
4896 bool isTaskReductionMod =
4897 opInst.getReductionMod() == omp::ReductionModifier::task &&
4898 opInst.getNumReductionVars() > 0;
4899
4900 auto bodyGenCB =
4901 [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
4902 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
4904 opInst, builder, moduleTranslation, privateVarsInfo, allocaIP);
4905 if (handleError(afterAllocas, *opInst).failed())
4906 return llvm::make_error<PreviouslyReportedError>();
4907
4908 // Allocate reduction vars
4909 DenseMap<Value, llvm::Value *> reductionVariableMap;
4910
4911 MutableArrayRef<BlockArgument> reductionArgs =
4912 cast<omp::BlockArgOpenMPOpInterface>(*opInst).getReductionBlockArgs();
4913
4914 allocaIP =
4915 InsertPointTy(allocaIP.getBlock(),
4916 allocaIP.getBlock()->getTerminator()->getIterator());
4917
4918 if (failed(allocReductionVars(
4919 opInst, reductionArgs, builder, moduleTranslation, allocaIP,
4920 reductionDecls, privateReductionVariables, reductionVariableMap,
4921 deferredStores, isByRef)))
4922 return llvm::make_error<PreviouslyReportedError>();
4923
4924 assert(afterAllocas.get()->getSinglePredecessor());
4925 builder.restoreIP(codeGenIP);
4926
4927 if (handleError(
4928 initPrivateVars(builder, moduleTranslation, privateVarsInfo),
4929 *opInst)
4930 .failed())
4931 return llvm::make_error<PreviouslyReportedError>();
4932
4933 if (failed(copyFirstPrivateVars(
4934 opInst, builder, moduleTranslation, privateVarsInfo.mlirVars,
4935 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
4936 opInst.getPrivateNeedsBarrier())))
4937 return llvm::make_error<PreviouslyReportedError>();
4938
4939 if (failed(
4940 initReductionVars(opInst, reductionArgs, builder, moduleTranslation,
4941 afterAllocas.get()->getSinglePredecessor(),
4942 reductionDecls, privateReductionVariables,
4943 reductionVariableMap, isByRef, deferredStores)))
4944 return llvm::make_error<PreviouslyReportedError>();
4945
4946 // For `reduction(task, ...)` open a task-reduction scope so participating
4947 // explicit tasks accumulate into the per-thread private copies; the
4948 // parallel reduction then combines those copies across the team.
4949 if (isTaskReductionMod &&
4950 !emitTaskReductionInitCall(reductionDecls, privateReductionVariables,
4951 "__omp_taskred_mod_", builder, allocaIP,
4952 moduleTranslation, /*isModifier=*/true,
4953 /*isWorksharing=*/false))
4954 return llvm::createStringError(
4955 "failed to emit task reduction modifier initialization");
4956
4957 // Save the alloca insertion point on ModuleTranslation stack for use in
4958 // nested regions.
4960 moduleTranslation, allocaIP, deallocBlocks);
4961
4962 // ParallelOp has only one region associated with it.
4964 opInst.getRegion(), "omp.par.region", builder, moduleTranslation);
4965 if (!regionBlock)
4966 return regionBlock.takeError();
4967
4968 // Process the reductions if required.
4969 if (opInst.getNumReductionVars() > 0) {
4970 // Collect reduction info
4972 SmallVector<OwningAtomicReductionGen> owningAtomicReductionGens;
4974 owningReductionGenRefDataPtrGens;
4976 collectReductionInfo(opInst, builder, moduleTranslation, reductionDecls,
4977 owningReductionGens, owningAtomicReductionGens,
4978 owningReductionGenRefDataPtrGens,
4979 privateReductionVariables, reductionInfos, isByRef);
4980
4981 // Move to region cont block
4982 builder.SetInsertPoint((*regionBlock)->getTerminator());
4983
4984 // Close the task-reduction scope before the per-thread reduction
4985 // contributions are combined across the team.
4986 if (isTaskReductionMod)
4987 emitTaskReductionModifierFini(/*isWorksharing=*/false, builder,
4988 moduleTranslation);
4989
4990 // Generate reductions from info
4991 llvm::UnreachableInst *tempTerminator = builder.CreateUnreachable();
4992 builder.SetInsertPoint(tempTerminator);
4993
4994 llvm::OpenMPIRBuilder::InsertPointOrErrorTy contInsertPoint =
4995 ompBuilder->createReductions(builder, allocaIP, reductionInfos,
4996 isByRef,
4997 /*IsNoWait=*/false,
4998 /*IsTeamsReduction=*/false);
4999 if (!contInsertPoint)
5000 return contInsertPoint.takeError();
5001
5002 if (!contInsertPoint->getBlock())
5003 return llvm::make_error<PreviouslyReportedError>();
5004
5005 tempTerminator->eraseFromParent();
5006 builder.restoreIP(*contInsertPoint);
5007 }
5008
5009 return llvm::Error::success();
5010 };
5011
5012 auto privCB = [](InsertPointTy allocaIP, InsertPointTy codeGenIP,
5013 llvm::Value &, llvm::Value &val, llvm::Value *&replVal) {
5014 // tell OpenMPIRBuilder not to do anything. We handled Privatisation in
5015 // bodyGenCB.
5016 replVal = &val;
5017 return codeGenIP;
5018 };
5019
5020 // TODO: Perform finalization actions for variables. This has to be
5021 // called for variables which have destructors/finalizers.
5022 auto finiCB = [&](InsertPointTy codeGenIP) -> llvm::Error {
5023 InsertPointTy oldIP = builder.saveIP();
5024 builder.restoreIP(codeGenIP);
5025
5026 // if the reduction has a cleanup region, inline it here to finalize the
5027 // reduction variables
5028 SmallVector<Region *> reductionCleanupRegions;
5029 llvm::transform(reductionDecls, std::back_inserter(reductionCleanupRegions),
5030 [](omp::DeclareReductionOp reductionDecl) {
5031 return &reductionDecl.getCleanupRegion();
5032 });
5033 if (failed(inlineOmpRegionCleanup(
5034 reductionCleanupRegions, privateReductionVariables,
5035 moduleTranslation, builder, "omp.reduction.cleanup")))
5036 return llvm::createStringError(
5037 "failed to inline `cleanup` region of `omp.declare_reduction`");
5038
5039 if (failed(cleanupPrivateVars(opInst, builder, moduleTranslation,
5040 opInst.getLoc(), privateVarsInfo)))
5041 return llvm::make_error<PreviouslyReportedError>();
5042
5043 // If we could be performing cancellation, add the cancellation barrier on
5044 // the way out of the outlined region.
5045 if (isCancellable) {
5046 auto IPOrErr = ompBuilder->createBarrier(
5047 llvm::OpenMPIRBuilder::LocationDescription(builder),
5048 llvm::omp::Directive::OMPD_unknown,
5049 /* ForceSimpleCall */ false,
5050 /* CheckCancelFlag */ false);
5051 if (!IPOrErr)
5052 return IPOrErr.takeError();
5053 }
5054
5055 builder.restoreIP(oldIP);
5056 return llvm::Error::success();
5057 };
5058
5059 llvm::Value *ifCond = nullptr;
5060 if (auto ifVar = opInst.getIfExpr())
5061 ifCond = moduleTranslation.lookupValue(ifVar);
5062 llvm::Value *numThreads = nullptr;
5063 if (!opInst.getNumThreadsVars().empty())
5064 numThreads = moduleTranslation.lookupValue(opInst.getNumThreads(0));
5065 auto pbKind = llvm::omp::OMP_PROC_BIND_default;
5066 if (auto bind = opInst.getProcBindKind())
5067 pbKind = getProcBindKind(*bind);
5068
5070 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
5071 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
5072 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5073
5074 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
5075 ompBuilder->createParallel(ompLoc, allocaIP, deallocBlocks, bodyGenCB,
5076 privCB, finiCB, ifCond, numThreads, pbKind,
5077 isCancellable);
5078
5079 if (failed(handleError(afterIP, *opInst)))
5080 return failure();
5081
5082 builder.restoreIP(*afterIP);
5083 return success();
5084}
5085
5086/// Convert Order attribute to llvm::omp::OrderKind.
5087static llvm::omp::OrderKind
5088convertOrderKind(std::optional<omp::ClauseOrderKind> o) {
5089 if (!o)
5090 return llvm::omp::OrderKind::OMP_ORDER_unknown;
5091 switch (*o) {
5092 case omp::ClauseOrderKind::Concurrent:
5093 return llvm::omp::OrderKind::OMP_ORDER_concurrent;
5094 }
5095 llvm_unreachable("Unknown ClauseOrderKind kind");
5096}
5097
5098/// Converts an OpenMP simd loop into LLVM IR using OpenMPIRBuilder.
5099static LogicalResult
5100convertOmpSimd(Operation &opInst, llvm::IRBuilderBase &builder,
5101 LLVM::ModuleTranslation &moduleTranslation) {
5102 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5103 auto simdOp = cast<omp::SimdOp>(opInst);
5104
5105 if (failed(checkImplementationStatus(opInst)))
5106 return failure();
5107
5108 PrivateVarsInfo privateVarsInfo(simdOp);
5109
5110 MutableArrayRef<BlockArgument> reductionArgs =
5111 cast<omp::BlockArgOpenMPOpInterface>(opInst).getReductionBlockArgs();
5112 DenseMap<Value, llvm::Value *> reductionVariableMap;
5113 SmallVector<llvm::Value *> privateReductionVariables(
5114 simdOp.getNumReductionVars());
5115 SmallVector<DeferredStore> deferredStores;
5117 collectReductionDecls(simdOp, reductionDecls);
5118 llvm::ArrayRef<bool> isByRef = getIsByRef(simdOp.getReductionByref());
5119 assert(isByRef.size() == simdOp.getNumReductionVars());
5120
5121 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
5122 findAllocInsertPoints(builder, moduleTranslation);
5123
5125 simdOp, builder, moduleTranslation, privateVarsInfo, allocaIP);
5126 if (handleError(afterAllocas, opInst).failed())
5127 return failure();
5128
5129 // Initialize linear variables and linear step
5130 LinearClauseProcessor linearClauseProcessor;
5131 if (linearClauseProcessor.initLinearIV(simdOp).failed())
5132 return failure();
5133
5134 if (!simdOp.getLinearVars().empty()) {
5135 auto linearVarTypes = simdOp.getLinearVarTypes().value();
5136 for (mlir::Attribute linearVarType : linearVarTypes)
5137 linearClauseProcessor.registerType(moduleTranslation, linearVarType);
5138 for (auto [idx, linearVar] : llvm::enumerate(simdOp.getLinearVars())) {
5139 bool isImplicit = false;
5140 for (auto [mlirPrivVar, llvmPrivateVar] : llvm::zip_equal(
5141 privateVarsInfo.mlirVars, privateVarsInfo.llvmVars)) {
5142 // If the linear variable is implicit, reuse the already
5143 // existing llvm::Value
5144 if (linearVar == mlirPrivVar) {
5145 isImplicit = true;
5146 linearClauseProcessor.createLinearVar(builder, moduleTranslation,
5147 llvmPrivateVar, idx);
5148 break;
5149 }
5150 }
5151
5152 if (!isImplicit)
5153 linearClauseProcessor.createLinearVar(
5154 builder, moduleTranslation,
5155 moduleTranslation.lookupValue(linearVar), idx);
5156 }
5157 for (mlir::Value linearStep : simdOp.getLinearStepVars())
5158 linearClauseProcessor.initLinearStep(moduleTranslation, linearStep);
5159 }
5160
5161 if (failed(allocReductionVars(simdOp, reductionArgs, builder,
5162 moduleTranslation, allocaIP, reductionDecls,
5163 privateReductionVariables, reductionVariableMap,
5164 deferredStores, isByRef)))
5165 return failure();
5166
5167 if (handleError(initPrivateVars(builder, moduleTranslation, privateVarsInfo),
5168 opInst)
5169 .failed())
5170 return failure();
5171
5172 // No call to copyFirstPrivateVars because FIRSTPRIVATE is not allowed for
5173 // SIMD.
5174
5175 assert(afterAllocas.get()->getSinglePredecessor());
5176 if (failed(initReductionVars(simdOp, reductionArgs, builder,
5177 moduleTranslation,
5178 afterAllocas.get()->getSinglePredecessor(),
5179 reductionDecls, privateReductionVariables,
5180 reductionVariableMap, isByRef, deferredStores)))
5181 return failure();
5182
5183 llvm::ConstantInt *simdlen = nullptr;
5184 if (std::optional<uint64_t> simdlenVar = simdOp.getSimdlen())
5185 simdlen = builder.getInt64(simdlenVar.value());
5186
5187 llvm::ConstantInt *safelen = nullptr;
5188 if (std::optional<uint64_t> safelenVar = simdOp.getSafelen())
5189 safelen = builder.getInt64(safelenVar.value());
5190
5191 llvm::MapVector<llvm::Value *, llvm::Value *> alignedVars;
5192 llvm::omp::OrderKind order = convertOrderKind(simdOp.getOrder());
5193
5194 llvm::BasicBlock *sourceBlock = builder.GetInsertBlock();
5195 std::optional<ArrayAttr> alignmentValues = simdOp.getAlignments();
5196 mlir::OperandRange operands = simdOp.getAlignedVars();
5197 for (size_t i = 0; i < operands.size(); ++i) {
5198 llvm::Value *alignment = nullptr;
5199 llvm::Value *llvmVal = moduleTranslation.lookupValue(operands[i]);
5200 llvm::Type *ty = llvmVal->getType();
5201
5202 auto intAttr = cast<IntegerAttr>((*alignmentValues)[i]);
5203 alignment = builder.getInt64(intAttr.getInt());
5204 assert(ty->isPointerTy() && "Invalid type for aligned variable");
5205 assert(alignment && "Invalid alignment value");
5206
5207 // Check if the alignment value is not a power of 2. If so, skip emitting
5208 // alignment.
5209 if (!intAttr.getValue().isPowerOf2())
5210 continue;
5211
5212 auto curInsert = builder.saveIP();
5213 builder.SetInsertPoint(sourceBlock);
5214 llvmVal = builder.CreateLoad(ty, llvmVal);
5215 builder.restoreIP(curInsert);
5216 alignedVars[llvmVal] = alignment;
5217 }
5218
5220 simdOp.getRegion(), "omp.simd.region", builder, moduleTranslation);
5221
5222 if (failed(handleError(regionBlock, opInst)))
5223 return failure();
5224
5225 llvm::CanonicalLoopInfo *loopInfo = findCurrentLoopInfo(moduleTranslation);
5226 // Emit Initialization for linear variables
5227 if (simdOp.getLinearVars().size()) {
5228 linearClauseProcessor.initLinearVar(builder, moduleTranslation,
5229 loopInfo->getPreheader());
5230
5231 linearClauseProcessor.updateLinearVar(builder, loopInfo->getBody(),
5232 loopInfo->getIndVar());
5233 }
5234 builder.SetInsertPoint(*regionBlock, (*regionBlock)->begin());
5235
5236 for (size_t index = 0; index < simdOp.getLinearVars().size(); index++)
5237 linearClauseProcessor.rewriteInPlace(builder, loopInfo->getBody(),
5238 loopInfo->getLatch(), index);
5239
5240 ompBuilder->applySimd(loopInfo, alignedVars,
5241 simdOp.getIfExpr()
5242 ? moduleTranslation.lookupValue(simdOp.getIfExpr())
5243 : nullptr,
5244 order, simdlen, safelen);
5245
5246 linearClauseProcessor.updateLinearIV(builder, moduleTranslation);
5247 linearClauseProcessor.emitStoresForLinearVar(builder);
5248
5249 // We now need to reduce the per-simd-lane reduction variable into the
5250 // original variable. This works a bit differently to other reductions (e.g.
5251 // wsloop) because we don't need to call into the OpenMP runtime to handle
5252 // threads: everything happened in this one thread.
5253 for (auto [i, tuple] : llvm::enumerate(
5254 llvm::zip(reductionDecls, isByRef, simdOp.getReductionVars(),
5255 privateReductionVariables))) {
5256 auto [decl, byRef, reductionVar, privateReductionVar] = tuple;
5257
5258 OwningReductionGen gen = makeReductionGen(decl, builder, moduleTranslation);
5259 llvm::Value *originalVariable = moduleTranslation.lookupValue(reductionVar);
5260 llvm::Type *reductionType = moduleTranslation.convertType(decl.getType());
5261
5262 // We have one less load for by-ref case because that load is now inside of
5263 // the reduction region.
5264 llvm::Value *redValue = originalVariable;
5265 if (!byRef)
5266 redValue =
5267 builder.CreateLoad(reductionType, redValue, "red.value." + Twine(i));
5268 llvm::Value *privateRedValue = builder.CreateLoad(
5269 reductionType, privateReductionVar, "red.private.value." + Twine(i));
5270 llvm::Value *reduced;
5271
5272 auto res = gen(builder.saveIP(), redValue, privateRedValue, reduced);
5273 if (failed(handleError(res, opInst)))
5274 return failure();
5275 builder.restoreIP(res.get());
5276
5277 // For by-ref case, the store is inside of the reduction region.
5278 if (!byRef)
5279 builder.CreateStore(reduced, originalVariable);
5280 }
5281
5282 // After the construct, deallocate private reduction variables.
5283 SmallVector<Region *> reductionRegions;
5284 llvm::transform(reductionDecls, std::back_inserter(reductionRegions),
5285 [](omp::DeclareReductionOp reductionDecl) {
5286 return &reductionDecl.getCleanupRegion();
5287 });
5288 if (failed(inlineOmpRegionCleanup(reductionRegions, privateReductionVariables,
5289 moduleTranslation, builder,
5290 "omp.reduction.cleanup")))
5291 return failure();
5292
5293 return cleanupPrivateVars(simdOp, builder, moduleTranslation, simdOp.getLoc(),
5294 privateVarsInfo);
5295}
5296
5297/// Converts an OpenMP loop nest into LLVM IR using OpenMPIRBuilder.
5298static LogicalResult
5299convertOmpLoopNest(Operation &opInst, llvm::IRBuilderBase &builder,
5300 LLVM::ModuleTranslation &moduleTranslation) {
5301 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5302 auto loopOp = cast<omp::LoopNestOp>(opInst);
5303
5304 if (failed(checkImplementationStatus(opInst)))
5305 return failure();
5306
5307 // Set up the source location value for OpenMP runtime.
5308 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5309
5310 // Generator of the canonical loop body.
5313 auto bodyGen = [&](llvm::OpenMPIRBuilder::InsertPointTy ip,
5314 llvm::Value *iv) -> llvm::Error {
5315 // Make sure further conversions know about the induction variable.
5316 moduleTranslation.mapValue(
5317 loopOp.getRegion().front().getArgument(loopInfos.size()), iv);
5318
5319 // Capture the body insertion point for use in nested loops. BodyIP of the
5320 // CanonicalLoopInfo always points to the beginning of the entry block of
5321 // the body.
5322 bodyInsertPoints.push_back(ip);
5323
5324 if (loopInfos.size() != loopOp.getNumLoops() - 1)
5325 return llvm::Error::success();
5326
5327 // Convert the body of the loop.
5328 builder.restoreIP(ip);
5330 loopOp.getRegion(), "omp.loop_nest.region", builder, moduleTranslation);
5331 if (!regionBlock)
5332 return regionBlock.takeError();
5333
5334 builder.SetInsertPoint(*regionBlock, (*regionBlock)->begin());
5335 return llvm::Error::success();
5336 };
5337
5338 // Delegate actual loop construction to the OpenMP IRBuilder.
5339 // TODO: this currently assumes omp.loop_nest is semantically similar to SCF
5340 // loop, i.e. it has a positive step, uses signed integer semantics.
5341 // Reconsider this code when the nested loop operation clearly supports more
5342 // cases.
5343 for (unsigned i = 0, e = loopOp.getNumLoops(); i < e; ++i) {
5344 llvm::Value *lowerBound =
5345 moduleTranslation.lookupValue(loopOp.getLoopLowerBounds()[i]);
5346 llvm::Value *upperBound =
5347 moduleTranslation.lookupValue(loopOp.getLoopUpperBounds()[i]);
5348 llvm::Value *step = moduleTranslation.lookupValue(loopOp.getLoopSteps()[i]);
5349
5350 // Make sure loop trip count are emitted in the preheader of the outermost
5351 // loop at the latest so that they are all available for the new collapsed
5352 // loop will be created below.
5353 llvm::OpenMPIRBuilder::LocationDescription loc = ompLoc;
5354 llvm::OpenMPIRBuilder::InsertPointTy computeIP = ompLoc.IP;
5355 if (i != 0) {
5356 loc = llvm::OpenMPIRBuilder::LocationDescription(bodyInsertPoints.back(),
5357 ompLoc.DL);
5358 computeIP = loopInfos.front()->getPreheaderIP();
5359 }
5360
5362 ompBuilder->createCanonicalLoop(
5363 loc, bodyGen, lowerBound, upperBound, step,
5364 /*IsSigned=*/true, loopOp.getLoopInclusive(), computeIP);
5365
5366 if (failed(handleError(loopResult, *loopOp)))
5367 return failure();
5368
5369 loopInfos.push_back(*loopResult);
5370 }
5371
5372 llvm::OpenMPIRBuilder::InsertPointTy afterIP =
5373 loopInfos.front()->getAfterIP();
5374
5375 // Do tiling.
5376 if (const auto &tiles = loopOp.getTileSizes()) {
5377 llvm::Type *ivType = loopInfos.front()->getIndVarType();
5379
5380 for (auto tile : tiles.value()) {
5381 llvm::Value *tileVal = llvm::ConstantInt::get(ivType, tile);
5382 tileSizes.push_back(tileVal);
5383 }
5384
5385 std::vector<llvm::CanonicalLoopInfo *> newLoops =
5386 ompBuilder->tileLoops(ompLoc.DL, loopInfos, tileSizes);
5387
5388 // Update afterIP to get the correct insertion point after
5389 // tiling.
5390 llvm::BasicBlock *afterBB = newLoops.front()->getAfter();
5391 llvm::BasicBlock *afterAfterBB = afterBB->getSingleSuccessor();
5392 afterIP = {afterAfterBB, afterAfterBB->begin()};
5393
5394 // Update the loop infos.
5395 loopInfos.clear();
5396 for (const auto &newLoop : newLoops)
5397 loopInfos.push_back(newLoop);
5398 } // Tiling done.
5399
5400 // Do collapse.
5401 const auto &numCollapse = loopOp.getCollapseNumLoops();
5403 loopInfos.begin(), loopInfos.begin() + (numCollapse));
5404
5405 auto newTopLoopInfo =
5406 ompBuilder->collapseLoops(ompLoc.DL, collapseLoopInfos, {});
5407
5408 assert(newTopLoopInfo && "New top loop information is missing");
5409 moduleTranslation.stackWalk<OpenMPLoopInfoStackFrame>(
5410 [&](OpenMPLoopInfoStackFrame &frame) {
5411 frame.loopInfo = newTopLoopInfo;
5412 return WalkResult::interrupt();
5413 });
5414
5415 // Continue building IR after the loop. Note that the LoopInfo returned by
5416 // `collapseLoops` points inside the outermost loop and is intended for
5417 // potential further loop transformations. Use the insertion point stored
5418 // before collapsing loops instead.
5419 builder.restoreIP(afterIP);
5420 return success();
5421}
5422
5423/// Convert an omp.canonical_loop to LLVM-IR
5424static LogicalResult
5425convertOmpCanonicalLoopOp(omp::CanonicalLoopOp op, llvm::IRBuilderBase &builder,
5426 LLVM::ModuleTranslation &moduleTranslation) {
5427 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5428
5429 llvm::OpenMPIRBuilder::LocationDescription loopLoc(builder);
5430 Value loopIV = op.getInductionVar();
5431 Value loopTC = op.getTripCount();
5432
5433 llvm::Value *llvmTC = moduleTranslation.lookupValue(loopTC);
5434
5436 ompBuilder->createCanonicalLoop(
5437 loopLoc,
5438 [&](llvm::OpenMPIRBuilder::InsertPointTy ip, llvm::Value *llvmIV) {
5439 // Register the mapping of MLIR induction variable to LLVM-IR
5440 // induction variable
5441 moduleTranslation.mapValue(loopIV, llvmIV);
5442
5443 builder.restoreIP(ip);
5445 convertOmpOpRegions(op.getRegion(), "omp.loop.region", builder,
5446 moduleTranslation);
5447
5448 return bodyGenStatus.takeError();
5449 },
5450 llvmTC, "omp.loop");
5451 if (!llvmOrError)
5452 return op.emitError(llvm::toString(llvmOrError.takeError()));
5453
5454 llvm::CanonicalLoopInfo *llvmCLI = *llvmOrError;
5455 llvm::IRBuilderBase::InsertPoint afterIP = llvmCLI->getAfterIP();
5456 builder.restoreIP(afterIP);
5457
5458 // Register the mapping of MLIR loop to LLVM-IR OpenMPIRBuilder loop
5459 if (Value cli = op.getCli())
5460 moduleTranslation.mapOmpLoop(cli, llvmCLI);
5461
5462 return success();
5463}
5464
5465/// Apply a `#pragma omp unroll` / "!$omp unroll" transformation using the
5466/// OpenMPIRBuilder.
5467static LogicalResult
5468applyUnrollHeuristic(omp::UnrollHeuristicOp op, llvm::IRBuilderBase &builder,
5469 LLVM::ModuleTranslation &moduleTranslation) {
5470 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5471
5472 Value applyee = op.getApplyee();
5473 assert(applyee && "Loop to apply unrolling on required");
5474
5475 llvm::CanonicalLoopInfo *consBuilderCLI =
5476 moduleTranslation.lookupOMPLoop(applyee);
5477 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5478 ompBuilder->unrollLoopHeuristic(loc.DL, consBuilderCLI);
5479
5480 moduleTranslation.invalidateOmpLoop(applyee);
5481 return success();
5482}
5483
5484/// Apply a `#pragma omp unroll full` / `!$omp unroll full` transformation
5485/// using the OpenMPIRBuilder.
5486static LogicalResult
5487applyUnrollFull(omp::UnrollFullOp op, llvm::IRBuilderBase &builder,
5488 LLVM::ModuleTranslation &moduleTranslation) {
5489 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5490
5491 Value applyee = op.getApplyee();
5492 assert(applyee && "Loop to apply unrolling on required");
5493
5494 llvm::CanonicalLoopInfo *consBuilderCLI =
5495 moduleTranslation.lookupOMPLoop(applyee);
5496 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5497 ompBuilder->unrollLoopFull(loc.DL, consBuilderCLI);
5498
5499 moduleTranslation.invalidateOmpLoop(applyee);
5500 return success();
5501}
5502
5503/// Apply a `#pragma omp unroll partial` / `!$omp unroll partial`
5504/// transformation using the OpenMPIRBuilder.
5505static LogicalResult
5506applyUnrollPartial(omp::UnrollPartialOp op, llvm::IRBuilderBase &builder,
5507 LLVM::ModuleTranslation &moduleTranslation) {
5508 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5509
5510 Value applyee = op.getApplyee();
5511 assert(applyee && "Loop to apply unrolling on required");
5512
5513 llvm::CanonicalLoopInfo *consBuilderCLI =
5514 moduleTranslation.lookupOMPLoop(applyee);
5515 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5516
5517 // No generatee is supported yet, so the unrolled loop's CanonicalLoopInfo is
5518 // not requested and unrolling is deferred to LLVM's LoopUnroll pass.
5519 int32_t factor = static_cast<int32_t>(op.getUnrollFactor());
5520 ompBuilder->unrollLoopPartial(loc.DL, consBuilderCLI, factor,
5521 /*UnrolledCLI=*/nullptr);
5522
5523 moduleTranslation.invalidateOmpLoop(applyee);
5524 return success();
5525}
5526
5527/// Apply a `#pragma omp tile` / `!$omp tile` transformation using the
5528/// OpenMPIRBuilder.
5529static LogicalResult applyTile(omp::TileOp op, llvm::IRBuilderBase &builder,
5530 LLVM::ModuleTranslation &moduleTranslation) {
5531 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5532 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5533
5535 SmallVector<llvm::Value *> translatedSizes;
5536
5537 for (Value size : op.getSizes()) {
5538 llvm::Value *translatedSize = moduleTranslation.lookupValue(size);
5539 assert(translatedSize &&
5540 "sizes clause arguments must already be translated");
5541 translatedSizes.push_back(translatedSize);
5542 }
5543
5544 for (Value applyee : op.getApplyees()) {
5545 llvm::CanonicalLoopInfo *consBuilderCLI =
5546 moduleTranslation.lookupOMPLoop(applyee);
5547 assert(applyee && "Canonical loop must already been translated");
5548 translatedLoops.push_back(consBuilderCLI);
5549 }
5550
5551 auto generatedLoops =
5552 ompBuilder->tileLoops(loc.DL, translatedLoops, translatedSizes);
5553 if (!op.getGeneratees().empty()) {
5554 for (auto [mlirLoop, genLoop] :
5555 zip_equal(op.getGeneratees(), generatedLoops))
5556 moduleTranslation.mapOmpLoop(mlirLoop, genLoop);
5557 }
5558
5559 // CLIs can only be consumed once
5560 for (Value applyee : op.getApplyees())
5561 moduleTranslation.invalidateOmpLoop(applyee);
5562
5563 return success();
5564}
5565
5566/// Apply a `#pragma omp fuse` / `!$omp fuse` transformation using the
5567/// OpenMPIRBuilder.
5568static LogicalResult applyFuse(omp::FuseOp op, llvm::IRBuilderBase &builder,
5569 LLVM::ModuleTranslation &moduleTranslation) {
5570 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5571 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5572
5573 // Select what CLIs are going to be fused
5574 SmallVector<llvm::CanonicalLoopInfo *> beforeFuse, toFuse, afterFuse;
5575 for (size_t i = 0; i < op.getApplyees().size(); i++) {
5576 Value applyee = op.getApplyees()[i];
5577 llvm::CanonicalLoopInfo *consBuilderCLI =
5578 moduleTranslation.lookupOMPLoop(applyee);
5579 assert(applyee && "Canonical loop must already been translated");
5580 if (op.getFirst().has_value() && i < op.getFirst().value() - 1)
5581 beforeFuse.push_back(consBuilderCLI);
5582 else if (op.getCount().has_value() &&
5583 i >= op.getFirst().value() + op.getCount().value() - 1)
5584 afterFuse.push_back(consBuilderCLI);
5585 else
5586 toFuse.push_back(consBuilderCLI);
5587 }
5588 assert(
5589 (op.getGeneratees().empty() ||
5590 beforeFuse.size() + afterFuse.size() + 1 == op.getGeneratees().size()) &&
5591 "Wrong number of generatees");
5592
5593 // do the fuse
5594 auto generatedLoop = ompBuilder->fuseLoops(loc.DL, toFuse);
5595 if (!op.getGeneratees().empty()) {
5596 size_t i = 0;
5597 for (; i < beforeFuse.size(); i++)
5598 moduleTranslation.mapOmpLoop(op.getGeneratees()[i], beforeFuse[i]);
5599 moduleTranslation.mapOmpLoop(op.getGeneratees()[i++], generatedLoop);
5600 for (; i < afterFuse.size(); i++)
5601 moduleTranslation.mapOmpLoop(op.getGeneratees()[i], afterFuse[i]);
5602 }
5603
5604 // CLIs can only be consumed once
5605 for (Value applyee : op.getApplyees())
5606 moduleTranslation.invalidateOmpLoop(applyee);
5607
5608 return success();
5609}
5610
5611/// Convert an Atomic Ordering attribute to llvm::AtomicOrdering.
5612static llvm::AtomicOrdering
5613convertAtomicOrdering(std::optional<omp::ClauseMemoryOrderKind> ao) {
5614 if (!ao)
5615 return llvm::AtomicOrdering::Monotonic; // Default Memory Ordering
5616
5617 switch (*ao) {
5618 case omp::ClauseMemoryOrderKind::Seq_cst:
5619 return llvm::AtomicOrdering::SequentiallyConsistent;
5620 case omp::ClauseMemoryOrderKind::Acq_rel:
5621 return llvm::AtomicOrdering::AcquireRelease;
5622 case omp::ClauseMemoryOrderKind::Acquire:
5623 return llvm::AtomicOrdering::Acquire;
5624 case omp::ClauseMemoryOrderKind::Release:
5625 return llvm::AtomicOrdering::Release;
5626 case omp::ClauseMemoryOrderKind::Relaxed:
5627 return llvm::AtomicOrdering::Monotonic;
5628 }
5629 llvm_unreachable("Unknown ClauseMemoryOrderKind kind");
5630}
5631
5632/// Compute the cmpxchg failure ordering for an atomic compare op: use the
5633/// `fail` clause ordering when present (the verifier guarantees it is a valid
5634/// cmpxchg failure ordering), otherwise the strongest failure ordering derived
5635/// from the success ordering (which matches the OpenMPIRBuilder default).
5636static llvm::AtomicOrdering
5637getAtomicCompareFailureOrdering(omp::AtomicCompareOp atomicCompareOp,
5638 llvm::AtomicOrdering atomicOrdering) {
5639 if (atomicCompareOp.getFailMemoryOrder())
5640 return convertAtomicOrdering(atomicCompareOp.getFailMemoryOrder());
5641 return llvm::AtomicCmpXchgInst::getStrongestFailureOrdering(atomicOrdering);
5642}
5643
5644/// Convert omp.atomic.read operation to LLVM IR.
5645static LogicalResult
5646convertOmpAtomicRead(Operation &opInst, llvm::IRBuilderBase &builder,
5647 LLVM::ModuleTranslation &moduleTranslation) {
5648 auto readOp = cast<omp::AtomicReadOp>(opInst);
5649 if (failed(checkImplementationStatus(opInst)))
5650 return failure();
5651
5652 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5653 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
5654 findAllocInsertPoints(builder, moduleTranslation);
5655
5656 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5657
5658 llvm::AtomicOrdering AO = convertAtomicOrdering(readOp.getMemoryOrder());
5659 llvm::Value *x = moduleTranslation.lookupValue(readOp.getX());
5660 llvm::Value *v = moduleTranslation.lookupValue(readOp.getV());
5661
5662 llvm::Type *elementType =
5663 moduleTranslation.convertType(readOp.getElementType());
5664
5665 llvm::OpenMPIRBuilder::AtomicOpValue V = {v, elementType, false, false};
5666 llvm::OpenMPIRBuilder::AtomicOpValue X = {x, elementType, false, false};
5667 builder.restoreIP(ompBuilder->createAtomicRead(ompLoc, X, V, AO, allocaIP));
5668 return success();
5669}
5670
5671/// Converts an omp.atomic.write operation to LLVM IR.
5672static LogicalResult
5673convertOmpAtomicWrite(Operation &opInst, llvm::IRBuilderBase &builder,
5674 LLVM::ModuleTranslation &moduleTranslation) {
5675 auto writeOp = cast<omp::AtomicWriteOp>(opInst);
5676 if (failed(checkImplementationStatus(opInst)))
5677 return failure();
5678
5679 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5680 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
5681 findAllocInsertPoints(builder, moduleTranslation);
5682
5683 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5684 llvm::AtomicOrdering ao = convertAtomicOrdering(writeOp.getMemoryOrder());
5685 llvm::Value *expr = moduleTranslation.lookupValue(writeOp.getExpr());
5686 llvm::Value *dest = moduleTranslation.lookupValue(writeOp.getX());
5687 llvm::Type *ty = moduleTranslation.convertType(writeOp.getExpr().getType());
5688 llvm::OpenMPIRBuilder::AtomicOpValue x = {dest, ty, /*isSigned=*/false,
5689 /*isVolatile=*/false};
5690 builder.restoreIP(
5691 ompBuilder->createAtomicWrite(ompLoc, x, expr, ao, allocaIP));
5692 return success();
5693}
5694
5695/// Converts an LLVM dialect binary operation to the corresponding enum value
5696/// for `atomicrmw` supported binary operation.
5697static llvm::AtomicRMWInst::BinOp convertBinOpToAtomic(Operation &op) {
5699 .Case([&](LLVM::AddOp) { return llvm::AtomicRMWInst::BinOp::Add; })
5700 .Case([&](LLVM::SubOp) { return llvm::AtomicRMWInst::BinOp::Sub; })
5701 .Case([&](LLVM::AndOp) { return llvm::AtomicRMWInst::BinOp::And; })
5702 .Case([&](LLVM::OrOp) { return llvm::AtomicRMWInst::BinOp::Or; })
5703 .Case([&](LLVM::XOrOp) { return llvm::AtomicRMWInst::BinOp::Xor; })
5704 .Case([&](LLVM::UMaxOp) { return llvm::AtomicRMWInst::BinOp::UMax; })
5705 .Case([&](LLVM::UMinOp) { return llvm::AtomicRMWInst::BinOp::UMin; })
5706 .Case([&](LLVM::FAddOp) { return llvm::AtomicRMWInst::BinOp::FAdd; })
5707 .Case([&](LLVM::FSubOp) { return llvm::AtomicRMWInst::BinOp::FSub; })
5708 .Default(llvm::AtomicRMWInst::BinOp::BAD_BINOP);
5709}
5710
5711static void extractAtomicControlFlags(omp::AtomicUpdateOp atomicUpdateOp,
5712 bool &isIgnoreDenormalMode,
5713 bool &isFineGrainedMemory,
5714 bool &isRemoteMemory) {
5715 isIgnoreDenormalMode = false;
5716 isFineGrainedMemory = false;
5717 isRemoteMemory = false;
5718 if (atomicUpdateOp &&
5719 atomicUpdateOp->hasAttr(atomicUpdateOp.getAtomicControlAttrName())) {
5720 mlir::omp::AtomicControlAttr atomicControlAttr =
5721 atomicUpdateOp.getAtomicControlAttr();
5722 isIgnoreDenormalMode = atomicControlAttr.getIgnoreDenormalMode();
5723 isFineGrainedMemory = atomicControlAttr.getFineGrainedMemory();
5724 isRemoteMemory = atomicControlAttr.getRemoteMemory();
5725 }
5726}
5727
5728/// Converts an OpenMP atomic update operation using OpenMPIRBuilder.
5729static LogicalResult
5730convertOmpAtomicUpdate(omp::AtomicUpdateOp &opInst,
5731 llvm::IRBuilderBase &builder,
5732 LLVM::ModuleTranslation &moduleTranslation) {
5733 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5734 if (failed(checkImplementationStatus(*opInst)))
5735 return failure();
5736
5737 // Convert values and types.
5738 auto &innerOpList = opInst.getRegion().front().getOperations();
5739 bool isXBinopExpr{false};
5740 llvm::AtomicRMWInst::BinOp binop;
5741 mlir::Value mlirExpr;
5742 llvm::Value *llvmExpr = nullptr;
5743 llvm::Value *llvmX = nullptr;
5744 llvm::Type *llvmXElementType = nullptr;
5745 if (innerOpList.size() == 2) {
5746 // The two operations here are the update and the terminator.
5747 // Since we can identify the update operation, there is a possibility
5748 // that we can generate the atomicrmw instruction.
5749 mlir::Operation &innerOp = *opInst.getRegion().front().begin();
5750 if (!llvm::is_contained(innerOp.getOperands(),
5751 opInst.getRegion().getArgument(0))) {
5752 return opInst.emitError("no atomic update operation with region argument"
5753 " as operand found inside atomic.update region");
5754 }
5755 binop = convertBinOpToAtomic(innerOp);
5756 isXBinopExpr = innerOp.getOperand(0) == opInst.getRegion().getArgument(0);
5757 mlirExpr = (isXBinopExpr ? innerOp.getOperand(1) : innerOp.getOperand(0));
5758 llvmExpr = moduleTranslation.lookupValue(mlirExpr);
5759 } else {
5760 // Since the update region includes more than one operation
5761 // we will resort to generating a cmpxchg loop.
5762 binop = llvm::AtomicRMWInst::BinOp::BAD_BINOP;
5763 }
5764 llvmX = moduleTranslation.lookupValue(opInst.getX());
5765 llvmXElementType = moduleTranslation.convertType(
5766 opInst.getRegion().getArgument(0).getType());
5767 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {llvmX, llvmXElementType,
5768 /*isSigned=*/false,
5769 /*isVolatile=*/false};
5770
5771 llvm::AtomicOrdering atomicOrdering =
5772 convertAtomicOrdering(opInst.getMemoryOrder());
5773
5774 // Generate update code.
5775 auto updateFn =
5776 [&opInst, &moduleTranslation](
5777 llvm::Value *atomicx,
5778 llvm::IRBuilder<> &builder) -> llvm::Expected<llvm::Value *> {
5779 Block &bb = *opInst.getRegion().begin();
5780 moduleTranslation.mapValue(*opInst.getRegion().args_begin(), atomicx);
5781 moduleTranslation.mapBlock(&bb, builder.GetInsertBlock());
5782 if (failed(moduleTranslation.convertBlock(bb, true, builder)))
5783 return llvm::make_error<PreviouslyReportedError>();
5784
5785 omp::YieldOp yieldop = dyn_cast<omp::YieldOp>(bb.getTerminator());
5786 assert(yieldop && yieldop.getResults().size() == 1 &&
5787 "terminator must be omp.yield op and it must have exactly one "
5788 "argument");
5789 return moduleTranslation.lookupValue(yieldop.getResults()[0]);
5790 };
5791
5792 bool isIgnoreDenormalMode;
5793 bool isFineGrainedMemory;
5794 bool isRemoteMemory;
5795 extractAtomicControlFlags(opInst, isIgnoreDenormalMode, isFineGrainedMemory,
5796 isRemoteMemory);
5797 // Handle ambiguous alloca, if any.
5798 auto allocaIP = findAllocInsertPoints(builder, moduleTranslation);
5799 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5800 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
5801 ompBuilder->createAtomicUpdate(ompLoc, allocaIP, llvmAtomicX, llvmExpr,
5802 atomicOrdering, binop, updateFn,
5803 isXBinopExpr, isIgnoreDenormalMode,
5804 isFineGrainedMemory, isRemoteMemory);
5805
5806 if (failed(handleError(afterIP, *opInst)))
5807 return failure();
5808
5809 builder.restoreIP(*afterIP);
5810 return success();
5811}
5812
5813/// Helper to extract the OMPAtomicCompareOp from an integer comparison
5814/// predicate. Returns std::nullopt for unsupported predicates.
5815static std::optional<llvm::omp::OMPAtomicCompareOp>
5816convertICmpPredicateToAtomicCompareOp(LLVM::ICmpPredicate predicate) {
5817 switch (predicate) {
5818 case LLVM::ICmpPredicate::eq:
5819 return llvm::omp::OMPAtomicCompareOp::EQ;
5820 case LLVM::ICmpPredicate::slt:
5821 case LLVM::ICmpPredicate::ult:
5822 return llvm::omp::OMPAtomicCompareOp::MIN;
5823 case LLVM::ICmpPredicate::sgt:
5824 case LLVM::ICmpPredicate::ugt:
5825 return llvm::omp::OMPAtomicCompareOp::MAX;
5826 default:
5827 return std::nullopt;
5828 }
5829}
5830
5831/// Helper to extract the OMPAtomicCompareOp from a floating-point comparison
5832/// predicate. Returns std::nullopt for unsupported predicates.
5833static std::optional<llvm::omp::OMPAtomicCompareOp>
5834convertFCmpPredicateToAtomicCompareOp(LLVM::FCmpPredicate predicate) {
5835 switch (predicate) {
5836 case LLVM::FCmpPredicate::oeq:
5837 case LLVM::FCmpPredicate::ueq:
5838 return llvm::omp::OMPAtomicCompareOp::EQ;
5839 case LLVM::FCmpPredicate::olt:
5840 case LLVM::FCmpPredicate::ult:
5841 return llvm::omp::OMPAtomicCompareOp::MIN;
5842 case LLVM::FCmpPredicate::ogt:
5843 case LLVM::FCmpPredicate::ugt:
5844 return llvm::omp::OMPAtomicCompareOp::MAX;
5845 default:
5846 return std::nullopt;
5847 }
5848}
5849
5850/// Result of matching the decomposed complex equality pattern inside an atomic
5851/// compare region.
5853 bool isComplex = false;
5854 bool isNE = false; // `or` of the field compares => NE (unsupported).
5855 mlir::Value eAggregate; // The complex expected value (`e`).
5856 bool isXBinopExpr = false; // True if x is the first fcmp operand.
5857};
5858
5859/// Detect a decomposed complex equality comparison in an atomic compare region:
5860/// %re_x = llvm.extractvalue %xval[0]
5861/// %re_e = llvm.extractvalue %eStruct[0]
5862/// %cmp_re = llvm.fcmp "oeq" %re_x, %re_e
5863/// %im_x = llvm.extractvalue %xval[1]
5864/// %im_e = llvm.extractvalue %eStruct[1]
5865/// %cmp_im = llvm.fcmp "oeq" %im_x, %im_e
5866/// %cmp = llvm.and %cmp_re, %cmp_im (llvm.or would be NE)
5867/// It is recognised by an and/or whose operands are both fcmps operating on
5868/// extractvalues, one chain rooted at the block argument (x) and the other at
5869/// the expected complex value (e).
5872 auto traceToAggregate = [](mlir::Value v) -> mlir::Value {
5873 if (auto extractOp = v.getDefiningOp<LLVM::ExtractValueOp>())
5874 return extractOp.getContainer();
5875 return nullptr;
5876 };
5877 for (Operation &op : block.getOperations()) {
5878 if (!isa<LLVM::AndOp, LLVM::OrOp>(op))
5879 continue;
5880 auto lhsFcmp = op.getOperand(0).getDefiningOp<LLVM::FCmpOp>();
5881 auto rhsFcmp = op.getOperand(1).getDefiningOp<LLVM::FCmpOp>();
5882 if (!lhsFcmp || !rhsFcmp)
5883 continue;
5884 mlir::Value lhsAgg0 = traceToAggregate(lhsFcmp.getOperand(0));
5885 mlir::Value lhsAgg1 = traceToAggregate(lhsFcmp.getOperand(1));
5886 bool lhsXIsOp0 = (lhsAgg0 == block.getArgument(0));
5887 bool lhsXIsOp1 = (lhsAgg1 == block.getArgument(0));
5888 if (!lhsXIsOp0 && !lhsXIsOp1)
5889 continue;
5890 mlir::Value eAggregate = lhsXIsOp0 ? lhsAgg1 : lhsAgg0;
5891 if (!eAggregate)
5892 continue;
5893 result.isComplex = true;
5894 result.isNE = isa<LLVM::OrOp>(op);
5895 result.eAggregate = eAggregate;
5896 result.isXBinopExpr = lhsXIsOp0;
5897 break;
5898 }
5899 return result;
5900}
5901
5902/// Emit an IEEE-754-correct `cmpxchg` for a complex (struct-typed) atomic
5903/// compare with `fcmp oeq`. The old value of X is returned (as the complex
5904/// struct type) in \p oldComplex and the success flag (i1) in \p cmpOk.
5905/// \p failOrdering is the memory ordering used when the compare-exchange does
5906/// not store; it must be a valid cmpxchg failure ordering.
5907static void emitComplexAtomicCmpXchg(llvm::IRBuilderBase &builder,
5908 llvm::Value *llvmX, llvm::Type *complexTy,
5909 llvm::Value *eVal, llvm::Value *dVal,
5910 llvm::AtomicOrdering atomicOrdering,
5911 llvm::AtomicOrdering failOrdering,
5912 bool isWeak, llvm::Value *&oldComplex,
5913 llvm::Value *&cmpOk) {
5914 const llvm::DataLayout &DL =
5915 builder.GetInsertBlock()->getModule()->getDataLayout();
5916 unsigned totalBits = DL.getTypeStoreSizeInBits(complexTy).getFixedValue();
5917 llvm::IntegerType *intTy =
5918 llvm::IntegerType::get(builder.getContext(), totalBits);
5919 llvm::Align complexAlign = DL.getABITypeAlign(complexTy);
5920 llvm::Align intAlign = DL.getABITypeAlign(intTy);
5921 llvm::Align maxAlign = std::max(complexAlign, intAlign);
5922
5923 // Spill D to obtain its integer bit pattern for the swap value.
5924 llvm::AllocaInst *dAlloca =
5925 builder.CreateAlloca(complexTy, nullptr, "cmplx.d");
5926 dAlloca->setAlignment(maxAlign);
5927 builder.CreateAlignedStore(dVal, dAlloca, maxAlign);
5928 llvm::Value *dInt =
5929 builder.CreateAlignedLoad(intTy, dAlloca, maxAlign, "cmplx.d.int");
5930
5931 // Load X atomically and reinterpret as complex. Use the failure ordering: on
5932 // a failed component comparison we branch around the cmpxchg, so this load is
5933 // the only memory op on that path.
5934 llvm::LoadInst *xCurr =
5935 builder.CreateAlignedLoad(intTy, llvmX, maxAlign, "cmplx.x.load");
5936 xCurr->setAtomic(failOrdering);
5937 llvm::AllocaInst *xAlloca =
5938 builder.CreateAlloca(complexTy, nullptr, "cmplx.x");
5939 xAlloca->setAlignment(maxAlign);
5940 builder.CreateAlignedStore(xCurr, xAlloca, maxAlign);
5941 llvm::Value *xStruct =
5942 builder.CreateAlignedLoad(complexTy, xAlloca, maxAlign, "cmplx.x.val");
5943
5944 // Component-wise IEEE-754 equality: `fcmp oeq` yields false for NaN (so a
5945 // NaN component correctly makes the compare fail) and true for +0.0 vs -0.0
5946 // (so a zero-sign difference does not spuriously fail the compare).
5947 llvm::Value *reX = builder.CreateExtractValue(xStruct, 0);
5948 llvm::Value *imX = builder.CreateExtractValue(xStruct, 1);
5949 llvm::Value *reE = builder.CreateExtractValue(eVal, 0);
5950 llvm::Value *imE = builder.CreateExtractValue(eVal, 1);
5951 llvm::Value *reEq = builder.CreateFCmpOEQ(reX, reE, "cmplx.re.eq");
5952 llvm::Value *imEq = builder.CreateFCmpOEQ(imX, imE, "cmplx.im.eq");
5953 llvm::Value *fpEqual = builder.CreateAnd(reEq, imEq, "cmplx.eq");
5954
5955 // When the components compare equal, attempt the swap using X's own loaded
5956 // bit pattern as the comparand; otherwise the compare fails and X is left
5957 // unchanged (the captured old value is the value just loaded).
5958 llvm::BasicBlock *curBB = builder.GetInsertBlock();
5959 llvm::Function *fn = curBB->getParent();
5960 llvm::BasicBlock *swapBB =
5961 llvm::BasicBlock::Create(builder.getContext(), "cmplx.atomic.swap", fn);
5962 llvm::BasicBlock *exitBB =
5963 llvm::BasicBlock::Create(builder.getContext(), "cmplx.atomic.exit", fn);
5964 builder.CreateCondBr(fpEqual, swapBB, exitBB);
5965
5966 builder.SetInsertPoint(swapBB);
5967 llvm::AtomicCmpXchgInst *cmpXchg = builder.CreateAtomicCmpXchg(
5968 llvmX, xCurr, dInt, maxAlign, atomicOrdering, failOrdering);
5969 cmpXchg->setWeak(isWeak);
5970 llvm::Value *oldSwap = builder.CreateExtractValue(cmpXchg, 0);
5971 llvm::Value *okSwap = builder.CreateExtractValue(cmpXchg, 1);
5972 builder.CreateBr(exitBB);
5973
5974 // Merge the swap and no-swap paths.
5975 builder.SetInsertPoint(exitBB);
5976 llvm::PHINode *oldIntPHI = builder.CreatePHI(intTy, 2, "cmplx.old.int");
5977 oldIntPHI->addIncoming(oldSwap, swapBB);
5978 oldIntPHI->addIncoming(xCurr, curBB);
5979 llvm::PHINode *okPHI = builder.CreatePHI(builder.getInt1Ty(), 2, "cmplx.ok");
5980 okPHI->addIncoming(okSwap, swapBB);
5981 okPHI->addIncoming(builder.getFalse(), curBB);
5982
5983 // Reinterpret the old integer value as the complex struct via memory.
5984 llvm::AllocaInst *oldAlloca =
5985 builder.CreateAlloca(complexTy, nullptr, "cmplx.old");
5986 oldAlloca->setAlignment(maxAlign);
5987 builder.CreateAlignedStore(oldIntPHI, oldAlloca, maxAlign);
5988 oldComplex = builder.CreateAlignedLoad(complexTy, oldAlloca, maxAlign,
5989 "cmplx.old.val");
5990 cmpOk = okPHI;
5991}
5992
5993/// Holds the extracted comparison pattern information from an atomic compare
5994/// region.
5996 llvm::omp::OMPAtomicCompareOp compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
5997 llvm::Value *eVal = nullptr;
5998 llvm::Value *dVal = nullptr;
5999 bool isXBinopExpr = false;
6000 bool isSigned = false;
6001};
6002/// Extract comparison predicate, expected value (e), desired value (d), and
6003/// related flags from an atomic compare region block by scanning for
6004/// icmp/fcmp/select/min/max operations.
6005static LogicalResult extractAtomicComparePattern(
6006 Block &block,
6007 llvm::function_ref<llvm::Value *(mlir::Value)> materializeValue,
6008 omp::AtomicCompareOp atomicCompareOp, AtomicComparePatternInfo &info) {
6009 // Complex equality is a decomposed per-field pattern (extractvalue + fcmp +
6010 // and) rather than a single scalar compare. Detect it first so the scalar
6011 // icmp/fcmp handling below does not mistake a real/imaginary field for the
6012 // whole expected value.
6014 cplx.isComplex) {
6015 if (cplx.isNE)
6016 return atomicCompareOp.emitError(
6017 "unsupported comparison predicate (NE) for complex atomic compare");
6018 info.compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
6019 info.isXBinopExpr = cplx.isXBinopExpr;
6020 info.eVal = materializeValue(cplx.eAggregate);
6021 for (Operation &op : block.getOperations()) {
6022 if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
6023 info.dVal = materializeValue(selectOp.getTrueValue());
6024 break;
6025 }
6026 }
6027 return success();
6028 }
6029
6030 for (Operation &op : block.getOperations()) {
6031 // Pre-filter: skip icmps that don't involve the block argument
6032 // (e.g., truthiness extractions from logical-to-integer conversion).
6033 if (auto icmpOp = dyn_cast<LLVM::ICmpOp>(op);
6034 icmpOp && icmpOp.getOperand(0) != block.getArgument(0) &&
6035 icmpOp.getOperand(1) != block.getArgument(0))
6036 continue;
6037
6038 LogicalResult result =
6040 .Case<LLVM::ICmpOp>([&](LLVM::ICmpOp icmpOp) -> LogicalResult {
6041 auto maybeOp =
6042 convertICmpPredicateToAtomicCompareOp(icmpOp.getPredicate());
6043 if (!maybeOp)
6044 return atomicCompareOp.emitError(
6045 "unsupported comparison predicate in atomic compare");
6046 info.compareOp = *maybeOp;
6047 LLVM::ICmpPredicate pred = icmpOp.getPredicate();
6048 info.isSigned = (pred == LLVM::ICmpPredicate::slt ||
6049 pred == LLVM::ICmpPredicate::sgt ||
6050 pred == LLVM::ICmpPredicate::sle ||
6051 pred == LLVM::ICmpPredicate::sge);
6052 info.isXBinopExpr =
6053 (icmpOp.getOperand(0) == block.getArgument(0));
6054 mlir::Value eOperand = info.isXBinopExpr ? icmpOp.getOperand(1)
6055 : icmpOp.getOperand(0);
6056 info.eVal = materializeValue(eOperand);
6057 return success();
6058 })
6059 .Case<LLVM::FCmpOp>([&](LLVM::FCmpOp fcmpOp) -> LogicalResult {
6060 auto maybeOp =
6061 convertFCmpPredicateToAtomicCompareOp(fcmpOp.getPredicate());
6062 if (!maybeOp)
6063 return atomicCompareOp.emitError(
6064 "unsupported comparison predicate in atomic compare");
6065 info.compareOp = *maybeOp;
6066 info.isXBinopExpr =
6067 (fcmpOp.getOperand(0) == block.getArgument(0));
6068 mlir::Value eOperand = info.isXBinopExpr ? fcmpOp.getOperand(1)
6069 : fcmpOp.getOperand(0);
6070 info.eVal = materializeValue(eOperand);
6071 return success();
6072 })
6073 .Case<LLVM::SelectOp>([&](LLVM::SelectOp selectOp) {
6074 if (!info.dVal)
6075 info.dVal = materializeValue(selectOp.getTrueValue());
6076 return success();
6077 })
6078 .Case<mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
6079 mlir::arith::MaxUIOp, mlir::arith::MinUIOp,
6080 mlir::arith::MaximumFOp, mlir::arith::MinimumFOp,
6081 LLVM::SMaxOp, LLVM::SMinOp, LLVM::UMaxOp, LLVM::UMinOp,
6082 LLVM::MaxNumOp, LLVM::MinNumOp>([&](Operation *) {
6083 // Canonicalized min/max ops (arith or LLVM intrinsic form).
6084 // max(x,e) came from slt/ult/olt -> OMPAtomicCompareOp::MIN
6085 // min(x,e) came from sgt/ugt/ogt -> OMPAtomicCompareOp::MAX
6086 // (OMPIRBuilder inverts: MIN->atomicrmw max, MAX->atomicrmw min)
6087 bool isMax = isa<mlir::arith::MaxSIOp, mlir::arith::MaxUIOp,
6088 mlir::arith::MaximumFOp, LLVM::SMaxOp,
6089 LLVM::UMaxOp, LLVM::MaxNumOp>(op);
6090 info.compareOp = isMax ? llvm::omp::OMPAtomicCompareOp::MIN
6091 : llvm::omp::OMPAtomicCompareOp::MAX;
6092 info.isSigned = isa<mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
6093 LLVM::SMaxOp, LLVM::SMinOp>(op);
6094 info.isXBinopExpr = (op.getOperand(0) == block.getArgument(0));
6095 mlir::Value eOperand =
6096 info.isXBinopExpr ? op.getOperand(1) : op.getOperand(0);
6097 info.eVal = materializeValue(eOperand);
6098 info.dVal = info.eVal;
6099 return success();
6100 })
6101 .Default([](Operation *) { return success(); });
6102
6103 if (failed(result))
6104 return result;
6105 }
6106 return success();
6107}
6108
6109static LogicalResult
6110convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
6111 llvm::IRBuilderBase &builder,
6112 LLVM::ModuleTranslation &moduleTranslation) {
6113 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
6114 if (failed(checkImplementationStatus(*atomicCaptureOp)))
6115 return failure();
6116
6117 omp::AtomicUpdateOp atomicUpdateOp = atomicCaptureOp.getAtomicUpdateOp();
6118 omp::AtomicWriteOp atomicWriteOp = atomicCaptureOp.getAtomicWriteOp();
6119 omp::AtomicCompareOp atomicCompareOp = atomicCaptureOp.getAtomicCompareOp();
6120
6121 // If the capture contains an atomic.compare, delegate to
6122 // createAtomicCompare with the capture variable (V) set.
6123 if (atomicCompareOp) {
6124 omp::AtomicReadOp atomicReadOp = atomicCaptureOp.getAtomicReadOp();
6125 assert(atomicReadOp && "expected atomic.read in capture+compare");
6126
6127 Region &region = atomicCompareOp.getRegion();
6128 Block &block = region.front();
6129
6130 llvm::Type *llvmXElementType =
6131 moduleTranslation.convertType(block.getArgument(0).getType());
6132 llvm::Value *llvmX = moduleTranslation.lookupValue(atomicCompareOp.getX());
6133 llvm::Value *llvmV = moduleTranslation.lookupValue(atomicReadOp.getV());
6134
6135 bool isSigned = false;
6136 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {
6137 llvmX, llvmXElementType, isSigned, /*IsVolatile=*/false};
6138 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicV = {
6139 llvmV, llvmXElementType, /*isSigned=*/false, /*IsVolatile=*/false};
6140 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicR = {nullptr, nullptr, false,
6141 false};
6142
6143 llvm::AtomicOrdering atomicOrdering =
6144 convertAtomicOrdering(atomicCaptureOp.getMemoryOrder());
6145
6146 // Pre-translate non-pattern operations inside the compare region.
6147 auto isAtomicComparePatternOp = [](Operation &op) {
6148 return llvm::isa<LLVM::ICmpOp, LLVM::FCmpOp, LLVM::SelectOp, LLVM::AndOp,
6149 LLVM::OrOp, LLVM::SMaxOp, LLVM::SMinOp, LLVM::UMaxOp,
6150 LLVM::UMinOp, LLVM::MaxNumOp, LLVM::MinNumOp,
6151 mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
6152 mlir::arith::MaxUIOp, mlir::arith::MinUIOp,
6153 mlir::arith::MaximumFOp, mlir::arith::MinimumFOp>(op);
6154 };
6155 for (Operation &op : block.without_terminator()) {
6156 if (isAtomicComparePatternOp(op))
6157 continue;
6158 bool allOperandsMapped =
6159 llvm::all_of(op.getOperands(), [&](mlir::Value v) {
6160 return moduleTranslation.lookupValue(v) != nullptr;
6161 });
6162 if (!allOperandsMapped)
6163 continue;
6164 if (failed(moduleTranslation.convertOperation(op, builder)))
6165 return atomicCompareOp.emitError(
6166 "failed to translate operation inside atomic compare region");
6167 }
6168
6169 auto materializeValue = [&](mlir::Value val) -> llvm::Value * {
6170 if (llvm::Value *existing = moduleTranslation.lookupValue(val))
6171 return existing;
6172 if (auto loadOp = val.getDefiningOp<LLVM::LoadOp>()) {
6173 if (loadOp->getParentRegion() == &region) {
6174 llvm::Value *loadAddr =
6175 moduleTranslation.lookupValue(loadOp.getAddr());
6176 if (!loadAddr)
6177 return nullptr;
6178 llvm::Type *loadType =
6179 moduleTranslation.convertType(loadOp.getResult().getType());
6180 return builder.CreateLoad(loadType, loadAddr);
6181 }
6182 }
6183 return nullptr;
6184 };
6185
6186 // Extract comparison predicate, eVal, and dVal from the region.
6187 AtomicComparePatternInfo patternInfo;
6188 if (failed(extractAtomicComparePattern(block, materializeValue,
6189 atomicCompareOp, patternInfo)))
6190 return failure();
6191
6192 llvm::omp::OMPAtomicCompareOp compareOp = patternInfo.compareOp;
6193 llvm::Value *eVal = patternInfo.eVal;
6194 llvm::Value *dVal = patternInfo.dVal;
6195 bool isXBinopExpr = patternInfo.isXBinopExpr;
6196 isSigned = patternInfo.isSigned;
6197
6198 if (!eVal)
6199 return atomicCompareOp.emitError(
6200 "failed to extract expected value (e) from atomic compare region");
6201 if (!dVal) {
6202 auto yieldOp = cast<omp::YieldOp>(block.getTerminator());
6203 if (yieldOp.getResults().empty())
6204 return atomicCompareOp.emitError(
6205 "failed to extract desired value (d) from atomic compare region");
6206 dVal = materializeValue(yieldOp.getResults()[0]);
6207 }
6208
6209 llvmAtomicX.IsSigned = isSigned;
6210
6211 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6212 bool isReadFirst = isa<omp::AtomicReadOp>(atomicCaptureOp.getFirstOp());
6213 bool isPostfixCapture = !isReadFirst;
6214 bool isFailOnly = atomicCaptureOp.getFailOnly();
6215
6216 // Complex equality capture: x is struct-typed, which the OMPIRBuilder
6217 // cannot handle, so emit an IEEE-754-correct cmpxchg (as in the non-capture
6218 // complex path) and reconstruct the captured value from its result. The
6219 // helper compares components with `fcmp oeq` so `-0.0 == +0.0` and `NaN`
6220 // are handled as in the scalar float path. Complex only supports the ==
6221 // comparison.
6222 if (llvmXElementType->isStructTy()) {
6223 llvm::Value *oldComplex = nullptr;
6224 llvm::Value *cmpOk = nullptr;
6225 llvm::AtomicOrdering failOrdering =
6226 getAtomicCompareFailureOrdering(atomicCompareOp, atomicOrdering);
6227 emitComplexAtomicCmpXchg(builder, llvmX, llvmXElementType, eVal, dVal,
6228 atomicOrdering, failOrdering,
6229 atomicCompareOp.getWeak(), oldComplex, cmpOk);
6230
6231 if (isFailOnly) {
6232 // v is written only when the compare fails (cmpOk == false).
6233 llvm::Value *cmpFailed = builder.CreateNot(cmpOk);
6234 llvm::BasicBlock *curBB = builder.GetInsertBlock();
6235 llvm::Function *fn = curBB->getParent();
6236 llvm::BasicBlock *contBB = llvm::BasicBlock::Create(
6237 builder.getContext(), "omp.atomic.cont", fn);
6238 llvm::BasicBlock *exitBB = llvm::BasicBlock::Create(
6239 builder.getContext(), "omp.atomic.exit", fn);
6240 builder.CreateCondBr(cmpFailed, contBB, exitBB);
6241 builder.SetInsertPoint(contBB);
6242 builder.CreateStore(oldComplex, llvmAtomicV.Var,
6243 llvmAtomicV.IsVolatile);
6244 builder.CreateBr(exitBB);
6245 builder.SetInsertPoint(exitBB);
6246 } else if (isPostfixCapture) {
6247 // v gets the new value of x: d on success, old x otherwise.
6248 llvm::Value *newComplex = builder.CreateSelect(cmpOk, dVal, oldComplex);
6249 builder.CreateStore(newComplex, llvmAtomicV.Var,
6250 llvmAtomicV.IsVolatile);
6251 } else {
6252 // Prefix: v gets the old value of x.
6253 builder.CreateStore(oldComplex, llvmAtomicV.Var,
6254 llvmAtomicV.IsVolatile);
6255 }
6256
6257 // Emit flush after atomic compare if needed (release/acq_rel/seq_cst).
6258 if (atomicOrdering == llvm::AtomicOrdering::Release ||
6259 atomicOrdering == llvm::AtomicOrdering::AcquireRelease ||
6260 atomicOrdering == llvm::AtomicOrdering::SequentiallyConsistent) {
6261 llvm::OpenMPIRBuilder::LocationDescription flushLoc(builder);
6262 ompBuilder->createFlush(flushLoc);
6263 }
6264 return success();
6265 }
6266
6267 // Min/max (<, >) comparisons lower to an atomicrmw. The OMPIRBuilder has no
6268 // notion of a failed compare for an atomicrmw, so the fail-only capture
6269 // form (v written only when the compare fails) has no valid mapping and is
6270 // Min/max (<, >) comparisons lower to an atomicrmw. The OMPIRBuilder has no
6271 // notion of a failed compare for an atomicrmw (it asserts on IsFailOnly),
6272 // so for min/max the fail-only capture is reconstructed manually below.
6273 bool isMinMax = compareOp != llvm::omp::OMPAtomicCompareOp::EQ;
6274
6275 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicVForCall = llvmAtomicV;
6276 // Capture into V is reconstructed manually below for:
6277 // * postfix capture (v gets the new value of x): equality from the
6278 // cmpxchg result, min/max from the atomicrmw result;
6279 // * min/max fail-only capture (the OMPIRBuilder cannot express it).
6280 // Bypass V in the OMPIRBuilder for those cases so it does not also emit its
6281 // own (for min/max, incorrect or unsupported) capture store.
6282 bool minMaxManualCapture = isMinMax && (isPostfixCapture || isFailOnly);
6283 bool eqPostfixManualCapture = !isMinMax && isPostfixCapture && !isFailOnly;
6284 if (minMaxManualCapture || eqPostfixManualCapture)
6285 llvmAtomicVForCall = {nullptr, nullptr, false, false};
6286
6287 // The OMPIRBuilder only understands IsFailOnly for the equality (cmpxchg)
6288 // path; for min/max it would assert. Min/max fail-only is handled here.
6289 bool builderFailOnly = isFailOnly && !isMinMax;
6290
6291 // IsPostfixUpdate selects which value the OMPIRBuilder captures into V:
6292 // * min/max prefix and equality prefix: a direct store of the old value
6293 // (IsPostfixUpdate=true).
6294 // * equality fail-only: a conditional store (IsPostfixUpdate=false).
6295 // Manually-reconstructed captures bypass V above.
6296 bool isPostfixUpdate = !builderFailOnly;
6297
6298 bool isWeak = atomicCompareOp.getWeak();
6299 bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
6300 llvm::AtomicOrdering failureOrdering =
6301 getAtomicCompareFailureOrdering(atomicCompareOp, atomicOrdering);
6302 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
6303 ompBuilder->createAtomicCompare(
6304 ompLoc, llvmAtomicX, llvmAtomicVForCall, llvmAtomicR, eVal, dVal,
6305 atomicOrdering, compareOp, isXBinopExpr, isPostfixUpdate,
6306 builderFailOnly, failureOrdering, isWeak);
6307 ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
6308
6309 if (failed(handleError(afterIP, *atomicCaptureOp)))
6310 return failure();
6311
6312 builder.restoreIP(*afterIP);
6313
6314 // Min/max capture is reconstructed from the atomicrmw the OMPIRBuilder
6315 // emits (its result is the old value of x). V was bypassed above.
6316 // * postfix: v gets the new value min/max(old, e);
6317 // * fail-only: v gets the old value, but only when the compare failed
6318 // (i.e. the atomicrmw did not change x).
6319 // (Prefix min/max captures the old value directly through V, so nothing
6320 // extra is needed there.)
6321 if (isMinMax && (isPostfixCapture || isFailOnly)) {
6322 llvm::BasicBlock *curBB = builder.GetInsertBlock();
6323 llvm::AtomicRMWInst *rmw = nullptr;
6324 for (auto &inst : llvm::reverse(*curBB)) {
6325 if (auto *r = dyn_cast<llvm::AtomicRMWInst>(&inst)) {
6326 rmw = r;
6327 break;
6328 }
6329 }
6330 assert(rmw && "expected atomicrmw for min/max compare capture");
6331 llvm::Value *oldVal = rmw;
6332 llvm::Value *rhs = rmw->getValOperand();
6333
6334 if (isFailOnly) {
6335 // The compare "failed" (the else branch runs) exactly when the
6336 // atomicrmw did not change x. Recompute the original update condition
6337 // on the old value and negate it. v is stored only in that case.
6338 llvm::CmpInst::Predicate updatePred;
6339 switch (rmw->getOperation()) {
6340 case llvm::AtomicRMWInst::Min:
6341 updatePred = llvm::CmpInst::ICMP_SGT;
6342 break;
6343 case llvm::AtomicRMWInst::Max:
6344 updatePred = llvm::CmpInst::ICMP_SLT;
6345 break;
6346 case llvm::AtomicRMWInst::UMin:
6347 updatePred = llvm::CmpInst::ICMP_UGT;
6348 break;
6349 case llvm::AtomicRMWInst::UMax:
6350 updatePred = llvm::CmpInst::ICMP_ULT;
6351 break;
6352 case llvm::AtomicRMWInst::FMin:
6353 updatePred = llvm::CmpInst::FCMP_OGT;
6354 break;
6355 case llvm::AtomicRMWInst::FMax:
6356 updatePred = llvm::CmpInst::FCMP_OLT;
6357 break;
6358 default:
6359 llvm_unreachable(
6360 "unexpected atomicrmw op for min/max compare capture");
6361 }
6362 llvm::Value *updated = builder.CreateCmp(updatePred, oldVal, rhs);
6363 llvm::Value *failed = builder.CreateNot(updated);
6364 llvm::Function *fn = curBB->getParent();
6365 llvm::BasicBlock *contBB = llvm::BasicBlock::Create(
6366 builder.getContext(), "omp.atomic.cont", fn);
6367 llvm::BasicBlock *exitBB = llvm::BasicBlock::Create(
6368 builder.getContext(), "omp.atomic.exit", fn);
6369 builder.CreateCondBr(failed, contBB, exitBB);
6370 builder.SetInsertPoint(contBB);
6371 builder.CreateStore(oldVal, llvmAtomicV.Var, llvmAtomicV.IsVolatile);
6372 builder.CreateBr(exitBB);
6373 builder.SetInsertPoint(exitBB);
6374 } else {
6375 llvm::Intrinsic::ID id;
6376 switch (rmw->getOperation()) {
6377 case llvm::AtomicRMWInst::Min:
6378 id = llvm::Intrinsic::smin;
6379 break;
6380 case llvm::AtomicRMWInst::Max:
6381 id = llvm::Intrinsic::smax;
6382 break;
6383 case llvm::AtomicRMWInst::UMin:
6384 id = llvm::Intrinsic::umin;
6385 break;
6386 case llvm::AtomicRMWInst::UMax:
6387 id = llvm::Intrinsic::umax;
6388 break;
6389 case llvm::AtomicRMWInst::FMin:
6390 id = llvm::Intrinsic::minnum;
6391 break;
6392 case llvm::AtomicRMWInst::FMax:
6393 id = llvm::Intrinsic::maxnum;
6394 break;
6395 default:
6396 llvm_unreachable(
6397 "unexpected atomicrmw op for min/max compare capture");
6398 }
6399 llvm::Value *newVal = builder.CreateBinaryIntrinsic(id, oldVal, rhs);
6400 builder.CreateStore(newVal, llvmAtomicV.Var, llvmAtomicV.IsVolatile);
6401 }
6402 }
6403
6404 // Equality postfix: v = select(success, D, old) — reconstructs the new
6405 // value of x from the cmpxchg result.
6406 if (!isMinMax && isPostfixCapture && !isFailOnly) {
6407 llvm::BasicBlock *curBB = builder.GetInsertBlock();
6408 llvm::Value *oldVal = nullptr;
6409 llvm::Value *successVal = nullptr;
6410
6411 // Integer path (and non-HandleFPNegZero FP path): a single cmpxchg
6412 // lives in the current block.
6413 for (auto &inst : llvm::reverse(*curBB)) {
6414 if (isa<llvm::AtomicCmpXchgInst>(&inst)) {
6415 oldVal = builder.CreateExtractValue(&inst, /*Idxs=*/0);
6416 successVal = builder.CreateExtractValue(&inst, /*Idxs=*/1);
6417 break;
6418 }
6419 }
6420
6421 // FP HandleFPNegZero path: the OMPIRBuilder emits a multi-block
6422 // structure (NaN / ±0.0 handling) with cmpxchg in predecessor
6423 // blocks. Results are merged via PHI nodes in the current (exit)
6424 // block: an i1 PHI for success and a bitcast of an integer PHI
6425 // for the old FP value.
6426 if (!oldVal) {
6427 for (auto &inst : *curBB) {
6428 auto *phi = dyn_cast<llvm::PHINode>(&inst);
6429 if (!phi)
6430 break;
6431 if (phi->getType()->isIntegerTy(1))
6432 successVal = phi;
6433 }
6434 for (auto &inst : *curBB) {
6435 if (auto *bc = dyn_cast<llvm::BitCastInst>(&inst)) {
6436 oldVal = bc;
6437 break;
6438 }
6439 }
6440 }
6441
6442 assert(oldVal && "expected cmpxchg or PHI+bitcast for compare capture");
6443 assert(successVal && "expected success flag for compare capture");
6444 llvm::Value *newVal = builder.CreateSelect(successVal, dVal, oldVal);
6445 builder.CreateStore(newVal, llvmAtomicV.Var, llvmAtomicV.IsVolatile);
6446 }
6447
6448 return success();
6449 }
6450
6451 mlir::Value mlirExpr;
6452 bool isXBinopExpr = false, isPostfixUpdate = false;
6453 llvm::AtomicRMWInst::BinOp binop = llvm::AtomicRMWInst::BinOp::BAD_BINOP;
6454
6455 assert((atomicUpdateOp || atomicWriteOp) &&
6456 "internal op must be an atomic.update or atomic.write op");
6457
6458 if (atomicWriteOp) {
6459 isPostfixUpdate = true;
6460 mlirExpr = atomicWriteOp.getExpr();
6461 } else {
6462 isPostfixUpdate = atomicCaptureOp.getSecondOp() ==
6463 atomicCaptureOp.getAtomicUpdateOp().getOperation();
6464 auto &innerOpList = atomicUpdateOp.getRegion().front().getOperations();
6465 // Find the binary update operation that uses the region argument
6466 // and get the expression to update
6467 if (innerOpList.size() == 2) {
6468 mlir::Operation &innerOp = *atomicUpdateOp.getRegion().front().begin();
6469 if (!llvm::is_contained(innerOp.getOperands(),
6470 atomicUpdateOp.getRegion().getArgument(0))) {
6471 return atomicUpdateOp.emitError(
6472 "no atomic update operation with region argument"
6473 " as operand found inside atomic.update region");
6474 }
6475 binop = convertBinOpToAtomic(innerOp);
6476 isXBinopExpr =
6477 innerOp.getOperand(0) == atomicUpdateOp.getRegion().getArgument(0);
6478 mlirExpr = (isXBinopExpr ? innerOp.getOperand(1) : innerOp.getOperand(0));
6479 } else {
6480 binop = llvm::AtomicRMWInst::BinOp::BAD_BINOP;
6481 }
6482 }
6483
6484 llvm::Value *llvmExpr = moduleTranslation.lookupValue(mlirExpr);
6485 llvm::Value *llvmX =
6486 moduleTranslation.lookupValue(atomicCaptureOp.getAtomicReadOp().getX());
6487 llvm::Value *llvmV =
6488 moduleTranslation.lookupValue(atomicCaptureOp.getAtomicReadOp().getV());
6489 llvm::Type *llvmXElementType = moduleTranslation.convertType(
6490 atomicCaptureOp.getAtomicReadOp().getElementType());
6491 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {llvmX, llvmXElementType,
6492 /*isSigned=*/false,
6493 /*isVolatile=*/false};
6494 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicV = {llvmV, llvmXElementType,
6495 /*isSigned=*/false,
6496 /*isVolatile=*/false};
6497
6498 llvm::AtomicOrdering atomicOrdering =
6499 convertAtomicOrdering(atomicCaptureOp.getMemoryOrder());
6500
6501 auto updateFn =
6502 [&](llvm::Value *atomicx,
6503 llvm::IRBuilder<> &builder) -> llvm::Expected<llvm::Value *> {
6504 if (atomicWriteOp)
6505 return moduleTranslation.lookupValue(atomicWriteOp.getExpr());
6506 Block &bb = *atomicUpdateOp.getRegion().begin();
6507 moduleTranslation.mapValue(*atomicUpdateOp.getRegion().args_begin(),
6508 atomicx);
6509 moduleTranslation.mapBlock(&bb, builder.GetInsertBlock());
6510 if (failed(moduleTranslation.convertBlock(bb, true, builder)))
6511 return llvm::make_error<PreviouslyReportedError>();
6512
6513 omp::YieldOp yieldop = dyn_cast<omp::YieldOp>(bb.getTerminator());
6514 assert(yieldop && yieldop.getResults().size() == 1 &&
6515 "terminator must be omp.yield op and it must have exactly one "
6516 "argument");
6517 return moduleTranslation.lookupValue(yieldop.getResults()[0]);
6518 };
6519
6520 bool isIgnoreDenormalMode;
6521 bool isFineGrainedMemory;
6522 bool isRemoteMemory;
6523 extractAtomicControlFlags(atomicUpdateOp, isIgnoreDenormalMode,
6524 isFineGrainedMemory, isRemoteMemory);
6525 // Handle ambiguous alloca, if any.
6526 auto allocaIP = findAllocInsertPoints(builder, moduleTranslation);
6527 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6528 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
6529 ompBuilder->createAtomicCapture(
6530 ompLoc, allocaIP, llvmAtomicX, llvmAtomicV, llvmExpr, atomicOrdering,
6531 binop, updateFn, atomicUpdateOp, isPostfixUpdate, isXBinopExpr,
6532 isIgnoreDenormalMode, isFineGrainedMemory, isRemoteMemory);
6533
6534 if (failed(handleError(afterIP, *atomicCaptureOp)))
6535 return failure();
6536
6537 builder.restoreIP(*afterIP);
6538 return success();
6539}
6540
6541/// Converts an omp.atomic.compare operation to LLVM IR.
6542///
6543/// if (x == e) x = d
6544/// The region contains a comparison + select pattern:
6545/// ^bb0(%xval: T):
6546/// %cmp = llvm.icmp/fcmp <pred> %xval, %e : T
6547/// %sel = llvm.select %cmp, %d, %xval : i1, T
6548/// omp.yield(%sel : T)
6549///
6550/// From MLIR extract:
6551/// 1) comparison operator
6552/// 2) expected value (e)
6553/// 3) desired value (d)
6554/// These are passed to OpenMPIRBuilder::createAtomicCompare which generates
6555/// the actual cmpxchg / atomicrmw instruction.
6556///
6557static LogicalResult
6558convertOmpAtomicCompare(omp::AtomicCompareOp atomicCompareOp,
6559 llvm::IRBuilderBase &builder,
6560 LLVM::ModuleTranslation &moduleTranslation) {
6561 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
6562 if (failed(checkImplementationStatus(*atomicCompareOp)))
6563 return failure();
6564
6565 Region &region = atomicCompareOp.getRegion();
6566 Block &block = region.front();
6567
6568 // Determine element type from the region block argument
6569 llvm::Type *llvmXElementType =
6570 moduleTranslation.convertType(block.getArgument(0).getType());
6571 if (!llvmXElementType)
6572 return atomicCompareOp.emitError(
6573 "unable to determine element type for atomic compare");
6574
6575 llvm::Value *llvmX = moduleTranslation.lookupValue(atomicCompareOp.getX());
6576
6577 // IsSigned is determined from the comparison predicate in the region.
6578 // Signed ICmp predicates (slt/sgt) set this to true; unsigned (ult/ugt)
6579 // leave it false. For EQ and float comparisons, signedness is irrelevant.
6580 bool isSigned = false;
6581 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {llvmX, llvmXElementType,
6582 isSigned,
6583 /*IsVolatile=*/false};
6584
6585 llvm::AtomicOrdering atomicOrdering =
6586 convertAtomicOrdering(atomicCompareOp.getMemoryOrder());
6587
6588 auto isAtomicComparePatternOp = [](Operation &op) {
6589 return llvm::isa<LLVM::ICmpOp, LLVM::FCmpOp, LLVM::SelectOp, LLVM::AndOp,
6590 LLVM::OrOp, LLVM::SMaxOp, LLVM::SMinOp, LLVM::UMaxOp,
6591 LLVM::UMinOp, LLVM::MaxNumOp, LLVM::MinNumOp,
6592 mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
6593 mlir::arith::MaxUIOp, mlir::arith::MinUIOp,
6594 mlir::arith::MaximumFOp, mlir::arith::MinimumFOp>(op);
6595 };
6596
6597 // Pre-translate operations inside the region that compute e and d (e.g.,
6598 // GEP, loads for dereferencing Fortran pointers) but are not part of the
6599 // atomic compare-and-swap pattern (icmp/fcmp, select, and/or).
6600 //
6601 // 1) Validity: The OpenMP spec requires e and d to be evaluated before the
6602 // atomic operation, so emitting their computation here is correct.
6603 // 2) Memory effects: These ops only depend on values defined outside the
6604 // region. They cannot observe the block argument (%xval), which is the
6605 // value loaded atomically by cmpxchg and does not exist yet.
6606 // 3) Invariant enforcement: The `allOperandsMapped` check below skips any
6607 // op whose operands include the unmapped block argument, guaranteeing
6608 // only region-external-dependent ops are pre-translated.
6609 for (Operation &op : block.without_terminator()) {
6610 // Skip operations that form the atomic compare pattern — these are
6611 // not emitted as individual instructions but are analyzed below to
6612 // extract the comparison predicate, expected value (e), and desired
6613 // value (d) for generating a single cmpxchg/atomicrmw.
6614 if (isAtomicComparePatternOp(op))
6615 continue;
6616
6617 // Avoid translating ops that depend on the unmapped block argument.
6618 bool allOperandsMapped = llvm::all_of(op.getOperands(), [&](mlir::Value v) {
6619 return moduleTranslation.lookupValue(v) != nullptr;
6620 });
6621 if (!allOperandsMapped)
6622 continue;
6623
6624 if (failed(moduleTranslation.convertOperation(op, builder)))
6625 return atomicCompareOp.emitError(
6626 "failed to translate operation inside atomic compare region");
6627 }
6628
6629 // Look up a value that may have been pre-translated or defined outside the
6630 // region.
6631 auto materializeValue = [&](mlir::Value val) -> llvm::Value * {
6632 // Check if the value is already mapped (pre-translated or defined outside).
6633 if (llvm::Value *existing = moduleTranslation.lookupValue(val))
6634 return existing;
6635 // Fallback for a single LoadOp whose address is mapped but whose result
6636 // was not pre-translated.
6637 if (auto loadOp = val.getDefiningOp<LLVM::LoadOp>()) {
6638 if (loadOp->getParentRegion() == &region) {
6639 llvm::Value *loadAddr = moduleTranslation.lookupValue(loadOp.getAddr());
6640 if (!loadAddr)
6641 return nullptr;
6642 llvm::Type *loadType =
6643 moduleTranslation.convertType(loadOp.getResult().getType());
6644 return builder.CreateLoad(loadType, loadAddr);
6645 }
6646 }
6647 return nullptr;
6648 };
6649
6650 // Walk the region to extract comparison predicate, eVal, and dVal.
6651 // if (x == eVal) x = dVal
6652 llvm::omp::OMPAtomicCompareOp compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
6653 llvm::Value *eVal = nullptr;
6654 llvm::Value *dVal = nullptr;
6655 bool isXBinopExpr = false;
6656
6657 // Check for a decomposed complex comparison pattern (extractvalue + fcmp +
6658 // and/or of the real/imaginary fields).
6660 bool isComplexPattern = cplx.isComplex;
6661 if (isComplexPattern) {
6662 if (cplx.isNE)
6663 // OrOp corresponds to NE, which is not a valid atomic compare op.
6664 return atomicCompareOp.emitError(
6665 "unsupported comparison predicate (NE) for complex atomic compare");
6666 compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
6667 isXBinopExpr = cplx.isXBinopExpr;
6668 eVal = materializeValue(cplx.eAggregate);
6669 }
6670
6671 if (isComplexPattern) {
6672 // dVal from SelectOp or YieldOp.
6673 for (Operation &op : block.getOperations()) {
6674 if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
6675 dVal = materializeValue(selectOp.getTrueValue());
6676 break;
6677 }
6678 }
6679 if (!dVal) {
6680 auto yieldOp = cast<omp::YieldOp>(block.getTerminator());
6681 if (yieldOp.getResults().empty())
6682 return atomicCompareOp.emitError(
6683 "failed to extract desired value (d) from atomic compare region");
6684 dVal = materializeValue(yieldOp.getResults()[0]);
6685 }
6686
6687 llvm::Value *oldComplex = nullptr;
6688 llvm::Value *cmpOk = nullptr;
6689 llvm::AtomicOrdering failOrdering =
6690 getAtomicCompareFailureOrdering(atomicCompareOp, atomicOrdering);
6691 emitComplexAtomicCmpXchg(builder, llvmX, llvmXElementType, eVal, dVal,
6692 atomicOrdering, failOrdering,
6693 atomicCompareOp.getWeak(), oldComplex, cmpOk);
6694 (void)oldComplex;
6695 (void)cmpOk;
6696
6697 // Emit flush after atomic compare if needed (for release, acq_rel,
6698 // seq_cst orderings).
6699 if (atomicOrdering == llvm::AtomicOrdering::Release ||
6700 atomicOrdering == llvm::AtomicOrdering::AcquireRelease ||
6701 atomicOrdering == llvm::AtomicOrdering::SequentiallyConsistent) {
6702 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6703 ompBuilder->createFlush(ompLoc);
6704 }
6705 return success();
6706 } else {
6707 AtomicComparePatternInfo patternInfo;
6708 if (failed(extractAtomicComparePattern(block, materializeValue,
6709 atomicCompareOp, patternInfo)))
6710 return failure();
6711 compareOp = patternInfo.compareOp;
6712 eVal = patternInfo.eVal;
6713 dVal = patternInfo.dVal;
6714 isXBinopExpr = patternInfo.isXBinopExpr;
6715 isSigned = patternInfo.isSigned;
6716 }
6717
6718 if (!eVal)
6719 return atomicCompareOp.emitError(
6720 "failed to extract expected value (e) from atomic compare region");
6721 if (!dVal) {
6722 // Fall back to the yield operand.
6723 auto yieldOp = cast<omp::YieldOp>(block.getTerminator());
6724 if (yieldOp.getResults().empty())
6725 return atomicCompareOp.emitError(
6726 "failed to extract desired value (d) from atomic compare region");
6727 dVal = materializeValue(yieldOp.getResults()[0]);
6728 }
6729
6730 llvmAtomicX.IsSigned = isSigned;
6731
6732 llvm::OpenMPIRBuilder::AtomicOpValue vOpVal = {nullptr, nullptr, false,
6733 false};
6734 llvm::OpenMPIRBuilder::AtomicOpValue rOpVal = {nullptr, nullptr, false,
6735 false};
6736 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6737
6738 bool isWeak = atomicCompareOp.getWeak();
6739
6740 bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
6741 llvm::AtomicOrdering failureOrdering =
6742 getAtomicCompareFailureOrdering(atomicCompareOp, atomicOrdering);
6743 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
6744 ompBuilder->createAtomicCompare(
6745 ompLoc, llvmAtomicX, vOpVal, rOpVal, eVal, dVal, atomicOrdering,
6746 compareOp, isXBinopExpr, /*IsPostfixUpdate=*/false,
6747 /*IsFailOnly=*/false, failureOrdering, isWeak);
6748 ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
6749
6750 if (failed(handleError(afterIP, *atomicCompareOp)))
6751 return failure();
6752
6753 builder.restoreIP(*afterIP);
6754 return success();
6755}
6756
6757static llvm::omp::Directive convertCancellationConstructType(
6758 omp::ClauseCancellationConstructType directive) {
6759 switch (directive) {
6760 case omp::ClauseCancellationConstructType::Loop:
6761 return llvm::omp::Directive::OMPD_for;
6762 case omp::ClauseCancellationConstructType::Parallel:
6763 return llvm::omp::Directive::OMPD_parallel;
6764 case omp::ClauseCancellationConstructType::Sections:
6765 return llvm::omp::Directive::OMPD_sections;
6766 case omp::ClauseCancellationConstructType::Taskgroup:
6767 return llvm::omp::Directive::OMPD_taskgroup;
6768 }
6769 llvm_unreachable("Unhandled cancellation construct type");
6770}
6771
6772static LogicalResult
6773convertOmpCancel(omp::CancelOp op, llvm::IRBuilderBase &builder,
6774 LLVM::ModuleTranslation &moduleTranslation) {
6775 if (failed(checkImplementationStatus(*op.getOperation())))
6776 return failure();
6777
6778 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6779 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
6780
6781 llvm::Value *ifCond = nullptr;
6782 if (Value ifVar = op.getIfExpr())
6783 ifCond = moduleTranslation.lookupValue(ifVar);
6784
6785 llvm::omp::Directive cancelledDirective =
6786 convertCancellationConstructType(op.getCancelDirective());
6787
6788 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
6789 ompBuilder->createCancel(ompLoc, ifCond, cancelledDirective);
6790
6791 if (failed(handleError(afterIP, *op.getOperation())))
6792 return failure();
6793
6794 builder.restoreIP(afterIP.get());
6795
6796 return success();
6797}
6798
6799static LogicalResult
6800convertOmpCancellationPoint(omp::CancellationPointOp op,
6801 llvm::IRBuilderBase &builder,
6802 LLVM::ModuleTranslation &moduleTranslation) {
6803 if (failed(checkImplementationStatus(*op.getOperation())))
6804 return failure();
6805
6806 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6807 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
6808
6809 llvm::omp::Directive cancelledDirective =
6810 convertCancellationConstructType(op.getCancelDirective());
6811
6812 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
6813 ompBuilder->createCancellationPoint(ompLoc, cancelledDirective);
6814
6815 if (failed(handleError(afterIP, *op.getOperation())))
6816 return failure();
6817
6818 builder.restoreIP(afterIP.get());
6819
6820 return success();
6821}
6822
6823/// Converts an OpenMP Threadprivate operation into LLVM IR using
6824/// OpenMPIRBuilder.
6825static LogicalResult
6826convertOmpThreadprivate(Operation &opInst, llvm::IRBuilderBase &builder,
6827 LLVM::ModuleTranslation &moduleTranslation) {
6828 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6829 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
6830 auto threadprivateOp = cast<omp::ThreadprivateOp>(opInst);
6831
6832 if (failed(checkImplementationStatus(opInst)))
6833 return failure();
6834
6835 Value symAddr = threadprivateOp.getSymAddr();
6836 auto *symOp = symAddr.getDefiningOp();
6837
6838 if (auto asCast = dyn_cast<LLVM::AddrSpaceCastOp>(symOp))
6839 symOp = asCast.getOperand().getDefiningOp();
6840
6841 if (!isa<LLVM::AddressOfOp>(symOp))
6842 return opInst.emitError("Addressing symbol not found");
6843 LLVM::AddressOfOp addressOfOp = dyn_cast<LLVM::AddressOfOp>(symOp);
6844
6845 LLVM::GlobalOp global =
6846 addressOfOp.getGlobal(moduleTranslation.symbolTable());
6847 llvm::GlobalValue *globalValue = moduleTranslation.lookupGlobal(global);
6848 llvm::Type *type = globalValue->getValueType();
6849 llvm::TypeSize typeSize =
6850 builder.GetInsertBlock()->getModule()->getDataLayout().getTypeStoreSize(
6851 type);
6852 llvm::ConstantInt *size = builder.getInt64(typeSize.getFixedValue());
6853 llvm::Value *callInst = ompBuilder->createCachedThreadPrivate(
6854 ompLoc, globalValue, size, global.getSymName() + ".cache");
6855 moduleTranslation.mapValue(opInst.getResult(0), callInst);
6856
6857 return success();
6858}
6859
6860static llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseKind
6861convertToDeviceClauseKind(mlir::omp::DeclareTargetDeviceType deviceClause) {
6862 switch (deviceClause) {
6863 case mlir::omp::DeclareTargetDeviceType::host:
6864 return llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseHost;
6865 break;
6866 case mlir::omp::DeclareTargetDeviceType::nohost:
6867 return llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseNoHost;
6868 break;
6869 case mlir::omp::DeclareTargetDeviceType::any:
6870 return llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseAny;
6871 break;
6872 }
6873 llvm_unreachable("unhandled device clause");
6874}
6875
6876static llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryKind
6878 mlir::omp::DeclareTargetCaptureClause captureClause) {
6879 switch (captureClause) {
6880 case mlir::omp::DeclareTargetCaptureClause::to:
6881 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryTo;
6882 case mlir::omp::DeclareTargetCaptureClause::link:
6883 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryLink;
6884 case mlir::omp::DeclareTargetCaptureClause::enter:
6885 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryEnter;
6886 case mlir::omp::DeclareTargetCaptureClause::none:
6887 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryNone;
6888 }
6889 llvm_unreachable("unhandled capture clause");
6890}
6891
6893 Operation *op = value.getDefiningOp();
6894 if (auto addrCast = dyn_cast_if_present<LLVM::AddrSpaceCastOp>(op))
6895 op = addrCast->getOperand(0).getDefiningOp();
6896 if (auto addressOfOp = dyn_cast_if_present<LLVM::AddressOfOp>(op)) {
6897 auto modOp = addressOfOp->getParentOfType<mlir::ModuleOp>();
6898 return modOp.lookupSymbol(addressOfOp.getGlobalName());
6899 }
6900 return nullptr;
6901}
6902
6904 while (Operation *op = value.getDefiningOp()) {
6905 if (auto addrCast = dyn_cast_if_present<LLVM::AddrSpaceCastOp>(op))
6906 value = addrCast.getOperand();
6907 // Traces through hlfir.declare, fir.declare to reach the base address and
6908 // use for type lookup.
6909 else if (op->getName().getIdentifier() &&
6910 (op->getName().getIdentifier().str() == "hlfir.declare" ||
6911 op->getName().getIdentifier().str() == "fir.declare")) {
6912 if (op->getNumOperands() > 0)
6913 value = op->getOperand(0);
6914 else
6915 break;
6916 } else {
6917 break;
6918 }
6919 }
6920 return value;
6921}
6922
6923static llvm::SmallString<64>
6924getDeclareTargetRefPtrSuffix(LLVM::GlobalOp globalOp,
6925 llvm::OpenMPIRBuilder &ompBuilder,
6926 llvm::vfs::FileSystem &vfs) {
6927 llvm::SmallString<64> suffix;
6928 llvm::raw_svector_ostream os(suffix);
6929 if (globalOp.getVisibility() == mlir::SymbolTable::Visibility::Private) {
6930 auto loc = globalOp->getLoc()->findInstanceOf<FileLineColLoc>();
6931 auto fileInfoCallBack = [&loc]() {
6932 return std::pair<std::string, uint64_t>(
6933 llvm::StringRef(loc.getFilename()), loc.getLine());
6934 };
6935
6936 os << llvm::format(
6937 "_%x",
6938 ompBuilder.getTargetEntryUniqueInfo(fileInfoCallBack, vfs).FileID);
6939 }
6940 os << "_decl_tgt_ref_ptr";
6941
6942 return suffix;
6943}
6944
6945static bool isDeclareTargetLink(Value value) {
6946 if (auto declareTargetGlobal =
6947 dyn_cast_if_present<omp::DeclareTargetInterface>(
6948 getGlobalOpFromValue(value)))
6949 if (declareTargetGlobal.getDeclareTargetCaptureClause() ==
6950 omp::DeclareTargetCaptureClause::link)
6951 return true;
6952 return false;
6953}
6954
6955static bool isDeclareTargetTo(Value value) {
6956 if (auto declareTargetGlobal =
6957 dyn_cast_if_present<omp::DeclareTargetInterface>(
6958 getGlobalOpFromValue(value)))
6959 if (declareTargetGlobal.getDeclareTargetCaptureClause() ==
6960 omp::DeclareTargetCaptureClause::to ||
6961 declareTargetGlobal.getDeclareTargetCaptureClause() ==
6962 omp::DeclareTargetCaptureClause::enter)
6963 return true;
6964 return false;
6965}
6966
6967// Returns the reference pointer generated by the lowering of the declare
6968// target operation in cases where the link clause is used or the to clause is
6969// used in USM mode.
6970static llvm::Value *
6972 LLVM::ModuleTranslation &moduleTranslation) {
6973 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
6974 if (auto gOp =
6975 dyn_cast_or_null<LLVM::GlobalOp>(getGlobalOpFromValue(value))) {
6976 // In this case, we must utilise the reference pointer generated by
6977 // the declare target operation, similar to Clang
6978 if (isDeclareTargetLink(value) ||
6979 (isDeclareTargetTo(value) &&
6980 ompBuilder->Config.hasRequiresUnifiedSharedMemory())) {
6982 gOp, *ompBuilder, moduleTranslation.getFileSystem());
6983
6984 if (gOp.getSymName().contains(suffix))
6985 return moduleTranslation.getLLVMModule()->getNamedValue(
6986 gOp.getSymName());
6987
6988 return moduleTranslation.getLLVMModule()->getNamedValue(
6989 (gOp.getSymName().str() + suffix.str()).str());
6990 }
6991 }
6992 return nullptr;
6993}
6994
6995namespace {
6996// Append customMappers information to existing MapInfosTy
6997struct MapInfosTy : llvm::OpenMPIRBuilder::MapInfosTy {
6998 SmallVector<Operation *, 4> Mappers;
6999
7000 /// Append arrays in \a CurInfo.
7001 void append(MapInfosTy &curInfo) {
7002 Mappers.append(curInfo.Mappers.begin(), curInfo.Mappers.end());
7003 llvm::OpenMPIRBuilder::MapInfosTy::append(curInfo);
7004 }
7005};
7006// A small helper structure to contain data gathered
7007// for map lowering and coalese it into one area and
7008// avoiding extra computations such as searches in the
7009// llvm module for lowered mapped variables or checking
7010// if something is declare target (and retrieving the
7011// value) more than neccessary.
7012struct MapInfoData : MapInfosTy {
7013 llvm::SmallVector<bool, 4> IsDeclareTarget;
7014 llvm::SmallVector<bool, 4> IsAMember;
7015 // Identify if mapping was added by mapClause or use_device clauses.
7016 llvm::SmallVector<bool, 4> IsAMapping;
7017 llvm::SmallVector<mlir::Operation *, 4> MapClause;
7018 llvm::SmallVector<llvm::Value *, 4> OriginalValue;
7019 // Stripped off array/pointer to get the underlying
7020 // element type
7021 llvm::SmallVector<llvm::Type *, 4> BaseType;
7022
7023 /// Append arrays in \a CurInfo.
7024 void append(MapInfoData &CurInfo) {
7025 IsDeclareTarget.append(CurInfo.IsDeclareTarget.begin(),
7026 CurInfo.IsDeclareTarget.end());
7027 MapClause.append(CurInfo.MapClause.begin(), CurInfo.MapClause.end());
7028 OriginalValue.append(CurInfo.OriginalValue.begin(),
7029 CurInfo.OriginalValue.end());
7030 BaseType.append(CurInfo.BaseType.begin(), CurInfo.BaseType.end());
7031 MapInfosTy::append(CurInfo);
7032 }
7033};
7034
7035enum class TargetDirectiveEnumTy : uint32_t {
7036 None = 0,
7037 Target = 1,
7038 TargetData = 2,
7039 TargetEnterData = 3,
7040 TargetExitData = 4,
7041 TargetUpdate = 5
7042};
7043
7044static TargetDirectiveEnumTy getTargetDirectiveEnumTyFromOp(Operation *op) {
7045 return llvm::TypeSwitch<Operation *, TargetDirectiveEnumTy>(op)
7046 .Case([](omp::TargetDataOp) { return TargetDirectiveEnumTy::TargetData; })
7047 .Case([](omp::TargetEnterDataOp) {
7048 return TargetDirectiveEnumTy::TargetEnterData;
7049 })
7050 .Case([&](omp::TargetExitDataOp) {
7051 return TargetDirectiveEnumTy::TargetExitData;
7052 })
7053 .Case([&](omp::TargetUpdateOp) {
7054 return TargetDirectiveEnumTy::TargetUpdate;
7055 })
7056 .Case([&](omp::TargetOp) { return TargetDirectiveEnumTy::Target; })
7057 .Default([&](Operation *op) { return TargetDirectiveEnumTy::None; });
7058}
7059
7060} // namespace
7061
7062static uint64_t getArrayElementSizeInBits(LLVM::LLVMArrayType arrTy,
7063 DataLayout &dl) {
7064 if (auto nestedArrTy = llvm::dyn_cast_if_present<LLVM::LLVMArrayType>(
7065 arrTy.getElementType()))
7066 return getArrayElementSizeInBits(nestedArrTy, dl);
7067 return dl.getTypeSizeInBits(arrTy.getElementType());
7068}
7069
7070// The intent is to verify if the mapped data being passed is a
7071// pointer -> pointee that requires special handling in certain cases,
7072// e.g. applying the OMP_MAP_PTR_AND_OBJ map type.
7073//
7074// There may be a better way to verify this, but unfortunately with
7075// opaque pointers we lose the ability to easily check if something is
7076// a pointer whilst maintaining access to the underlying type.
7077static bool checkIfPointerMap(omp::MapInfoOp mapOp) {
7078 // If we have a varPtrPtr field assigned then the underlying type is a pointer
7079 if (mapOp.getVarPtrPtr())
7080 return true;
7081
7082 // If the map data is declare target with a link clause, then it's represented
7083 // as a pointer when we lower it to LLVM-IR even if at the MLIR level it has
7084 // no relation to pointers.
7085 if (isDeclareTargetLink(mapOp.getVarPtr()))
7086 return true;
7087
7088 return false;
7089}
7090
7091// This function calculates the size to be offloaded for a specified type, given
7092// its associated map clause (which can contain bounds information which affects
7093// the total size), this size is calculated based on the underlying element type
7094// e.g. given a 1-D array of ints, we will calculate the size from the integer
7095// type * number of elements in the array. This size can be used in other
7096// calculations but is ultimately used as an argument to the OpenMP runtimes
7097// kernel argument structure which is generated through the combinedInfo data
7098// structures.
7099// This function is somewhat equivalent to Clang's getExprTypeSize inside of
7100// CGOpenMPRuntime.cpp.
7101static llvm::Value *getSizeInBytes(DataLayout &dl, const mlir::Type &type,
7102 Operation *clauseOp,
7103 llvm::Value *basePointer,
7104 llvm::Type *baseType,
7105 llvm::IRBuilderBase &builder,
7106 LLVM::ModuleTranslation &moduleTranslation) {
7107 if (auto memberClause =
7108 mlir::dyn_cast_if_present<mlir::omp::MapInfoOp>(clauseOp)) {
7109 // This calculates the size to transfer based on bounds and the underlying
7110 // element type, provided bounds have been specified (Fortran
7111 // pointers/allocatables/target and arrays that have sections specified fall
7112 // into this as well)
7113 if (!memberClause.getBounds().empty()) {
7114 llvm::Value *elementCount = builder.getInt64(1);
7115 for (auto bounds : memberClause.getBounds()) {
7116 if (auto boundOp = mlir::dyn_cast_if_present<mlir::omp::MapBoundsOp>(
7117 bounds.getDefiningOp())) {
7118 // The below calculation for the size to be mapped calculated from the
7119 // map.info's bounds is: (elemCount * [UB - LB] + 1), later we
7120 // multiply by the underlying element types byte size to get the full
7121 // size to be offloaded based on the bounds
7122 elementCount = builder.CreateMul(
7123 elementCount,
7124 builder.CreateAdd(
7125 builder.CreateSub(
7126 moduleTranslation.lookupValue(boundOp.getUpperBound()),
7127 moduleTranslation.lookupValue(boundOp.getLowerBound())),
7128 builder.getInt64(1)));
7129 }
7130 }
7131
7132 // utilising getTypeSizeInBits instead of getTypeSize as getTypeSize gives
7133 // the size in inconsistent byte or bit format.
7134 uint64_t underlyingTypeSzInBits = dl.getTypeSizeInBits(type);
7135 if (auto arrTy = llvm::dyn_cast_if_present<LLVM::LLVMArrayType>(type))
7136 underlyingTypeSzInBits = getArrayElementSizeInBits(arrTy, dl);
7137
7138 // The size in bytes x number of elements, the sizeInBytes stored is
7139 // the underyling types size, e.g. if ptr<i32>, it'll be the i32's
7140 // size, so we do some on the fly runtime math to get the size in
7141 // bytes from the extent (ub - lb) * sizeInBytes. NOTE: This may need
7142 // some adjustment for members with more complex types.
7143 llvm::Value *sizeCalc = builder.CreateMul(
7144 elementCount, builder.getInt64(underlyingTypeSzInBits / 8),
7145 "element_count");
7146
7147 // This is a part of a "complicated" bit of size calculation logic that is
7148 // in place to handle a couple of scenarios, one specific to Fortran and
7149 // the other a more general OpenMP issue. The other piece of the
7150 // calculation can be found as the final size calculation within the
7151 // processIndividualMap function. Ideally we would move it here, but due
7152 // to the complexity of calculating the final base address of some
7153 // constructs (required for a nullary check), it's left as the final step.
7154 // So, in the below 2 cases, the nullary check is in processIndividualMap
7155 // and the size equality check is here. The cases this modifications help
7156 // cover are:
7157 //
7158 // 1) If an argument has a null base pointer, then the size must be set to
7159 // 0 to avoid the runtime exploding/complaining about an illegal
7160 // pointer map. The size returning non-zero is feasible in certain
7161 // cases if for example someone has specified there own bounds/range.
7162 // 2) We wish to support a very specific OpenMP Fortran edge-case where a
7163 // size zero array can be legally presence checked and found to be on
7164 // device when it has been mapped. In these rare occasions the
7165 // allocatable/pointer will have a size of 1 allocated for the
7166 // underlying data, but this wall not be represented within the size of
7167 // the descriptor, so we get a non-nullary pointer and a size of 0,
7168 // allowing us to specify a size of 1 in these cases registering it on
7169 // the device mapping table as present.
7170 //
7171 // The default fall through case is just returning the size calculation
7172 // above, if we are not nullary and the size we calculate is non-zero,
7173 // which is basically any pointer type that is allocated in someway
7174 // (providing you are not running on a rare system that allows malloc's of
7175 // size 0 with whatever caveats that may come with).
7176 //
7177 // Later in the nullary check in processIndividualMap it just devolves to
7178 // selecting a size of 0 if we are nullary, if we are not, we will return
7179 // either 1 or the calculated size, depending on the outcome of this
7180 // select.
7181 if (checkIfPointerMap(memberClause)) {
7182 return builder.CreateSelect(
7183 builder.CreateICmpEQ(sizeCalc, builder.getInt64(0)),
7184 builder.getInt64(1), sizeCalc);
7185 }
7186
7187 return sizeCalc;
7188 }
7189 }
7190
7191 return builder.getInt64(dl.getTypeSizeInBits(type) / 8);
7192}
7193
7194// Convert the MLIR map flag set to the runtime map flag set for embedding
7195// in LLVM-IR. This is important as the two bit-flag lists do not correspond
7196// 1-to-1 as there's flags the runtime doesn't care about and vice versa.
7197// Certain flags are discarded here such as RefPtee and co.
7198static llvm::omp::OpenMPOffloadMappingFlags
7199convertClauseMapFlags(omp::ClauseMapFlags mlirFlags) {
7200 const bool hasExplicitMap =
7201 (mlirFlags & ~omp::ClauseMapFlags::is_device_ptr) !=
7202 omp::ClauseMapFlags::none;
7203
7204 llvm::omp::OpenMPOffloadMappingFlags mapType =
7205 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE;
7206
7207 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::to))
7208 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TO;
7209
7210 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::from))
7211 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_FROM;
7212
7213 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::always))
7214 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ALWAYS;
7215
7216 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::del))
7217 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_DELETE;
7218
7219 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::return_param))
7220 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
7221
7222 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::priv))
7223 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_PRIVATE;
7224
7225 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::literal))
7226 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL;
7227
7228 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::implicit))
7229 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_IMPLICIT;
7230
7231 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::close))
7232 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_CLOSE;
7233
7234 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::present))
7235 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_PRESENT;
7236
7237 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::ompx_hold))
7238 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_OMPX_HOLD;
7239
7240 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::attach))
7241 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH;
7242
7243 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::is_device_ptr)) {
7244 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TARGET_PARAM;
7245 if (!hasExplicitMap)
7246 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL;
7247 }
7248
7249 return mapType;
7250}
7251
7253 MapInfoData &mapData, SmallVectorImpl<Value> &mapVars,
7254 LLVM::ModuleTranslation &moduleTranslation, DataLayout &dl,
7255 llvm::IRBuilderBase &builder, ArrayRef<Value> useDevPtrOperands = {},
7256 ArrayRef<Value> useDevAddrOperands = {},
7257 ArrayRef<Value> hasDevAddrOperands = {}) {
7258
7259 auto checkRefPtrOrPteeMapWithAttach = [](omp::ClauseMapFlags mapType) {
7260 bool hasRefType =
7261 bitEnumContainsAll(mapType, omp::ClauseMapFlags::ref_ptr) ||
7262 bitEnumContainsAll(mapType, omp::ClauseMapFlags::ref_ptee);
7263 return hasRefType &&
7264 bitEnumContainsAll(mapType, omp::ClauseMapFlags::attach);
7265 };
7266
7267 auto checkIsAMember = [](const auto &mapVars, auto mapOp) {
7268 // Check if this is a member mapping and correctly assign that it is, if
7269 // it is a member of a larger object.
7270 // TODO: Need better handling of members, and distinguishing of members
7271 // that are implicitly allocated on device vs explicitly passed in as
7272 // arguments.
7273 // TODO: May require some further additions to support nested record
7274 // types, i.e. member maps that can have member maps.
7275 for (Value mapValue : mapVars) {
7276 auto map = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
7277 for (auto member : map.getMembers())
7278 if (member == mapOp)
7279 return true;
7280 }
7281 return false;
7282 };
7283
7284 // Process MapOperands
7285 for (Value mapValue : mapVars) {
7286 auto mapOp = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
7287 bool isRefPtrOrPteeMapWithAttach =
7288 checkRefPtrOrPteeMapWithAttach(mapOp.getMapType());
7289 Value offloadPtr = (mapOp.getVarPtrPtr() && !isRefPtrOrPteeMapWithAttach)
7290 ? mapOp.getVarPtrPtr()
7291 : mapOp.getVarPtr();
7292 mapData.OriginalValue.push_back(moduleTranslation.lookupValue(offloadPtr));
7293 mapData.Pointers.push_back(
7294 isRefPtrOrPteeMapWithAttach
7295 ? moduleTranslation.lookupValue(mapOp.getVarPtrPtr())
7296 : mapData.OriginalValue.back());
7297
7298 if (llvm::Value *refPtr =
7299 getRefPtrIfDeclareTarget(offloadPtr, moduleTranslation)) {
7300 mapData.IsDeclareTarget.push_back(true);
7301 mapData.BasePointers.push_back(refPtr);
7302 } else if (isDeclareTargetTo(offloadPtr)) {
7303 mapData.IsDeclareTarget.push_back(true);
7304 mapData.BasePointers.push_back(mapData.OriginalValue.back());
7305 } else { // regular mapped variable
7306 mapData.IsDeclareTarget.push_back(false);
7307 mapData.BasePointers.push_back(mapData.OriginalValue.back());
7308 }
7309
7310 // In every situation we currently have if we have a varPtrPtr present
7311 // we wish to utilise it's type for the base type, main cases are
7312 // currently Fortran descriptor base address maps and attach maps.
7313 mapData.BaseType.push_back(moduleTranslation.convertType(
7314 mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtrType().value()
7315 : mapOp.getVarPtrType()));
7316
7317 // For the attach map cases, it's a little odd, as we effectively have to
7318 // utilise the base address (including all bounds offsets) for the pointer
7319 // field, the pointer address for the base address field, and the pointer
7320 // not the data (base addresses) size. So we end up with a mix of base
7321 // types and sizes we wish to insert here.
7322 mlir::Type sizeType = (isRefPtrOrPteeMapWithAttach || !mapOp.getVarPtrPtr())
7323 ? mapOp.getVarPtrType()
7324 : mapOp.getVarPtrPtrType().value();
7325 mapData.Sizes.push_back(getSizeInBytes(
7326 dl, sizeType, isRefPtrOrPteeMapWithAttach ? nullptr : mapOp,
7327 mapData.Pointers.back(), moduleTranslation.convertType(sizeType),
7328 builder, moduleTranslation));
7329 mapData.MapClause.push_back(mapOp.getOperation());
7330 mapData.Types.push_back(convertClauseMapFlags(mapOp.getMapType()));
7331 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7332 mapData.HasAttachPtr.push_back(false);
7333 mapData.Names.push_back(LLVM::createMappingInformation(
7334 mapOp.getLoc(), *moduleTranslation.getOpenMPBuilder()));
7335 mapData.DevicePointers.push_back(llvm::OpenMPIRBuilder::DeviceInfoTy::None);
7336 if (mapOp.getMapperId())
7337 mapData.Mappers.push_back(
7339 mapOp, mapOp.getMapperIdAttr()));
7340 else
7341 mapData.Mappers.push_back(nullptr);
7342 mapData.IsAMapping.push_back(true);
7343 mapData.IsAMember.push_back(checkIsAMember(mapVars, mapOp));
7344 }
7345
7346 auto findMapInfo = [&mapData](llvm::Value *val,
7347 llvm::OpenMPIRBuilder::DeviceInfoTy devInfoTy,
7348 size_t memberCount) {
7349 unsigned index = 0;
7350 bool found = false;
7351 for (llvm::Value *basePtr : mapData.OriginalValue) {
7352 auto mapOp = cast<omp::MapInfoOp>(mapData.MapClause[index]);
7353 // TODO: Currently we define an equivalent mapping as
7354 // the same base pointer and an equivalent member count, but
7355 // that is a loose definition. We may have to extend to check
7356 // for other fields (varPtrPtr/individual members being mapped).
7357 // Note: Attach maps are not the same as a normal data transfer
7358 // they specify to the runtime to perform an attach map and they
7359 // (at least at the moment) are never something we would aim to
7360 // return in a use_dev_* clause, so they are skipped in terms of
7361 // duplicate maps.
7362 bool isAttachMap =
7363 (mapData.Types[index] &
7364 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) ==
7365 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH;
7366 if (!isAttachMap && basePtr == val && mapData.IsAMapping[index] &&
7367 memberCount == mapOp.getMembers().size()) {
7368 found = true;
7369 mapData.Types[index] |=
7370 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
7371 mapData.DevicePointers[index] = devInfoTy;
7372 }
7373 index++;
7374 }
7375 return found;
7376 };
7377
7378 // Process useDevPtr(Addr)Operands
7379 auto addDevInfos = [&](const llvm::ArrayRef<Value> &useDevOperands,
7380 llvm::OpenMPIRBuilder::DeviceInfoTy devInfoTy) {
7381 for (Value mapValue : useDevOperands) {
7382 auto mapOp = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
7383 Value offloadPtr =
7384 mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtr() : mapOp.getVarPtr();
7385 llvm::Value *origValue = moduleTranslation.lookupValue(offloadPtr);
7386
7387 // Check if map info is already present for this entry.
7388 if (!findMapInfo(origValue, devInfoTy, mapOp.getMembers().size())) {
7389 mapData.OriginalValue.push_back(origValue);
7390 mapData.Pointers.push_back(mapData.OriginalValue.back());
7391 mapData.IsDeclareTarget.push_back(false);
7392 mapData.BasePointers.push_back(mapData.OriginalValue.back());
7393 mlir::Type baseTy = mapOp.getVarPtrPtr()
7394 ? mapOp.getVarPtrPtrType().value()
7395 : mapOp.getVarPtrType();
7396 mapData.BaseType.push_back(moduleTranslation.convertType(baseTy));
7397 mapData.Sizes.push_back(builder.getInt64(0));
7398 mapData.MapClause.push_back(mapOp.getOperation());
7399 mapData.Types.push_back(
7400 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM);
7401 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7402 mapData.HasAttachPtr.push_back(false);
7403 mapData.Names.push_back(LLVM::createMappingInformation(
7404 mapOp.getLoc(), *moduleTranslation.getOpenMPBuilder()));
7405 mapData.DevicePointers.push_back(devInfoTy);
7406 mapData.Mappers.push_back(nullptr);
7407 mapData.IsAMapping.push_back(false);
7408 mapData.IsAMember.push_back(checkIsAMember(useDevOperands, mapOp));
7409 }
7410 }
7411 };
7412
7413 addDevInfos(useDevAddrOperands, llvm::OpenMPIRBuilder::DeviceInfoTy::Address);
7414 addDevInfos(useDevPtrOperands, llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer);
7415
7416 for (Value mapValue : hasDevAddrOperands) {
7417 auto mapOp = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
7418 Value offloadPtr =
7419 mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtr() : mapOp.getVarPtr();
7420 llvm::Value *origValue = moduleTranslation.lookupValue(offloadPtr);
7421 auto mapType = convertClauseMapFlags(mapOp.getMapType());
7422 auto mapTypeAlways = llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ALWAYS;
7423 bool isDevicePtr =
7424 (mapOp.getMapType() & omp::ClauseMapFlags::is_device_ptr) !=
7425 omp::ClauseMapFlags::none;
7426
7427 mapData.OriginalValue.push_back(origValue);
7428 mapData.BasePointers.push_back(origValue);
7429 mapData.Pointers.push_back(origValue);
7430 mapData.IsDeclareTarget.push_back(false);
7431
7432 mlir::Type baseTy = mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtrType().value()
7433 : mapOp.getVarPtrType();
7434 mapData.BaseType.push_back(moduleTranslation.convertType(baseTy));
7435 mapData.Sizes.push_back(builder.getInt64(dl.getTypeSize(baseTy)));
7436
7437 mapData.MapClause.push_back(mapOp.getOperation());
7438 if (llvm::to_underlying(mapType & mapTypeAlways)) {
7439 // Descriptors are mapped with the ALWAYS flag, since they can get
7440 // rematerialized, so the address of the decriptor for a given object
7441 // may change from one place to another.
7442 mapData.Types.push_back(mapType);
7443 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7444 mapData.HasAttachPtr.push_back(false);
7445 // Technically it's possible for a non-descriptor mapping to have
7446 // both has-device-addr and ALWAYS, so lookup the mapper in case it
7447 // exists.
7448 if (mapOp.getMapperId()) {
7449 mapData.Mappers.push_back(
7451 mapOp, mapOp.getMapperIdAttr()));
7452 } else {
7453 mapData.Mappers.push_back(nullptr);
7454 }
7455 } else {
7456 // For is_device_ptr we need the map type to propagate so the runtime
7457 // can materialize the device-side copy of the pointer container.
7458 mapData.Types.push_back(
7459 isDevicePtr ? mapType
7460 : llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL);
7461 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7462 mapData.HasAttachPtr.push_back(false);
7463 mapData.Mappers.push_back(nullptr);
7464 }
7465 mapData.Names.push_back(LLVM::createMappingInformation(
7466 mapOp.getLoc(), *moduleTranslation.getOpenMPBuilder()));
7467 mapData.DevicePointers.push_back(
7468 isDevicePtr ? llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer
7469 : llvm::OpenMPIRBuilder::DeviceInfoTy::Address);
7470 mapData.IsAMapping.push_back(false);
7471 mapData.IsAMember.push_back(checkIsAMember(hasDevAddrOperands, mapOp));
7472 }
7473}
7474
7475static int getMapDataMemberIdx(MapInfoData &mapData, omp::MapInfoOp memberOp) {
7476 auto *res = llvm::find(mapData.MapClause, memberOp);
7477 assert(res != mapData.MapClause.end() &&
7478 "MapInfoOp for member not found in MapData, cannot return index");
7479 return std::distance(mapData.MapClause.begin(), res);
7480}
7481
7483 omp::MapInfoOp mapInfo, bool first = true) {
7484 ArrayAttr indexAttr = mapInfo.getMembersIndexAttr();
7485 llvm::SmallVector<size_t> occludedChildren;
7486 llvm::sort(
7487 indices.begin(), indices.end(), [&](const size_t a, const size_t b) {
7488 // Bail early if we are asked to look at the same index. If we do not
7489 // bail early, we can end up mistakenly adding indices to
7490 // occludedChildren. This can occur with some types of libc++ hardening.
7491 if (a == b)
7492 return false;
7493
7494 auto memberIndicesA = cast<ArrayAttr>(indexAttr[a]);
7495 auto memberIndicesB = cast<ArrayAttr>(indexAttr[b]);
7496
7497 for (auto it : llvm::zip(memberIndicesA, memberIndicesB)) {
7498 int64_t aIndex = mlir::cast<IntegerAttr>(std::get<0>(it)).getInt();
7499 int64_t bIndex = mlir::cast<IntegerAttr>(std::get<1>(it)).getInt();
7500
7501 if (aIndex == bIndex)
7502 continue;
7503
7504 if (aIndex < bIndex)
7505 return first;
7506
7507 if (aIndex > bIndex)
7508 return !first;
7509 }
7510
7511 // Iterated up until the end of the smallest member and
7512 // they were found to be equal up to that point, so select
7513 // the member with the lowest index count, so the "parent"
7514 bool memberAParent = memberIndicesA.size() < memberIndicesB.size();
7515 if (memberAParent)
7516 occludedChildren.push_back(b);
7517 else
7518 occludedChildren.push_back(a);
7519 return memberAParent;
7520 });
7521
7522 for (auto v : occludedChildren)
7523 indices.erase(std::remove(indices.begin(), indices.end(), v),
7524 indices.end());
7525}
7526
7527static omp::MapInfoOp getFirstOrLastMappedMemberPtr(omp::MapInfoOp mapInfo,
7528 bool first) {
7529 ArrayAttr indexAttr = mapInfo.getMembersIndexAttr();
7530 // Only 1 member has been mapped, we can return it.
7531 if (indexAttr.size() == 1)
7532 return cast<omp::MapInfoOp>(mapInfo.getMembers()[0].getDefiningOp());
7533 llvm::SmallVector<size_t> indices(indexAttr.size());
7534 std::iota(indices.begin(), indices.end(), 0);
7535 sortMapIndices(indices, mapInfo, first);
7536 return llvm::cast<omp::MapInfoOp>(
7537 mapInfo.getMembers()[indices.front()].getDefiningOp());
7538}
7539
7540/// This function calculates the array/pointer offset for map data provided
7541/// with bounds operations, e.g. when provided something like the following:
7542///
7543/// Fortran
7544/// map(tofrom: array(2:5, 3:2))
7545///
7546/// We must calculate the initial pointer offset to pass across, this function
7547/// performs this using bounds.
7548///
7549/// TODO/WARNING: This only supports Fortran's column major indexing currently
7550/// as is noted in the note below and comments in the function, we must extend
7551/// this function when we add a C++ frontend.
7552/// NOTE: which while specified in row-major order it currently needs to be
7553/// flipped for Fortran's column order array allocation and access (as
7554/// opposed to C++'s row-major, hence the backwards processing where order is
7555/// important). This is likely important to keep in mind for the future when
7556/// we incorporate a C++ frontend, both frontends will need to agree on the
7557/// ordering of generated bounds operations (one may have to flip them) to
7558/// make the below lowering frontend agnostic. The offload size
7559/// calcualtion may also have to be adjusted for C++.
7560static std::vector<llvm::Value *>
7562 llvm::IRBuilderBase &builder, bool isArrayTy,
7563 OperandRange bounds) {
7564 std::vector<llvm::Value *> idx;
7565 // There's no bounds to calculate an offset from, we can safely
7566 // ignore and return no indices.
7567 if (bounds.empty())
7568 return idx;
7569
7570 // If we have an array type, then we have its type so can treat it as a
7571 // normal GEP instruction where the bounds operations are simply indexes
7572 // into the array. We currently do reverse order of the bounds, which
7573 // I believe leans more towards Fortran's column-major in memory.
7574 if (isArrayTy) {
7575 idx.push_back(builder.getInt64(0));
7576 for (int i = bounds.size() - 1; i >= 0; --i) {
7577 if (auto boundOp = dyn_cast_if_present<omp::MapBoundsOp>(
7578 bounds[i].getDefiningOp())) {
7579 idx.push_back(moduleTranslation.lookupValue(boundOp.getLowerBound()));
7580 }
7581 }
7582 } else {
7583 // If we do not have an array type, but we have bounds, then we're dealing
7584 // with a pointer that's being treated like an array and we have the
7585 // underlying type e.g. an i32, or f64 etc, e.g. a fortran descriptor base
7586 // address (pointer pointing to the actual data) so we must caclulate the
7587 // offset using a single index which the following loop attempts to
7588 // compute using the standard column-major algorithm e.g for a 3D array:
7589 //
7590 // ((((c_idx * b_len) + b_idx) * a_len) + a_idx)
7591 //
7592 // It is of note that it's doing column-major rather than row-major at the
7593 // moment, but having a way for the frontend to indicate which major format
7594 // to use or standardizing/canonicalizing the order of the bounds to compute
7595 // the offset may be useful in the future when there's other frontends with
7596 // different formats.
7597 for (int i = bounds.size() - 1; i >= 0; --i) {
7598 if (auto boundOp = dyn_cast_if_present<omp::MapBoundsOp>(
7599 bounds[i].getDefiningOp())) {
7600 if (i == ((int)bounds.size() - 1))
7601 idx.emplace_back(
7602 moduleTranslation.lookupValue(boundOp.getLowerBound()));
7603 else
7604 idx.back() = builder.CreateAdd(
7605 builder.CreateMul(idx.back(), moduleTranslation.lookupValue(
7606 boundOp.getExtent())),
7607 moduleTranslation.lookupValue(boundOp.getLowerBound()));
7608 }
7609 }
7610 }
7611
7612 return idx;
7613}
7614
7616 llvm::transform(values, std::back_inserter(ints), [](Attribute value) {
7617 return cast<IntegerAttr>(value).getInt();
7618 });
7619}
7620
7621// Gathers members that are overlapping in the parent, excluding members that
7622// themselves overlap, keeping the top-most (closest to parents level) map.
7623static void
7625 omp::MapInfoOp parentOp) {
7626 // No members mapped, no overlaps.
7627 if (parentOp.getMembers().empty())
7628 return;
7629
7630 // Single member, we can insert and return early.
7631 if (parentOp.getMembers().size() == 1) {
7632 overlapMapDataIdxs.push_back(0);
7633 return;
7634 }
7635
7636 ArrayAttr indexAttr = parentOp.getMembersIndexAttr();
7637 size_t numMembers = indexAttr.size();
7638
7639 // Pre-convert all member indices to integer arrays for efficient comparison.
7640 llvm::SmallVector<llvm::SmallVector<int64_t>> memberIndices(numMembers);
7641 for (auto [i, indicesAttr] : llvm::enumerate(indexAttr))
7642 getAsIntegers(cast<ArrayAttr>(indicesAttr), memberIndices[i]);
7643
7644 // For each member, check if it's superseded by another (shorter prefix)
7645 // member. If member j's indices are a prefix of member i's indices, then
7646 // i is a child of j and should be skipped. e.g. if member [0] is mapped,
7647 // we skip members [0,1], [0,2], etc.
7648 llvm::SmallDenseSet<size_t> skipIndices;
7649 for (size_t i = 0; i < numMembers; ++i) {
7650 const auto &iIndices = memberIndices[i];
7651 for (size_t j = 0; j < numMembers; ++j) {
7652 if (i == j)
7653 continue;
7654 const auto &jIndices = memberIndices[j];
7655 // If j's indices are a strict prefix of i's indices, skip i
7656 if (jIndices.size() < iIndices.size() &&
7657 std::equal(jIndices.begin(), jIndices.end(), iIndices.begin())) {
7658 skipIndices.insert(i);
7659 break; // No need to check other potential parents
7660 }
7661 }
7662 }
7663
7664 // Collect indices of members that are not superseded by a parent.
7665 for (size_t i = 0; i < numMembers; ++i)
7666 if (!skipIndices.contains(i))
7667 overlapMapDataIdxs.push_back(i);
7668}
7669
7670/// This function handles the insertion of a single item of map data from
7671/// MapInfoData into the OMPIRBuilder's MapInfo list. Utilising this function
7672/// means the map being inserted can be treated as a non-parent map entity,
7673/// if the memberOfFlag is set then the map being inserted is treated as
7674/// a member map of a larger entity. The insertion into the MapInfo list of
7675/// the OMPIRBuilder can vary based on a number of factors, such as if it's
7676/// a ref_ptr or ref_ptee map, if it's a member of a record, what construct
7677/// the map belongs to and the various map type bit flags that are set for
7678/// the map.
7679static void
7680processIndividualMap(llvm::IRBuilderBase &builder,
7681 llvm::OpenMPIRBuilder &ompBuilder, MapInfoData &mapData,
7682 size_t mapDataIdx, MapInfosTy &combinedInfo,
7683 TargetDirectiveEnumTy targetDirective,
7684 llvm::omp::OpenMPOffloadMappingFlags memberOfFlag =
7685 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE,
7686 bool isTargetParam = true, int mapDataParentIdx = -1) {
7687 auto mapFlag = mapData.Types[mapDataIdx];
7688 auto mapInfoOp = llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIdx]);
7689
7690 bool isPtrTy = checkIfPointerMap(mapInfoOp);
7691 bool isAttachMap = ((convertClauseMapFlags(mapInfoOp.getMapType()) &
7692 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) ==
7693 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH);
7694
7695 // Declare target variables are not passed to the kernel, and for the moment
7696 // attach maps are not passed to the kernel. However, it is possible to create
7697 // attach maps that transfer data and thus can be kernel arguments, but our
7698 // existing frontend does not do this.
7699 if (isTargetParam &&
7700 (targetDirective == TargetDirectiveEnumTy::Target &&
7701 !mapData.IsDeclareTarget[mapDataIdx]) &&
7702 !isAttachMap)
7703 mapFlag |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TARGET_PARAM;
7704
7705 if (mapInfoOp.getMapCaptureType() == omp::VariableCaptureKind::ByCopy &&
7706 !isPtrTy)
7707 mapFlag |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL;
7708
7709 // If we have a pointer and it's part of a MEMBER_OF mapping we do not apply
7710 // MEMBER_OF, as the runtime currently has a work-around that utilises
7711 // MEMBER_OF to prevent reference updating in certain scenarios instead of
7712 // target_param. However, this causes a noticeable issue in cases where we
7713 // map some data (Fortran descriptor primarily at the moment), alter it on
7714 // the host, and then expect it to not be updated in a subsequent implicit map
7715 // (such as an implicit map on a target).
7716 if (memberOfFlag != llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE) {
7717 if (!isPtrTy && !isAttachMap)
7718 ompBuilder.setCorrectMemberOfFlag(mapFlag, memberOfFlag);
7719
7720 // The return parameter should be the over-riding parent in cases where we
7721 // have a return parameter that is echoed to all members, the main case of
7722 // this currently is with fortran descriptors. It may need more finessing
7723 // for C/C++ in the future or descriptors that are members of derived
7724 // types.
7725 mapFlag &= ~llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
7726 }
7727
7728 // We apply MAP_PTR_AND_OBJ when within a declare mapper object as it enforces
7729 // MEMBER_OF mappings on maps that are passed the initial nesting depth, which
7730 // includes pointed to data and attach members, both of which are technically
7731 // not part of the main object. This has the side effect of causing early
7732 // map-backs in certain cases where an implicit declare mapper has been
7733 // emitted for a target region. Applying MAP_PTR_AND_OBJ in these situations
7734 // circumvents this.
7735 if (isPtrTy && !isAttachMap && mapData.IsDeclareTarget[mapDataIdx])
7736 mapFlag |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_PTR_AND_OBJ;
7737
7738 // if we're provided a mapDataParentIdx, then the data being mapped is
7739 // part of a larger object (in a parent <-> member mapping) and in this
7740 // case our BasePointer should be the parent. Except in the edge case
7741 // where we are mapping pointee data, where we try staying close to
7742 // what Clang currently does and utilise the regular base pointer of the
7743 // data.
7744 bool isRefPtee =
7745 !bitEnumContainsAll(mapInfoOp.getMapType(),
7746 omp::ClauseMapFlags::ref_ptr) &&
7747 bitEnumContainsAll(mapInfoOp.getMapType(), omp::ClauseMapFlags::ref_ptee);
7748 bool isRefPtrPtee = bitEnumContainsAll(mapInfoOp.getMapType(),
7749 omp::ClauseMapFlags::ref_ptr |
7750 omp::ClauseMapFlags::ref_ptee);
7751
7752 if (!mapInfoOp->getParentOfType<omp::DeclareMapperOp>() &&
7753 mapDataParentIdx >= 0 && !(isRefPtee || (isRefPtrPtee && isPtrTy))) {
7754 combinedInfo.BasePointers.emplace_back(
7755 mapData.BasePointers[mapDataParentIdx]);
7756 } else {
7757 combinedInfo.BasePointers.emplace_back(mapData.BasePointers[mapDataIdx]);
7758 }
7759
7760 combinedInfo.Pointers.emplace_back(mapData.Pointers[mapDataIdx]);
7761 combinedInfo.DevicePointers.emplace_back(
7762 memberOfFlag != llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE
7763 ? llvm::OpenMPIRBuilder::DeviceInfoTy::None
7764 : mapData.DevicePointers[mapDataIdx]);
7765 combinedInfo.Mappers.emplace_back(mapData.Mappers[mapDataIdx]);
7766 combinedInfo.Names.emplace_back(mapData.Names[mapDataIdx]);
7767 combinedInfo.Types.emplace_back(mapFlag);
7768 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7769 combinedInfo.HasAttachPtr.emplace_back(false);
7770 combinedInfo.Sizes.emplace_back(
7771 isPtrTy ? builder.CreateSelect(
7772 builder.CreateIsNull(mapData.Pointers[mapDataIdx]),
7773 builder.getInt64(0), mapData.Sizes[mapDataIdx])
7774 : mapData.Sizes[mapDataIdx]);
7775}
7776
7777// This creates two insertions into the MapInfosTy data structure for the
7778// "parent" of a set of members, (usually a container e.g.
7779// class/structure/derived type) when subsequent members have also been
7780// explicitly mapped on the same map clause. Certain types, such as Fortran
7781// descriptors are mapped like this as well, however, the members are
7782// implicit as far as a user is concerned, but we must explicitly map them
7783// internally.
7784//
7785// This function also returns the memberOfFlag for this particular parent,
7786// which is utilised in subsequent member mappings (by modifying there map type
7787// with it) to indicate that a member is part of this parent and should be
7788// treated by the runtime as such. Important to achieve the correct mapping.
7789//
7790// This function borrows a lot from Clang's emitCombinedEntry function
7791// inside of CGOpenMPRuntime.cpp
7793 LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder,
7794 llvm::OpenMPIRBuilder &ompBuilder, DataLayout &dl, MapInfosTy &combinedInfo,
7795 MapInfoData &mapData, uint64_t mapDataIndex,
7796 llvm::omp::OpenMPOffloadMappingFlags memberOfFlag,
7797 TargetDirectiveEnumTy targetDirective) {
7798 using MapFlags = llvm::omp::OpenMPOffloadMappingFlags;
7799 assert(!ompBuilder.Config.isTargetDevice() &&
7800 "function only supported for host device codegen");
7801 auto parentClause =
7802 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
7803 auto *parentMapper = mapData.Mappers[mapDataIndex];
7804
7805 // Map the first segment of the parent. If a user-defined mapper is attached,
7806 // include the parent's to/from-style bits (and common modifiers) in this
7807 // base entry so the mapper receives correct copy semantics via its 'type'
7808 // parameter. Also keep TARGET_PARAM when required for kernel arguments.
7809 MapFlags baseFlag = (targetDirective == TargetDirectiveEnumTy::Target &&
7810 !mapData.IsDeclareTarget[mapDataIndex])
7811 ? MapFlags::OMP_MAP_TARGET_PARAM
7812 : MapFlags::OMP_MAP_NONE;
7813
7814 if (parentMapper) {
7815 // Preserve relevant map-type bits from the parent clause. These include
7816 // the copy direction (TO/FROM), as well as commonly used modifiers that
7817 // should be visible to the mapper for correct behaviour.
7818 MapFlags parentFlags = mapData.Types[mapDataIndex];
7819 MapFlags preserve = MapFlags::OMP_MAP_TO | MapFlags::OMP_MAP_FROM |
7820 MapFlags::OMP_MAP_ALWAYS | MapFlags::OMP_MAP_CLOSE |
7821 MapFlags::OMP_MAP_PRESENT |
7822 MapFlags::OMP_MAP_OMPX_HOLD |
7823 MapFlags::OMP_MAP_IMPLICIT;
7824 baseFlag |= (parentFlags & preserve);
7825 } else {
7826 MapFlags parentFlags = mapData.Types[mapDataIndex];
7827 MapFlags preserve =
7828 MapFlags::OMP_MAP_PRESENT | MapFlags::OMP_MAP_RETURN_PARAM;
7829 baseFlag |= (parentFlags & preserve);
7830 }
7831
7832 combinedInfo.Types.emplace_back(baseFlag);
7833 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7834 combinedInfo.HasAttachPtr.emplace_back(false);
7835 combinedInfo.DevicePointers.emplace_back(
7836 mapData.DevicePointers[mapDataIndex]);
7837 // Only attach the mapper to the base entry when we are mapping the whole
7838 // parent. Combined/segment entries must not carry a mapper; otherwise the
7839 // mapper can be invoked with a partial size, which is undefined behaviour.
7840 combinedInfo.Mappers.emplace_back(
7841 parentMapper && !parentClause.getPartialMap() ? parentMapper : nullptr);
7842 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
7843 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder));
7844 combinedInfo.BasePointers.emplace_back(mapData.BasePointers[mapDataIndex]);
7845
7846 // Calculate size of the parent object being mapped based on the
7847 // addresses at runtime, highAddr - lowAddr = size. This of course
7848 // doesn't factor in allocated data like pointers, hence the further
7849 // processing of members specified by users, or in the case of
7850 // Fortran pointers and allocatables, the mapping of the pointed to
7851 // data by the descriptor (which itself, is a structure containing
7852 // runtime information on the dynamically allocated data).
7853 llvm::Value *lowAddr, *highAddr;
7854 if (!parentClause.getPartialMap()) {
7855 lowAddr = builder.CreatePointerCast(mapData.Pointers[mapDataIndex],
7856 builder.getPtrTy());
7857 highAddr = builder.CreatePointerCast(
7858 builder.CreateConstGEP1_32(mapData.BaseType[mapDataIndex],
7859 mapData.Pointers[mapDataIndex], 1),
7860 builder.getPtrTy());
7861 combinedInfo.Pointers.emplace_back(mapData.Pointers[mapDataIndex]);
7862 } else {
7863 auto mapOp = dyn_cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
7864 int firstMemberIdx = getMapDataMemberIdx(
7865 mapData, getFirstOrLastMappedMemberPtr(mapOp, true));
7866 lowAddr = builder.CreatePointerCast(mapData.BasePointers[firstMemberIdx],
7867 builder.getPtrTy());
7868
7869 int lastMemberIdx = getMapDataMemberIdx(
7870 mapData, getFirstOrLastMappedMemberPtr(mapOp, false));
7871 auto lastMemberMapInfo =
7872 cast<omp::MapInfoOp>(mapData.MapClause[lastMemberIdx]);
7873
7874 // NOTE: Currently, for RefPtee the BaseType is set to the varPtrPtr field,
7875 // which is the pointer datas type and not the member within the structure
7876 // that it's part of, so we have to make sure we use the member type in this
7877 // case when calculating the parents size offsets.
7878 // TODO: May be good to extend MapInfoData to support tracking of both
7879 // VarPtr/VarPtrPtr BaseType's to better distinguish what's being used more
7880 // consistently.
7881 bool isRefPteeMap = bitEnumContainsAll(lastMemberMapInfo.getMapType(),
7882 omp::ClauseMapFlags::ref_ptee) &&
7883 !bitEnumContainsAll(lastMemberMapInfo.getMapType(),
7884 omp::ClauseMapFlags::ref_ptr);
7885 llvm::Type *castType = mapData.BaseType[lastMemberIdx];
7886 if (isRefPteeMap)
7887 castType =
7888 moduleTranslation.convertType(lastMemberMapInfo.getVarPtrType());
7889 highAddr = builder.CreatePointerCast(
7890 builder.CreateGEP(castType, mapData.BasePointers[lastMemberIdx],
7891 builder.getInt64(1)),
7892 builder.getPtrTy());
7893 combinedInfo.Pointers.emplace_back(mapData.BasePointers[firstMemberIdx]);
7894 }
7895
7896 llvm::Value *size = builder.CreateIntCast(
7897 builder.CreatePtrDiff(builder.getInt8Ty(), highAddr, lowAddr),
7898 builder.getInt64Ty(),
7899 /*isSigned=*/false);
7900 combinedInfo.Sizes.push_back(size);
7901
7902 // This creates the initial MEMBER_OF mapping that consists of
7903 // the parent/top level container (same as above effectively, except
7904 // with a fixed initial compile time size and separate maptype which
7905 // indicates the true mape type (tofrom etc.). This parent mapping is
7906 // only relevant if the structure in its totality is being mapped,
7907 // otherwise the above suffices.
7908 if (!parentClause.getPartialMap()) {
7909 // TODO: This will need to be expanded to include the whole host of logic
7910 // for the map flags that Clang currently supports (e.g. it should do some
7911 // further case specific flag modifications). For the moment, it handles
7912 // what we support as expected.
7913 MapFlags mapFlag = mapData.Types[mapDataIndex];
7914 bool hasMapClose = (MapFlags(mapFlag) & MapFlags::OMP_MAP_CLOSE) ==
7915 MapFlags::OMP_MAP_CLOSE;
7916 ompBuilder.setCorrectMemberOfFlag(mapFlag, memberOfFlag);
7917
7918 llvm::SmallVector<size_t> overlapIdxs;
7919 // Find all of the members that "overlap", i.e. occlude other members that
7920 // were mapped alongside the parent, e.g. member [0], occludes [0,1] and
7921 // [0,2], but not [1,0].
7922 getOverlappedMembers(overlapIdxs, parentClause);
7923
7924 // When we only have one overlap we skip the case that tries to segment the
7925 // mapping as best it can without creating holes, as the calculation is more
7926 // likely to have more overhead than anything we gain from mapping a smaller
7927 // chunk of data. This can be seen in cases where we are mapping Fortran
7928 // descriptors which are a special case of record type mapping.
7929 //
7930 // The cases for close and update are unique edge cases where the segmenting
7931 // does not play well with the runtime currently.
7932 if (targetDirective == TargetDirectiveEnumTy::TargetUpdate || hasMapClose ||
7933 overlapIdxs.size() == 1) {
7934 combinedInfo.Types.emplace_back(mapFlag);
7935 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7936 combinedInfo.HasAttachPtr.emplace_back(false);
7937 combinedInfo.DevicePointers.emplace_back(
7938 mapData.DevicePointers[mapDataIndex]);
7939 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
7940 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder));
7941 combinedInfo.BasePointers.emplace_back(
7942 mapData.BasePointers[mapDataIndex]);
7943 combinedInfo.Pointers.emplace_back(mapData.Pointers[mapDataIndex]);
7944 combinedInfo.Sizes.emplace_back(mapData.Sizes[mapDataIndex]);
7945 combinedInfo.Mappers.emplace_back(nullptr);
7946 } else {
7947 // We need to make sure the overlapped members are sorted in order of
7948 // lowest address to highest address.
7949 sortMapIndices(overlapIdxs, parentClause);
7950
7951 lowAddr = builder.CreatePointerCast(mapData.Pointers[mapDataIndex],
7952 builder.getPtrTy());
7953 highAddr = builder.CreatePointerCast(
7954 builder.CreateConstGEP1_32(mapData.BaseType[mapDataIndex],
7955 mapData.Pointers[mapDataIndex], 1),
7956 builder.getPtrTy());
7957
7958 // Currently, the return parameter should be the over-riding parent in
7959 // cases where we have a return parameter that is echoed to all members,
7960 // the main case of this currently is with fortran descriptors. It may
7961 // need more finessing for C/C++ in the future or descriptors that are
7962 // members of derived types.
7963 mapFlag &= ~llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
7964
7965 // TODO: We may want to skip arrays/array sections in this as Clang does.
7966 // It appears to be an optimisation rather than a necessity though,
7967 // but this requires further investigation. However, we would have to make
7968 // sure to not exclude maps with bounds that ARE pointers, as these are
7969 // processed as separate components, i.e. pointer + data.
7970 for (auto v : overlapIdxs) {
7971 auto mapDataOverlapIdx = getMapDataMemberIdx(
7972 mapData,
7973 cast<omp::MapInfoOp>(parentClause.getMembers()[v].getDefiningOp()));
7974 auto isPtrMap = checkIfPointerMap(
7975 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataOverlapIdx]));
7976 combinedInfo.Types.emplace_back(mapFlag);
7977 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7978 combinedInfo.HasAttachPtr.emplace_back(false);
7979 combinedInfo.DevicePointers.emplace_back(
7980 llvm::OpenMPIRBuilder::DeviceInfoTy::None);
7981 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
7982 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder));
7983 combinedInfo.BasePointers.emplace_back(
7984 mapData.BasePointers[mapDataIndex]);
7985 combinedInfo.Mappers.emplace_back(nullptr);
7986 combinedInfo.Pointers.emplace_back(lowAddr);
7987 auto sizeCalc = builder.CreateIntCast(
7988 builder.CreatePtrDiff(builder.getInt8Ty(),
7989 mapData.OriginalValue[mapDataOverlapIdx],
7990 lowAddr),
7991 builder.getInt64Ty(), /*isSigned=*/true);
7992 // In certain cases, we'll generate a size of 0 if we're not careful
7993 // (e.g. if lowAddr happens to be the first member), which isn't
7994 // correct, even if the runtimes is sometimes fine with it so, in these
7995 // scenarios we select the types size instead.
7996 auto sizeSel = builder.CreateSelect(
7997 builder.CreateICmpNE(builder.getInt64(0), sizeCalc), sizeCalc,
7998 isPtrMap ? llvm::ConstantExpr::getSizeOf(builder.getPtrTy())
7999 : mapData.Sizes[mapDataOverlapIdx]);
8000 combinedInfo.Sizes.emplace_back(sizeSel);
8001 lowAddr = builder.CreateConstGEP1_32(
8002 isPtrMap ? builder.getPtrTy() : mapData.BaseType[mapDataOverlapIdx],
8003 mapData.BasePointers[mapDataOverlapIdx], 1);
8004 }
8005
8006 combinedInfo.Types.emplace_back(mapFlag);
8007 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
8008 combinedInfo.HasAttachPtr.emplace_back(false);
8009 combinedInfo.DevicePointers.emplace_back(
8010 llvm::OpenMPIRBuilder::DeviceInfoTy::None);
8011 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
8012 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder));
8013 combinedInfo.BasePointers.emplace_back(
8014 mapData.BasePointers[mapDataIndex]);
8015 combinedInfo.Mappers.emplace_back(nullptr);
8016 combinedInfo.Pointers.emplace_back(lowAddr);
8017 combinedInfo.Sizes.emplace_back(builder.CreateIntCast(
8018 builder.CreatePtrDiff(builder.getInt8Ty(), highAddr, lowAddr),
8019 builder.getInt64Ty(), true));
8020 }
8021 }
8022}
8023
8025 llvm::IRBuilderBase &builder,
8026 llvm::OpenMPIRBuilder &ompBuilder,
8027 DataLayout &dl, MapInfosTy &combinedInfo,
8028 MapInfoData &mapData, uint64_t mapDataIndex,
8029 TargetDirectiveEnumTy targetDirective) {
8030 assert(!ompBuilder.Config.isTargetDevice() &&
8031 "function only supported for host device codegen");
8032
8033 auto parentClause =
8034 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
8035
8036 // If we have a partial map (no parent referenced in the map clauses of the
8037 // directive, only members) and only a single member, we do not need to bind
8038 // the map of the member to the parent, we can pass the member separately.
8039 if (parentClause.getMembers().size() == 1 && parentClause.getPartialMap()) {
8040 auto memberClause = llvm::cast<omp::MapInfoOp>(
8041 parentClause.getMembers()[0].getDefiningOp());
8042 int memberDataIdx = getMapDataMemberIdx(mapData, memberClause);
8043 // Note: Clang treats arrays with explicit bounds that fall into this
8044 // category as a parent with map case, however, it seems this isn't a
8045 // requirement, and processing them as an individual map is fine. So,
8046 // we will handle them as individual maps for the moment, as it's
8047 // difficult for us to check this as we always require bounds to be
8048 // specified currently and it's also marginally more optimal (single
8049 // map rather than two). The difference may come from the fact that
8050 // Clang maps array without bounds as pointers (which we do not
8051 // currently do), whereas we treat them as arrays in all cases
8052 // currently.
8054 builder, ompBuilder, mapData, memberDataIdx, combinedInfo,
8055 targetDirective,
8056 /*MemberOfFlag=*/llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE,
8057 /*isTargetParam=*/true, mapDataIndex);
8058 return;
8059 }
8060
8061 auto collectMapInfoIdxs =
8062 [&](llvm::SmallVectorImpl<int64_t> &mapsAndInfoIdx) {
8063 auto parentClause =
8064 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
8065 mapsAndInfoIdx.push_back(getMapDataMemberIdx(mapData, parentClause));
8066 for (auto member : parentClause.getMembers())
8067 mapsAndInfoIdx.push_back(getMapDataMemberIdx(
8068 mapData, llvm::cast<omp::MapInfoOp>(member.getDefiningOp())));
8069 };
8070
8071 llvm::SmallVector<int64_t> mapInfoIdx;
8072 collectMapInfoIdxs(mapInfoIdx);
8073
8074 llvm::omp::OpenMPOffloadMappingFlags memberOfFlag =
8075 ompBuilder.getMemberOfFlag(combinedInfo.Types.size());
8076 for (size_t i = 0; i < mapInfoIdx.size(); i++) {
8077 // Index == 0 is the parent map and if it gets here it's an unattachable
8078 // type and should have OMP_MAP_TARGET_PARAM applied and no MEMBER_OF flag.
8079 if (i == 0) {
8080 mapParentWithMembers(moduleTranslation, builder, ompBuilder, dl,
8081 combinedInfo, mapData, mapInfoIdx[i], memberOfFlag,
8082 targetDirective);
8083 } else {
8084 processIndividualMap(builder, ompBuilder, mapData, mapInfoIdx[i],
8085 combinedInfo, targetDirective, memberOfFlag,
8086 /*isTargetParam=*/false, mapDataIndex);
8087 }
8088 }
8089}
8090
8091// This is a variation on Clang's GenerateOpenMPCapturedVars, which
8092// generates different operation (e.g. load/store) combinations for
8093// arguments to the kernel, based on map capture kinds which are then
8094// utilised in the combinedInfo in place of the original Map value.
8095static void
8096createAlteredByCaptureMap(MapInfoData &mapData,
8097 LLVM::ModuleTranslation &moduleTranslation,
8098 llvm::IRBuilderBase &builder) {
8099 assert(!moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
8100 "function only supported for host device codegen");
8101 for (size_t i = 0; i < mapData.MapClause.size(); ++i) {
8102 auto mapOp = cast<omp::MapInfoOp>(mapData.MapClause[i]);
8103 bool isAttachMap =
8104 ((convertClauseMapFlags(mapOp.getMapType()) &
8105 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) ==
8106 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH);
8107
8108 // If it's declare target, skip it, it's handled separately. However, if
8109 // it's declare target, and an attach map, we want to calculate the exact
8110 // address offset so that we attach correctly.
8111 if (!mapData.IsDeclareTarget[i] ||
8112 (mapData.IsDeclareTarget[i] && isAttachMap)) {
8113 omp::VariableCaptureKind captureKind = mapOp.getMapCaptureType();
8114 bool isPtrTy = checkIfPointerMap(mapOp);
8115
8116 // Currently handles array sectioning lowerbound case, but more
8117 // logic may be required in the future. Clang invokes EmitLValue,
8118 // which has specialised logic for special Clang types such as user
8119 // defines, so it is possible we will have to extend this for
8120 // structures or other complex types. As the general idea is that this
8121 // function mimics some of the logic from Clang that we require for
8122 // kernel argument passing from host -> device.
8123 switch (captureKind) {
8124 case omp::VariableCaptureKind::ByRef: {
8125 llvm::Value *newV = mapData.Pointers[i];
8126 std::vector<llvm::Value *> offsetIdx = calculateBoundsOffset(
8127 moduleTranslation, builder, mapData.BaseType[i]->isArrayTy(),
8128 mapOp.getBounds());
8129 if (isPtrTy)
8130 newV = builder.CreateLoad(builder.getPtrTy(), newV);
8131
8132 if (!offsetIdx.empty())
8133 newV = builder.CreateInBoundsGEP(mapData.BaseType[i], newV, offsetIdx,
8134 "array_offset");
8135 mapData.Pointers[i] = newV;
8136 } break;
8137 case omp::VariableCaptureKind::ByCopy: {
8138 llvm::Type *type = mapData.BaseType[i];
8139 llvm::Value *newV;
8140 if (mapData.Pointers[i]->getType()->isPointerTy())
8141 newV = builder.CreateLoad(type, mapData.Pointers[i]);
8142 else
8143 newV = mapData.Pointers[i];
8144
8145 if (!isPtrTy) {
8146 auto curInsert = builder.saveIP();
8147 llvm::DebugLoc DbgLoc = builder.getCurrentDebugLocation();
8148 builder.restoreIP(findAllocInsertPoints(builder, moduleTranslation));
8149 auto *memTempAlloc =
8150 builder.CreateAlloca(builder.getPtrTy(), nullptr, ".casted");
8151 builder.SetCurrentDebugLocation(DbgLoc);
8152 builder.restoreIP(curInsert);
8153
8154 builder.CreateStore(newV, memTempAlloc);
8155 newV = builder.CreateLoad(builder.getPtrTy(), memTempAlloc);
8156 }
8157
8158 mapData.Pointers[i] = newV;
8159 mapData.BasePointers[i] = newV;
8160 } break;
8161 case omp::VariableCaptureKind::This:
8162 case omp::VariableCaptureKind::VLAType:
8163 mapData.MapClause[i]->emitOpError("Unhandled capture kind");
8164 break;
8165 }
8166 }
8167 }
8168}
8169
8170// Generate all map related information and fill the combinedInfo.
8171static void genMapInfos(llvm::IRBuilderBase &builder,
8172 LLVM::ModuleTranslation &moduleTranslation,
8173 DataLayout &dl, MapInfosTy &combinedInfo,
8174 MapInfoData &mapData,
8175 TargetDirectiveEnumTy targetDirective) {
8176 assert(!moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
8177 "function only supported for host device codegen");
8178 // We wish to modify some of the methods in which arguments are
8179 // passed based on their capture type by the target region, this can
8180 // involve generating new loads and stores, which changes the
8181 // MLIR value to LLVM value mapping, however, we only wish to do this
8182 // locally for the current function/target and also avoid altering
8183 // ModuleTranslation, so we remap the base pointer or pointer stored
8184 // in the map infos corresponding MapInfoData, which is later accessed
8185 // by genMapInfos and createTarget to help generate the kernel and
8186 // kernel arg structure. It primarily becomes relevant in cases like
8187 // bycopy, or byref range'd arrays. In the default case, we simply
8188 // pass thee pointer byref as both basePointer and pointer.
8189 createAlteredByCaptureMap(mapData, moduleTranslation, builder);
8190
8191 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8192
8193 // We operate under the assumption that all vectors that are
8194 // required in MapInfoData are of equal lengths (either filled with
8195 // default constructed data or appropiate information) so we can
8196 // utilise the size from any component of MapInfoData, if we can't
8197 // something is missing from the initial MapInfoData construction.
8198 for (size_t i = 0; i < mapData.MapClause.size(); ++i) {
8199 if (mapData.IsAMember[i])
8200 continue;
8201
8202 auto mapInfoOp = dyn_cast<omp::MapInfoOp>(mapData.MapClause[i]);
8203 if (!mapInfoOp.getMembers().empty()) {
8204 processMapWithMembersOf(moduleTranslation, builder, *ompBuilder, dl,
8205 combinedInfo, mapData, i, targetDirective);
8206 continue;
8207 }
8208
8209 processIndividualMap(builder, *ompBuilder, mapData, i, combinedInfo,
8210 targetDirective);
8211 }
8212}
8213
8214static llvm::Expected<llvm::Function *>
8215emitUserDefinedMapper(Operation *declMapperOp, llvm::IRBuilderBase &builder,
8216 LLVM::ModuleTranslation &moduleTranslation,
8217 llvm::StringRef mapperFuncName,
8218 TargetDirectiveEnumTy targetDirective);
8219
8220static llvm::Expected<llvm::Function *>
8221getOrCreateUserDefinedMapperFunc(Operation *op, llvm::IRBuilderBase &builder,
8222 LLVM::ModuleTranslation &moduleTranslation,
8223 TargetDirectiveEnumTy targetDirective) {
8224 assert(!moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
8225 "function only supported for host device codegen");
8226 auto declMapperOp = cast<omp::DeclareMapperOp>(op);
8227 std::string mapperFuncName =
8228 moduleTranslation.getOpenMPBuilder()->createPlatformSpecificName(
8229 {"omp_mapper", declMapperOp.getSymName()});
8230
8231 if (auto *lookupFunc = moduleTranslation.lookupFunction(mapperFuncName))
8232 return lookupFunc;
8233
8234 // Recursive types can cause re-entrant mapper emission. The mapper function
8235 // is created by OpenMPIRBuilder before the callbacks run, so it may already
8236 // exist in the LLVM module even though it is not yet registered in the
8237 // ModuleTranslation mapping table. Reuse and register it to break the
8238 // recursion.
8239 if (llvm::Function *existingFunc =
8240 moduleTranslation.getLLVMModule()->getFunction(mapperFuncName)) {
8241 moduleTranslation.mapFunction(mapperFuncName, existingFunc);
8242 return existingFunc;
8243 }
8244
8245 return emitUserDefinedMapper(declMapperOp, builder, moduleTranslation,
8246 mapperFuncName, targetDirective);
8247}
8248
8249static llvm::Expected<llvm::Function *>
8250emitUserDefinedMapper(Operation *op, llvm::IRBuilderBase &builder,
8251 LLVM::ModuleTranslation &moduleTranslation,
8252 llvm::StringRef mapperFuncName,
8253 TargetDirectiveEnumTy targetDirective) {
8254 assert(!moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
8255 "function only supported for host device codegen");
8256 auto declMapperOp = cast<omp::DeclareMapperOp>(op);
8257 auto declMapperInfoOp = declMapperOp.getDeclareMapperInfo();
8258 if (failed(checkImplementationStatus(*declMapperInfoOp)))
8259 return llvm::make_error<PreviouslyReportedError>();
8260
8261 DataLayout dl = DataLayout(declMapperOp->getParentOfType<ModuleOp>());
8262 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8263 llvm::Type *varType = moduleTranslation.convertType(declMapperOp.getType());
8264 SmallVector<Value> mapVars = declMapperInfoOp.getMapVars();
8265
8266 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
8267
8268 // Fill up the arrays with all the mapped variables.
8269 MapInfosTy combinedInfo;
8270 auto genMapInfoCB =
8271 [&](InsertPointTy codeGenIP, llvm::Value *ptrPHI,
8272 llvm::Value *unused2) -> llvm::OpenMPIRBuilder::MapInfosOrErrorTy {
8273 builder.restoreIP(codeGenIP);
8274 moduleTranslation.mapValue(declMapperOp.getSymVal(), ptrPHI);
8275 moduleTranslation.mapBlock(&declMapperOp.getRegion().front(),
8276 builder.GetInsertBlock());
8277 if (failed(moduleTranslation.convertBlock(declMapperOp.getRegion().front(),
8278 /*ignoreArguments=*/true,
8279 builder)))
8280 return llvm::make_error<PreviouslyReportedError>();
8281 MapInfoData mapData;
8282 collectMapDataFromMapOperands(mapData, mapVars, moduleTranslation, dl,
8283 builder);
8284 genMapInfos(builder, moduleTranslation, dl, combinedInfo, mapData,
8285 targetDirective);
8286
8287 // Drop the mapping that is no longer necessary so that the same region
8288 // can be processed multiple times.
8289 moduleTranslation.forgetMapping(declMapperOp.getRegion());
8290 return combinedInfo;
8291 };
8292
8293 auto customMapperCB = [&](unsigned i) -> llvm::Expected<llvm::Function *> {
8294 if (!combinedInfo.Mappers[i])
8295 return nullptr;
8296 return getOrCreateUserDefinedMapperFunc(combinedInfo.Mappers[i], builder,
8297 moduleTranslation, targetDirective);
8298 };
8299
8300 llvm::Expected<llvm::Function *> newFn = ompBuilder->emitUserDefinedMapper(
8301 genMapInfoCB, varType, mapperFuncName, customMapperCB,
8302 /*PreserveMemberOfFlags=*/true);
8303 if (!newFn)
8304 return newFn.takeError();
8305 if ([[maybe_unused]] llvm::Function *mappedFunc =
8306 moduleTranslation.lookupFunction(mapperFuncName)) {
8307 assert(mappedFunc == *newFn &&
8308 "mapper function mapping disagrees with emitted function");
8309 } else {
8310 moduleTranslation.mapFunction(mapperFuncName, *newFn);
8311 }
8312 return *newFn;
8313}
8314
8315static LogicalResult
8316convertOmpTargetData(Operation *op, llvm::IRBuilderBase &builder,
8317 LLVM::ModuleTranslation &moduleTranslation) {
8318 llvm::Value *ifCond = nullptr;
8319 llvm::Value *deviceID = builder.getInt64(llvm::omp::OMP_DEVICEID_UNDEF);
8320 SmallVector<Value> mapVars;
8321 SmallVector<Value> useDevicePtrVars;
8322 SmallVector<Value> useDeviceAddrVars;
8323 llvm::omp::RuntimeFunction RTLFn;
8324 DataLayout DL = DataLayout(op->getParentOfType<ModuleOp>());
8325 TargetDirectiveEnumTy targetDirective = getTargetDirectiveEnumTyFromOp(op);
8326
8327 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8328 llvm::OpenMPIRBuilder::TargetDataInfo info(
8329 /*RequiresDevicePointerInfo=*/true,
8330 /*SeparateBeginEndCalls=*/true);
8331 assert(!ompBuilder->Config.isTargetDevice() &&
8332 "target data/enter/exit/update are host ops");
8333 bool isOffloadEntry = !ompBuilder->Config.TargetTriples.empty();
8334
8335 auto getDeviceID = [&](mlir::Value dev) -> llvm::Value * {
8336 llvm::Value *v = moduleTranslation.lookupValue(dev);
8337 return builder.CreateIntCast(v, builder.getInt64Ty(), /*isSigned=*/true);
8338 };
8339
8340 LogicalResult result =
8342 .Case([&](omp::TargetDataOp dataOp) {
8343 if (failed(checkImplementationStatus(*dataOp)))
8344 return failure();
8345
8346 if (auto ifVar = dataOp.getIfExpr())
8347 ifCond = moduleTranslation.lookupValue(ifVar);
8348
8349 if (mlir::Value devId = dataOp.getDevice())
8350 deviceID = getDeviceID(devId);
8351
8352 mapVars = dataOp.getMapVars();
8353 useDevicePtrVars = dataOp.getUseDevicePtrVars();
8354 useDeviceAddrVars = dataOp.getUseDeviceAddrVars();
8355 return success();
8356 })
8357 .Case([&](omp::TargetEnterDataOp enterDataOp) -> LogicalResult {
8358 if (failed(checkImplementationStatus(*enterDataOp)))
8359 return failure();
8360
8361 if (auto ifVar = enterDataOp.getIfExpr())
8362 ifCond = moduleTranslation.lookupValue(ifVar);
8363
8364 if (mlir::Value devId = enterDataOp.getDevice())
8365 deviceID = getDeviceID(devId);
8366
8367 RTLFn =
8368 enterDataOp.getNowait()
8369 ? llvm::omp::OMPRTL___tgt_target_data_begin_nowait_mapper
8370 : llvm::omp::OMPRTL___tgt_target_data_begin_mapper;
8371 mapVars = enterDataOp.getMapVars();
8372 info.HasNoWait = enterDataOp.getNowait();
8373 return success();
8374 })
8375 .Case([&](omp::TargetExitDataOp exitDataOp) -> LogicalResult {
8376 if (failed(checkImplementationStatus(*exitDataOp)))
8377 return failure();
8378
8379 if (auto ifVar = exitDataOp.getIfExpr())
8380 ifCond = moduleTranslation.lookupValue(ifVar);
8381
8382 if (mlir::Value devId = exitDataOp.getDevice())
8383 deviceID = getDeviceID(devId);
8384
8385 RTLFn = exitDataOp.getNowait()
8386 ? llvm::omp::OMPRTL___tgt_target_data_end_nowait_mapper
8387 : llvm::omp::OMPRTL___tgt_target_data_end_mapper;
8388 mapVars = exitDataOp.getMapVars();
8389 info.HasNoWait = exitDataOp.getNowait();
8390 return success();
8391 })
8392 .Case([&](omp::TargetUpdateOp updateDataOp) -> LogicalResult {
8393 if (failed(checkImplementationStatus(*updateDataOp)))
8394 return failure();
8395
8396 if (auto ifVar = updateDataOp.getIfExpr())
8397 ifCond = moduleTranslation.lookupValue(ifVar);
8398
8399 if (mlir::Value devId = updateDataOp.getDevice())
8400 deviceID = getDeviceID(devId);
8401
8402 RTLFn =
8403 updateDataOp.getNowait()
8404 ? llvm::omp::OMPRTL___tgt_target_data_update_nowait_mapper
8405 : llvm::omp::OMPRTL___tgt_target_data_update_mapper;
8406 mapVars = updateDataOp.getMapVars();
8407 info.HasNoWait = updateDataOp.getNowait();
8408 return success();
8409 })
8410 .DefaultUnreachable("unexpected operation");
8411
8412 if (failed(result))
8413 return failure();
8414 // Pretend we have IF(false) if we're not doing offload.
8415 if (!isOffloadEntry)
8416 ifCond = builder.getFalse();
8417
8418 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
8419 MapInfoData mapData;
8420 collectMapDataFromMapOperands(mapData, mapVars, moduleTranslation, DL,
8421 builder, useDevicePtrVars, useDeviceAddrVars);
8422
8423 // Fill up the arrays with all the mapped variables.
8424 MapInfosTy combinedInfo;
8425 auto genMapInfoCB = [&](InsertPointTy codeGenIP) -> MapInfosTy & {
8426 builder.restoreIP(codeGenIP);
8427 genMapInfos(builder, moduleTranslation, DL, combinedInfo, mapData,
8428 targetDirective);
8429 return combinedInfo;
8430 };
8431
8432 // Define a lambda to apply mappings between use_device_addr and
8433 // use_device_ptr base pointers, and their associated block arguments.
8434 auto mapUseDevice =
8435 [&moduleTranslation](
8436 llvm::OpenMPIRBuilder::DeviceInfoTy type,
8438 llvm::SmallVectorImpl<Value> &useDeviceVars, MapInfoData &mapInfoData,
8439 llvm::function_ref<llvm::Value *(llvm::Value *)> mapper = nullptr) {
8440 for (auto [arg, useDevVar] :
8441 llvm::zip_equal(blockArgs, useDeviceVars)) {
8442
8443 auto getMapBasePtr = [](omp::MapInfoOp mapInfoOp) {
8444 return mapInfoOp.getVarPtrPtr() ? mapInfoOp.getVarPtrPtr()
8445 : mapInfoOp.getVarPtr();
8446 };
8447
8448 auto useDevMap = cast<omp::MapInfoOp>(useDevVar.getDefiningOp());
8449 for (auto [mapClause, devicePointer, basePointer] : llvm::zip_equal(
8450 mapInfoData.MapClause, mapInfoData.DevicePointers,
8451 mapInfoData.BasePointers)) {
8452 auto mapOp = cast<omp::MapInfoOp>(mapClause);
8453 if (getMapBasePtr(mapOp) != getMapBasePtr(useDevMap) ||
8454 devicePointer != type)
8455 continue;
8456
8457 if (llvm::Value *devPtrInfoMap =
8458 mapper ? mapper(basePointer) : basePointer) {
8459 moduleTranslation.mapValue(arg, devPtrInfoMap);
8460 break;
8461 }
8462 }
8463 }
8464 };
8465
8466 using BodyGenTy = llvm::OpenMPIRBuilder::BodyGenTy;
8467 auto bodyGenCB = [&](InsertPointTy codeGenIP, BodyGenTy bodyGenType)
8468 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
8469 // We must always restoreIP regardless of doing anything the caller
8470 // does not restore it, leading to incorrect (no) branch generation.
8471 builder.restoreIP(codeGenIP);
8472 assert(isa<omp::TargetDataOp>(op) &&
8473 "BodyGen requested for non TargetDataOp");
8474 auto blockArgIface = cast<omp::BlockArgOpenMPOpInterface>(op);
8475 Region &region = cast<omp::TargetDataOp>(op).getRegion();
8476 switch (bodyGenType) {
8477 case BodyGenTy::Priv:
8478 // Check if any device ptr/addr info is available
8479 if (!info.DevicePtrInfoMap.empty()) {
8480 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Address,
8481 blockArgIface.getUseDeviceAddrBlockArgs(),
8482 useDeviceAddrVars, mapData,
8483 [&](llvm::Value *basePointer) -> llvm::Value * {
8484 if (!info.DevicePtrInfoMap[basePointer].second)
8485 return nullptr;
8486 return builder.CreateLoad(
8487 builder.getPtrTy(),
8488 info.DevicePtrInfoMap[basePointer].second);
8489 });
8490 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer,
8491 blockArgIface.getUseDevicePtrBlockArgs(), useDevicePtrVars,
8492 mapData, [&](llvm::Value *basePointer) {
8493 return info.DevicePtrInfoMap[basePointer].second;
8494 });
8495
8496 if (failed(inlineConvertOmpRegions(region, "omp.data.region", builder,
8497 moduleTranslation)))
8498 return llvm::make_error<PreviouslyReportedError>();
8499 }
8500 break;
8501 case BodyGenTy::DupNoPriv:
8502 if (info.DevicePtrInfoMap.empty()) {
8503 // For host device we still need to do the mapping for codegen,
8504 // otherwise it may try to lookup a missing value.
8505 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Address,
8506 blockArgIface.getUseDeviceAddrBlockArgs(),
8507 useDeviceAddrVars, mapData);
8508 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer,
8509 blockArgIface.getUseDevicePtrBlockArgs(), useDevicePtrVars,
8510 mapData);
8511 }
8512 break;
8513 case BodyGenTy::NoPriv:
8514 // If device info is available then region has already been generated
8515 if (info.DevicePtrInfoMap.empty()) {
8516 if (failed(inlineConvertOmpRegions(region, "omp.data.region", builder,
8517 moduleTranslation)))
8518 return llvm::make_error<PreviouslyReportedError>();
8519 }
8520 break;
8521 }
8522 return builder.saveIP();
8523 };
8524
8525 auto customMapperCB =
8526 [&](unsigned int i) -> llvm::Expected<llvm::Function *> {
8527 if (!combinedInfo.Mappers[i])
8528 return nullptr;
8529 info.HasMapper = true;
8530 return getOrCreateUserDefinedMapperFunc(combinedInfo.Mappers[i], builder,
8531 moduleTranslation, targetDirective);
8532 };
8533
8534 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
8536 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
8537 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
8538 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP = [&]() {
8539 if (isa<omp::TargetDataOp>(op))
8540 return ompBuilder->createTargetData(ompLoc, allocaIP, builder.saveIP(),
8541 deallocBlocks, deviceID, ifCond, info,
8542 genMapInfoCB, customMapperCB,
8543 /*MapperFunc=*/nullptr, bodyGenCB,
8544 /*DeviceAddrCB=*/nullptr);
8545 return ompBuilder->createTargetData(ompLoc, allocaIP, builder.saveIP(),
8546 deallocBlocks, deviceID, ifCond, info,
8547 genMapInfoCB, customMapperCB, &RTLFn);
8548 }();
8549
8550 if (failed(handleError(afterIP, *op)))
8551 return failure();
8552
8553 builder.restoreIP(*afterIP);
8554 return success();
8555}
8556
8557static LogicalResult
8558convertOmpDistribute(Operation &opInst, llvm::IRBuilderBase &builder,
8559 LLVM::ModuleTranslation &moduleTranslation) {
8560 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8561 auto distributeOp = cast<omp::DistributeOp>(opInst);
8562 if (failed(checkImplementationStatus(opInst)))
8563 return failure();
8564
8565 /// Process teams op reduction in distribute if the reduction is contained in
8566 /// this specific distribute op.
8567 omp::TeamsOp teamsOp = opInst.getParentOfType<omp::TeamsOp>();
8568 bool doDistributeReduction =
8569 teamsOp && getDistributeCapturingTeamsReduction(teamsOp) == distributeOp;
8570
8571 DenseMap<Value, llvm::Value *> reductionVariableMap;
8572 unsigned numReductionVars = teamsOp ? teamsOp.getNumReductionVars() : 0;
8574 SmallVector<llvm::Value *> privateReductionVariables(numReductionVars);
8575 llvm::ArrayRef<bool> isByRef;
8576
8577 if (doDistributeReduction) {
8578 isByRef = getIsByRef(teamsOp.getReductionByref());
8579 assert(isByRef.size() == teamsOp.getNumReductionVars());
8580
8581 collectReductionDecls(teamsOp, reductionDecls);
8582 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
8583 findAllocInsertPoints(builder, moduleTranslation);
8584
8585 MutableArrayRef<BlockArgument> reductionArgs =
8586 llvm::cast<omp::BlockArgOpenMPOpInterface>(*teamsOp)
8587 .getReductionBlockArgs();
8588
8590 teamsOp, reductionArgs, builder, moduleTranslation, allocaIP,
8591 reductionDecls, privateReductionVariables, reductionVariableMap,
8592 isByRef)))
8593 return failure();
8594 }
8595
8596 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
8597 auto bodyGenCB =
8598 [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
8599 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
8600 // Save the alloca insertion point on ModuleTranslation stack for use in
8601 // nested regions.
8603 moduleTranslation, allocaIP, deallocBlocks);
8604
8605 // DistributeOp has only one region associated with it.
8606 builder.restoreIP(codeGenIP);
8607 PrivateVarsInfo privVarsInfo(distributeOp);
8608
8610 distributeOp, builder, moduleTranslation, privVarsInfo, allocaIP);
8611 if (handleError(afterAllocas, opInst).failed())
8612 return llvm::make_error<PreviouslyReportedError>();
8613
8614 if (handleError(initPrivateVars(builder, moduleTranslation, privVarsInfo),
8615 opInst)
8616 .failed())
8617 return llvm::make_error<PreviouslyReportedError>();
8618
8619 if (failed(copyFirstPrivateVars(
8620 distributeOp, builder, moduleTranslation, privVarsInfo.mlirVars,
8621 privVarsInfo.llvmVars, privVarsInfo.privatizers,
8622 distributeOp.getPrivateNeedsBarrier())))
8623 return llvm::make_error<PreviouslyReportedError>();
8624
8625 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8626 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
8628 convertOmpOpRegions(distributeOp.getRegion(), "omp.distribute.region",
8629 builder, moduleTranslation);
8630 if (!regionBlock)
8631 return regionBlock.takeError();
8632 builder.SetInsertPoint(*regionBlock, (*regionBlock)->begin());
8633
8634 // Skip applying a workshare loop below when translating 'distribute
8635 // parallel do' (it's been already handled by this point while translating
8636 // the nested omp.wsloop).
8637 if (!isa_and_present<omp::WsloopOp>(distributeOp.getNestedWrapper())) {
8638 // TODO: Add support for clauses which are valid for DISTRIBUTE
8639 // constructs. Static schedule is the default.
8640 bool hasDistSchedule = distributeOp.getDistScheduleStatic();
8641 auto schedule = hasDistSchedule ? omp::ClauseScheduleKind::Distribute
8642 : omp::ClauseScheduleKind::Static;
8643 // dist_schedule clauses are ordered - otherise this should be false
8644 bool isOrdered = hasDistSchedule;
8645 std::optional<omp::ScheduleModifier> scheduleMod;
8646 bool isSimd = false;
8647 llvm::omp::WorksharingLoopType workshareLoopType =
8648 llvm::omp::WorksharingLoopType::DistributeStaticLoop;
8649 bool loopNeedsBarrier = false;
8650 llvm::Value *chunk = moduleTranslation.lookupValue(
8651 distributeOp.getDistScheduleChunkSize());
8652 llvm::CanonicalLoopInfo *loopInfo =
8653 findCurrentLoopInfo(moduleTranslation);
8654 llvm::OpenMPIRBuilder::InsertPointOrErrorTy wsloopIP =
8655 ompBuilder->applyWorkshareLoop(
8656 ompLoc.DL, loopInfo, allocaIP, loopNeedsBarrier,
8657 convertToScheduleKind(schedule), chunk, isSimd,
8658 scheduleMod == omp::ScheduleModifier::monotonic,
8659 scheduleMod == omp::ScheduleModifier::nonmonotonic, isOrdered,
8660 workshareLoopType, false, hasDistSchedule, chunk);
8661
8662 if (!wsloopIP)
8663 return wsloopIP.takeError();
8664 }
8665 if (failed(cleanupPrivateVars(distributeOp, builder, moduleTranslation,
8666 distributeOp.getLoc(), privVarsInfo)))
8667 return llvm::make_error<PreviouslyReportedError>();
8668
8669 return llvm::Error::success();
8670 };
8671
8673 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
8674 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
8675 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
8676 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
8677 ompBuilder->createDistribute(ompLoc, allocaIP, deallocBlocks, bodyGenCB);
8678
8679 if (failed(handleError(afterIP, opInst)))
8680 return failure();
8681
8682 builder.restoreIP(*afterIP);
8683
8684 if (doDistributeReduction) {
8685 // Process the reductions if required.
8687 teamsOp, builder, moduleTranslation, allocaIP, reductionDecls,
8688 privateReductionVariables, isByRef,
8689 /*isNoWait*/ false, /*isTeamsReduction*/ true);
8690 }
8691 return success();
8692}
8693
8694/// Lowers the FlagsAttr which is applied to the module when offloading. This
8695/// attribute contains OpenMP RTL globals that can be passed as flags to the
8696/// frontend, otherwise they are set to default
8697static LogicalResult
8698convertFlagsAttr(Operation *op, mlir::omp::FlagsAttr attribute,
8699 LLVM::ModuleTranslation &moduleTranslation) {
8700 auto offloadMod = dyn_cast<omp::OffloadModuleInterface>(op);
8701 if (!offloadMod)
8702 return op->emitOpError() << "omp flags attached to non offload module op";
8703
8704 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8705
8706 if (offloadMod.getIsTargetDevice())
8707 ompBuilder->M.addModuleFlag(llvm::Module::Max, "openmp-device",
8708 attribute.getOpenmpDeviceVersion());
8709
8710 // The flags below are only intended to be emitted for GPU offload targets.
8711 if (!offloadMod.getIsGPU())
8712 return success();
8713
8714 if (attribute.getNoGpuLib())
8715 return success();
8716
8717 ompBuilder->createGlobalFlag(attribute.getDebugKind(),
8718 "__omp_rtl_debug_kind");
8719 ompBuilder->createGlobalFlag(attribute.getAssumeTeamsOversubscription(),
8720 "__omp_rtl_assume_teams_oversubscription");
8721 ompBuilder->createGlobalFlag(attribute.getAssumeThreadsOversubscription(),
8722 "__omp_rtl_assume_threads_oversubscription");
8723 ompBuilder->createGlobalFlag(attribute.getAssumeNoThreadState(),
8724 "__omp_rtl_assume_no_thread_state");
8725 ompBuilder->createGlobalFlag(attribute.getAssumeNoNestedParallelism(),
8726 "__omp_rtl_assume_no_nested_parallelism");
8727 return success();
8728}
8729
8730static void getTargetEntryUniqueInfo(llvm::TargetRegionEntryInfo &targetInfo,
8731 omp::TargetOp targetOp,
8732 llvm::OpenMPIRBuilder &ompBuilder,
8733 llvm::vfs::FileSystem &vfs,
8734 llvm::StringRef parentName = "") {
8735 auto fileLoc = targetOp.getLoc()->findInstanceOf<FileLineColLoc>();
8736 assert(fileLoc && "No file found from location");
8737
8738 auto fileInfoCallBack = [&fileLoc]() {
8739 return std::pair<std::string, uint64_t>(
8740 llvm::StringRef(fileLoc.getFilename()), fileLoc.getLine());
8741 };
8742
8743 targetInfo =
8744 ompBuilder.getTargetEntryUniqueInfo(fileInfoCallBack, vfs, parentName);
8745}
8746
8747// The createDeviceArgumentAccessor function generates
8748// instructions for retrieving (acessing) kernel
8749// arguments inside of the device kernel for use by
8750// the kernel. This enables different semantics such as
8751// the creation of temporary copies of data allowing
8752// semantics like read-only/no host write back kernel
8753// arguments.
8754//
8755// This currently implements a very light version of Clang's
8756// EmitParmDecl's handling of direct argument handling as well
8757// as a portion of the argument access generation based on
8758// capture types found at the end of emitOutlinedFunctionPrologue
8759// in Clang. The indirect path handling of EmitParmDecl's may be
8760// required for future work, but a direct 1-to-1 copy doesn't seem
8761// possible as the logic is rather scattered throughout Clang's
8762// lowering and perhaps we wish to deviate slightly.
8763//
8764// \param mapData - A container containing vectors of information
8765// corresponding to the input argument, which should have a
8766// corresponding entry in the MapInfoData containers
8767// OrigialValue's.
8768// \param arg - This is the generated kernel function argument that
8769// corresponds to the passed in input argument. We generated different
8770// accesses of this Argument, based on capture type and other Input
8771// related information.
8772// \param input - This is the host side value that will be passed to
8773// the kernel i.e. the kernel input, we rewrite all uses of this within
8774// the kernel (as we generate the kernel body based on the target's region
8775// which maintians references to the original input) to the retVal argument
8776// apon exit of this function inside of the OMPIRBuilder. This interlinks
8777// the kernel argument to future uses of it in the function providing
8778// appropriate "glue" instructions inbetween.
8779// \param retVal - This is the value that all uses of input inside of the
8780// kernel will be re-written to, the goal of this function is to generate
8781// an appropriate location for the kernel argument to be accessed from,
8782// e.g. ByRef will result in a temporary allocation location and then
8783// a store of the kernel argument into this allocated memory which
8784// will then be loaded from, ByCopy will use the allocated memory
8785// directly.
8786static llvm::IRBuilderBase::InsertPoint createDeviceArgumentAccessor(
8787 omp::TargetOp targetOp, MapInfoData &mapData, llvm::Argument &arg,
8788 llvm::Value *input, llvm::Value *&retVal, llvm::IRBuilderBase &builder,
8789 llvm::OpenMPIRBuilder &ompBuilder,
8790 LLVM::ModuleTranslation &moduleTranslation,
8791 llvm::IRBuilderBase::InsertPoint allocaIP,
8792 llvm::IRBuilderBase::InsertPoint codeGenIP,
8794 assert(ompBuilder.Config.isTargetDevice() &&
8795 "function only supported for target device codegen");
8796 builder.restoreIP(allocaIP);
8797
8798 omp::VariableCaptureKind capture = omp::VariableCaptureKind::ByRef;
8799 LLVM::TypeToLLVMIRTranslator typeToLLVMIRTranslator(
8800 ompBuilder.M.getContext());
8801 unsigned alignmentValue = 0;
8802 BlockArgument mlirArg;
8804 cast<omp::BlockArgOpenMPOpInterface>(*targetOp).getBlockArgsPairs(
8805 blockArgsPairs);
8806 // Find the associated MapInfoData entry for the current input
8807 for (size_t i = 0; i < mapData.MapClause.size(); ++i) {
8808 if (mapData.OriginalValue[i] == input) {
8809 auto mapOp = cast<omp::MapInfoOp>(mapData.MapClause[i]);
8810 capture = mapOp.getMapCaptureType();
8811 // Get information of alignment of mapped object
8812 alignmentValue = typeToLLVMIRTranslator.getPreferredAlignment(
8813 mapOp.getVarPtrType(), ompBuilder.M.getDataLayout());
8814
8815 // Find the corresponding entry block argument, which can be associated to
8816 // a map, use_device* or has_device* clause.
8817 for (auto &[val, arg] : blockArgsPairs) {
8818 if (mapOp.getResult() == val) {
8819 mlirArg = arg;
8820 break;
8821 }
8822 }
8823 assert(mlirArg && "expected to find entry block argument for map clause");
8824 break;
8825 }
8826 }
8827
8828 unsigned int allocaAS = ompBuilder.M.getDataLayout().getAllocaAddrSpace();
8829 unsigned int defaultAS =
8830 ompBuilder.M.getDataLayout().getProgramAddressSpace();
8831
8832 // Create the allocation for the argument.
8833 llvm::Value *v = nullptr;
8834 if (omp::opInSharedDeviceContext(*targetOp) &&
8836 // Use the beginning of the codeGenIP rather than the usual allocation point
8837 // for shared memory allocations because otherwise these would be done prior
8838 // to the target initialization call. Also, the exit block (where the
8839 // deallocation is placed) is only executed if the initialization call
8840 // succeeds.
8841 builder.SetInsertPoint(codeGenIP.getBlock()->getFirstInsertionPt());
8842 v = ompBuilder.createOMPAllocShared(builder, arg.getType());
8843
8844 // Create deallocations in all provided deallocation points and then restore
8845 // the insertion point to right after the new allocations.
8846 llvm::IRBuilderBase::InsertPointGuard guard(builder);
8847 for (auto deallocIP : deallocIPs) {
8848 builder.SetInsertPoint(deallocIP.getBlock(), deallocIP.getPoint());
8849 ompBuilder.createOMPFreeShared(builder, v, arg.getType());
8850 }
8851 } else {
8852 // Use the current point, which was previously set to allocaIP.
8853 v = builder.CreateAlloca(arg.getType(), allocaAS);
8854
8855 if (allocaAS != defaultAS && arg.getType()->isPointerTy())
8856 v = builder.CreateAddrSpaceCast(v, builder.getPtrTy(defaultAS));
8857 }
8858
8859 builder.CreateStore(&arg, v);
8860
8861 builder.restoreIP(codeGenIP);
8862
8863 switch (capture) {
8864 case omp::VariableCaptureKind::ByCopy: {
8865 retVal = v;
8866 break;
8867 }
8868 case omp::VariableCaptureKind::ByRef: {
8869 llvm::LoadInst *loadInst = builder.CreateAlignedLoad(
8870 v->getType(), v,
8871 ompBuilder.M.getDataLayout().getPrefTypeAlign(v->getType()));
8872 // CreateAlignedLoad function creates similar LLVM IR:
8873 // %res = load ptr, ptr %input, align 8
8874 // This LLVM IR does not contain information about alignment
8875 // of the loaded value. We need to add !align metadata to unblock
8876 // optimizer. The existence of the !align metadata on the instruction
8877 // tells the optimizer that the value loaded is known to be aligned to
8878 // a boundary specified by the integer value in the metadata node.
8879 // Example:
8880 // %res = load ptr, ptr %input, align 8, !align !align_md_node
8881 // ^ ^
8882 // | |
8883 // alignment of %input address |
8884 // |
8885 // alignment of %res object
8886 if (v->getType()->isPointerTy() && alignmentValue) {
8887 llvm::MDBuilder MDB(builder.getContext());
8888 loadInst->setMetadata(
8889 llvm::LLVMContext::MD_align,
8890 llvm::MDNode::get(builder.getContext(),
8891 MDB.createConstant(llvm::ConstantInt::get(
8892 llvm::Type::getInt64Ty(builder.getContext()),
8893 alignmentValue))));
8894 }
8895 retVal = loadInst;
8896
8897 break;
8898 }
8899 case omp::VariableCaptureKind::This:
8900 case omp::VariableCaptureKind::VLAType:
8901 // TODO: Consider returning error to use standard reporting for
8902 // unimplemented features.
8903 assert(false && "Currently unsupported capture kind");
8904 break;
8905 }
8906
8907 return builder.saveIP();
8908}
8909
8910/// Follow uses of `host_eval`-defined block arguments of the given `omp.target`
8911/// operation and populate output variables with their corresponding host value
8912/// (i.e. operand evaluated outside of the target region), based on their uses
8913/// inside of the target region.
8914///
8915/// Loop bounds and steps are only optionally populated, if output vectors are
8916/// provided.
8917static void
8918extractHostEvalClauses(omp::TargetOp targetOp, Value &numThreads,
8919 Value &numTeamsLower, Value &numTeamsUpper,
8920 Value &threadLimit,
8921 llvm::SmallVectorImpl<Value> *lowerBounds = nullptr,
8922 llvm::SmallVectorImpl<Value> *upperBounds = nullptr,
8923 llvm::SmallVectorImpl<Value> *steps = nullptr) {
8924 auto blockArgIface = llvm::cast<omp::BlockArgOpenMPOpInterface>(*targetOp);
8925 for (auto item : llvm::zip_equal(targetOp.getHostEvalVars(),
8926 blockArgIface.getHostEvalBlockArgs())) {
8927 Value hostEvalVar = std::get<0>(item), blockArg = std::get<1>(item);
8928
8929 for (Operation *user : blockArg.getUsers()) {
8931 .Case([&](omp::TeamsOp teamsOp) {
8932 if (teamsOp.getNumTeamsLower() == blockArg)
8933 numTeamsLower = hostEvalVar;
8934 else if (llvm::is_contained(teamsOp.getNumTeamsUpperVars(),
8935 blockArg))
8936 numTeamsUpper = hostEvalVar;
8937 else if (!teamsOp.getThreadLimitVars().empty() &&
8938 teamsOp.getThreadLimit(0) == blockArg)
8939 threadLimit = hostEvalVar;
8940 else
8941 llvm_unreachable("unsupported host_eval use");
8942 })
8943 .Case([&](omp::ParallelOp parallelOp) {
8944 if (!parallelOp.getNumThreadsVars().empty() &&
8945 parallelOp.getNumThreads(0) == blockArg)
8946 numThreads = hostEvalVar;
8947 else
8948 llvm_unreachable("unsupported host_eval use");
8949 })
8950 .Case([&](omp::LoopNestOp loopOp) {
8951 auto processBounds =
8952 [&](OperandRange opBounds,
8953 llvm::SmallVectorImpl<Value> *outBounds) -> bool {
8954 bool found = false;
8955 for (auto [i, lb] : llvm::enumerate(opBounds)) {
8956 if (lb == blockArg) {
8957 found = true;
8958 if (outBounds)
8959 (*outBounds)[i] = hostEvalVar;
8960 }
8961 }
8962 return found;
8963 };
8964 bool found =
8965 processBounds(loopOp.getLoopLowerBounds(), lowerBounds);
8966 found = processBounds(loopOp.getLoopUpperBounds(), upperBounds) ||
8967 found;
8968 found = processBounds(loopOp.getLoopSteps(), steps) || found;
8969 (void)found;
8970 assert(found && "unsupported host_eval use");
8971 })
8972 .DefaultUnreachable("unsupported host_eval use");
8973 }
8974 }
8975}
8976
8977/// If \p op is of the given type parameter, return it casted to that type.
8978/// Otherwise, if its immediate parent operation (or some other higher-level
8979/// parent, if \p immediateParent is false) is of that type, return that parent
8980/// casted to the given type.
8981///
8982/// If \p op is \c null or neither it or its parent(s) are of the specified
8983/// type, return a \c null operation.
8984template <typename OpTy>
8985static OpTy castOrGetParentOfType(Operation *op, bool immediateParent = false) {
8986 if (!op)
8987 return OpTy();
8988
8989 if (OpTy casted = dyn_cast<OpTy>(op))
8990 return casted;
8991
8992 if (immediateParent)
8993 return dyn_cast_if_present<OpTy>(op->getParentOp());
8994
8995 return op->getParentOfType<OpTy>();
8996}
8997
8998/// If the given \p value is defined by an \c llvm.mlir.constant operation and
8999/// it is of an integer type, return its value.
9000static std::optional<int64_t> extractConstInteger(Value value) {
9001 if (!value)
9002 return std::nullopt;
9003
9004 if (auto constOp = value.getDefiningOp<LLVM::ConstantOp>())
9005 if (auto constAttr = dyn_cast<IntegerAttr>(constOp.getValue()))
9006 return constAttr.getInt();
9007
9008 return std::nullopt;
9009}
9010
9011static uint64_t getTypeByteSize(mlir::Type type, const DataLayout &dl) {
9012 uint64_t sizeInBits = dl.getTypeSizeInBits(type);
9013 uint64_t sizeInBytes = sizeInBits / 8;
9014 return sizeInBytes;
9015}
9016
9017template <typename OpTy>
9018static uint64_t getReductionDataSize(OpTy &op) {
9019 if (op.getNumReductionVars() > 0) {
9021 collectReductionDecls(op, reductions);
9022
9024 members.reserve(reductions.size());
9025 for (omp::DeclareReductionOp &red : reductions) {
9026 // For by-ref reductions, use the actual element type rather than the
9027 // pointer type so that the buffer size matches the access pattern in
9028 // the copy/reduce callbacks generated by OMPIRBuilder.
9029 if (red.getByrefElementType())
9030 members.push_back(*red.getByrefElementType());
9031 else
9032 members.push_back(red.getType());
9033 }
9034 Operation *opp = op.getOperation();
9035 auto structType = mlir::LLVM::LLVMStructType::getLiteral(
9036 opp->getContext(), members, /*isPacked=*/false);
9037 DataLayout dl = DataLayout(opp->getParentOfType<ModuleOp>());
9038 return getTypeByteSize(structType, dl);
9039 }
9040 return 0;
9041}
9042
9043/// Populate default `MinTeams`, `MaxTeams` and `MaxThreads` to their default
9044/// values as stated by the corresponding clauses, if constant.
9045///
9046/// These default values must be set before the creation of the outlined LLVM
9047/// function for the target region, so that they can be used to initialize the
9048/// corresponding global `ConfigurationEnvironmentTy` structure.
9049static void
9050initTargetDefaultAttrs(omp::TargetOp targetOp, Operation *capturedOp,
9051 llvm::OpenMPIRBuilder::TargetKernelDefaultAttrs &attrs,
9052 bool isTargetDevice, bool isGPU) {
9053 // TODO: Handle constant 'if' clauses.
9054
9055 Value numThreads, numTeamsLower, numTeamsUpper, threadLimit;
9056 if (!isTargetDevice) {
9057 extractHostEvalClauses(targetOp, numThreads, numTeamsLower, numTeamsUpper,
9058 threadLimit);
9059 } else {
9060 // In the target device, values for these clauses are not passed as
9061 // host_eval, but instead evaluated prior to entry to the region. This
9062 // ensures values are mapped and available inside of the target region.
9063 if (auto teamsOp = castOrGetParentOfType<omp::TeamsOp>(capturedOp)) {
9064 numTeamsLower = teamsOp.getNumTeamsLower();
9065 // Handle num_teams upper bounds (only first value for now)
9066 if (!teamsOp.getNumTeamsUpperVars().empty())
9067 numTeamsUpper = teamsOp.getNumTeams(0);
9068 if (!teamsOp.getThreadLimitVars().empty())
9069 threadLimit = teamsOp.getThreadLimit(0);
9070 }
9071
9072 if (auto parallelOp = castOrGetParentOfType<omp::ParallelOp>(capturedOp)) {
9073 if (!parallelOp.getNumThreadsVars().empty())
9074 numThreads = parallelOp.getNumThreads(0);
9075 }
9076 }
9077
9078 // Handle clauses impacting the number of teams.
9079
9080 int32_t minTeamsVal = 1, maxTeamsVal = -1;
9081 if (castOrGetParentOfType<omp::TeamsOp>(capturedOp)) {
9082 // TODO: Use `hostNumTeamsLower` to initialize `minTeamsVal`. For now,
9083 // match clang and set min and max to the same value.
9084 if (numTeamsUpper) {
9085 if (auto val = extractConstInteger(numTeamsUpper))
9086 minTeamsVal = maxTeamsVal = *val;
9087 } else {
9088 minTeamsVal = maxTeamsVal = 0;
9089 }
9090 } else if (castOrGetParentOfType<omp::ParallelOp>(capturedOp,
9091 /*immediateParent=*/true) ||
9093 /*immediateParent=*/true)) {
9094 minTeamsVal = maxTeamsVal = 1;
9095 } else {
9096 minTeamsVal = maxTeamsVal = -1;
9097 }
9098
9099 // Handle clauses impacting the number of threads.
9100
9101 auto setMaxValueFromClause = [](Value clauseValue, int32_t &result) {
9102 if (!clauseValue)
9103 return;
9104
9105 if (auto val = extractConstInteger(clauseValue))
9106 result = *val;
9107
9108 // Found an applicable clause, so it's not undefined. Mark as unknown
9109 // because it's not constant.
9110 if (result < 0)
9111 result = 0;
9112 };
9113
9114 // Extract 'thread_limit' clause from 'target' and 'teams' directives.
9115 int32_t targetThreadLimitVal = -1, teamsThreadLimitVal = -1;
9116 if (!targetOp.getThreadLimitVars().empty())
9117 setMaxValueFromClause(targetOp.getThreadLimit(0), targetThreadLimitVal);
9118 setMaxValueFromClause(threadLimit, teamsThreadLimitVal);
9119
9120 // Extract 'max_threads' clause from 'parallel' or set to 1 if it's SIMD.
9121 int32_t maxThreadsVal = -1;
9123 setMaxValueFromClause(numThreads, maxThreadsVal);
9124 else if (castOrGetParentOfType<omp::SimdOp>(capturedOp,
9125 /*immediateParent=*/true))
9126 maxThreadsVal = 1;
9127
9128 // For max values, < 0 means unset, == 0 means set but unknown. Select the
9129 // minimum value between 'max_threads' and 'thread_limit' clauses that were
9130 // set.
9131 int32_t combinedMaxThreadsVal = targetThreadLimitVal;
9132 if (combinedMaxThreadsVal < 0 ||
9133 (teamsThreadLimitVal >= 0 && teamsThreadLimitVal < combinedMaxThreadsVal))
9134 combinedMaxThreadsVal = teamsThreadLimitVal;
9135
9136 if (combinedMaxThreadsVal < 0 ||
9137 (maxThreadsVal >= 0 && maxThreadsVal < combinedMaxThreadsVal))
9138 combinedMaxThreadsVal = maxThreadsVal;
9139
9140 int32_t reductionDataSize = 0;
9141 if (isGPU && capturedOp) {
9142 if (auto teamsOp = castOrGetParentOfType<omp::TeamsOp>(capturedOp))
9143 reductionDataSize = getReductionDataSize(teamsOp);
9144 }
9145
9146 // Update kernel bounds structure for the `OpenMPIRBuilder` to use.
9147 // Use the kernel_type attribute set by the frontend instead of analyzing IR.
9148 omp::TargetExecMode execMode = targetOp.getKernelType();
9149 switch (execMode) {
9150 case omp::TargetExecMode::bare:
9151 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_BARE;
9152 break;
9153 case omp::TargetExecMode::generic:
9154 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_GENERIC;
9155 break;
9156 case omp::TargetExecMode::spmd:
9157 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_SPMD;
9158 break;
9159 case omp::TargetExecMode::spmd_no_loop:
9160 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_SPMD_NO_LOOP;
9161 break;
9162 }
9163 attrs.MinTeams.front() = minTeamsVal;
9164 attrs.MaxTeams.front() = maxTeamsVal;
9165 attrs.MinThreads.front() = 1;
9166 attrs.MaxThreads.front() = combinedMaxThreadsVal;
9167 attrs.ReductionDataSize = reductionDataSize;
9168}
9169
9170/// Gather LLVM runtime values for all clauses evaluated in the host that are
9171/// passed to the kernel invocation.
9172///
9173/// This function must be called only when compiling for the host. Also, it will
9174/// only provide correct results if it's called after the body of \c targetOp
9175/// has been fully generated.
9176static void
9177initTargetRuntimeAttrs(llvm::IRBuilderBase &builder,
9178 LLVM::ModuleTranslation &moduleTranslation,
9179 omp::TargetOp targetOp, Operation *capturedOp,
9180 llvm::OpenMPIRBuilder::TargetKernelRuntimeAttrs &attrs) {
9181 omp::LoopNestOp loopOp = castOrGetParentOfType<omp::LoopNestOp>(capturedOp);
9182 unsigned numLoops = loopOp ? loopOp.getNumLoops() : 0;
9183
9184 Value numThreads, numTeamsLower, numTeamsUpper, teamsThreadLimit;
9185 llvm::SmallVector<Value> lowerBounds(numLoops), upperBounds(numLoops),
9186 steps(numLoops);
9187 extractHostEvalClauses(targetOp, numThreads, numTeamsLower, numTeamsUpper,
9188 teamsThreadLimit, &lowerBounds, &upperBounds, &steps);
9189
9190 // TODO: Handle constant 'if' clauses.
9191 if (!targetOp.getThreadLimitVars().empty()) {
9192 Value targetThreadLimit = targetOp.getThreadLimit(0);
9193 attrs.TargetThreadLimit.front() =
9194 moduleTranslation.lookupValue(targetThreadLimit);
9195 }
9196
9197 // The __kmpc_push_num_teams_51 function expects int32 as the arguments. So,
9198 // truncate or sign extend lower and upper num_teams bounds as well as
9199 // thread_limit to match int32 ABI requirements for the OpenMP runtime.
9200 if (numTeamsLower)
9201 attrs.MinTeams.front() = builder.CreateSExtOrTrunc(
9202 moduleTranslation.lookupValue(numTeamsLower), builder.getInt32Ty());
9203
9204 if (numTeamsUpper)
9205 attrs.MaxTeams.front() = builder.CreateSExtOrTrunc(
9206 moduleTranslation.lookupValue(numTeamsUpper), builder.getInt32Ty());
9207
9208 if (teamsThreadLimit)
9209 attrs.TeamsThreadLimit.front() = builder.CreateSExtOrTrunc(
9210 moduleTranslation.lookupValue(teamsThreadLimit), builder.getInt32Ty());
9211
9212 if (numThreads)
9213 attrs.MaxThreads.front() = moduleTranslation.lookupValue(numThreads);
9214
9215 if (targetOp.hasHostEvalTripCount()) {
9216 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9217 attrs.LoopTripCount = nullptr;
9218
9219 // To calculate the trip count, we multiply together the trip counts of
9220 // every collapsed canonical loop. We don't need to create the loop nests
9221 // here, since we're only interested in the trip count.
9222 for (auto [loopLower, loopUpper, loopStep] :
9223 llvm::zip_equal(lowerBounds, upperBounds, steps)) {
9224 llvm::Value *lowerBound = moduleTranslation.lookupValue(loopLower);
9225 llvm::Value *upperBound = moduleTranslation.lookupValue(loopUpper);
9226 llvm::Value *step = moduleTranslation.lookupValue(loopStep);
9227
9228 if (!lowerBound || !upperBound || !step) {
9229 attrs.LoopTripCount = nullptr;
9230 break;
9231 }
9232
9233 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
9234 llvm::Value *tripCount = ompBuilder->calculateCanonicalLoopTripCount(
9235 loc, lowerBound, upperBound, step, /*IsSigned=*/true,
9236 loopOp.getLoopInclusive());
9237
9238 if (!attrs.LoopTripCount) {
9239 attrs.LoopTripCount = tripCount;
9240 continue;
9241 }
9242
9243 // TODO: Enable UndefinedSanitizer to diagnose an overflow here.
9244 attrs.LoopTripCount = builder.CreateMul(attrs.LoopTripCount, tripCount,
9245 {}, /*HasNUW=*/true);
9246 }
9247 }
9248
9249 attrs.DeviceID = builder.getInt64(llvm::omp::OMP_DEVICEID_UNDEF);
9250 if (mlir::Value devId = targetOp.getDevice()) {
9251 attrs.DeviceID = moduleTranslation.lookupValue(devId);
9252 attrs.DeviceID =
9253 builder.CreateSExtOrTrunc(attrs.DeviceID, builder.getInt64Ty());
9254 }
9255}
9256
9257static llvm::omp::OMPDynGroupprivateFallbackType
9258getDynGroupprivateFallbackType(omp::FallbackModifierAttr fallbackAttr) {
9259 omp::FallbackModifier fb = fallbackAttr ? fallbackAttr.getValue()
9260 : omp::FallbackModifier::default_mem;
9261 switch (fb) {
9262 case omp::FallbackModifier::abort:
9263 return llvm::omp::OMPDynGroupprivateFallbackType::Abort;
9264 case omp::FallbackModifier::null:
9265 return llvm::omp::OMPDynGroupprivateFallbackType::Null;
9266 case omp::FallbackModifier::default_mem:
9267 return llvm::omp::OMPDynGroupprivateFallbackType::DefaultMem;
9268 }
9269
9270 llvm_unreachable("unexpected dyn_groupprivate fallback type");
9271}
9272
9273static LogicalResult
9274convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
9275 LLVM::ModuleTranslation &moduleTranslation) {
9276 auto targetOp = cast<omp::TargetOp>(opInst);
9277
9278 // The current debug location already has the DISubprogram for the outlined
9279 // function that will be created for the target op. We save it here so that
9280 // we can set it on the outlined function.
9281 llvm::DebugLoc outlinedFnLoc = builder.getCurrentDebugLocation();
9282 if (failed(checkImplementationStatus(opInst)))
9283 return failure();
9284
9285 // During the handling of target op, we will generate instructions in the
9286 // parent function like call to the oulined function or branch to a new
9287 // BasicBlock. We set the debug location here to parent function so that those
9288 // get the correct debug locations. For outlined functions, the normal MLIR op
9289 // conversion will automatically pick the correct location.
9290 llvm::BasicBlock *parentBB = builder.GetInsertBlock();
9291 assert(parentBB && "No insert block is set for the builder");
9292 llvm::Function *parentLLVMFn = parentBB->getParent();
9293 assert(parentLLVMFn && "Parent Function must be valid");
9294 if (llvm::DISubprogram *SP = parentLLVMFn->getSubprogram())
9295 builder.SetCurrentDebugLocation(llvm::DILocation::get(
9296 parentLLVMFn->getContext(), outlinedFnLoc.getLine(),
9297 outlinedFnLoc.getCol(), SP, outlinedFnLoc.getInlinedAt()));
9298
9299 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9300 bool isTargetDevice = ompBuilder->Config.isTargetDevice();
9301 bool isGPU = ompBuilder->Config.isGPU();
9302
9303 auto parentFn = opInst.getParentOfType<LLVM::LLVMFuncOp>();
9304 auto argIface = cast<omp::BlockArgOpenMPOpInterface>(opInst);
9305 auto &targetRegion = targetOp.getRegion();
9306 // Holds the private vars that have been mapped along with the block
9307 // argument that corresponds to the MapInfoOp corresponding to the private
9308 // var in question. So, for instance:
9309 //
9310 // %10 = omp.map.info var_ptr(%6#0 : !fir.ref<!fir.box<!fir.heap<i32>>>, ..)
9311 // omp.target map_entries(%10 -> %arg0) private(@box.privatizer %6#0-> %arg1)
9312 //
9313 // Then, %10 has been created so that the descriptor can be used by the
9314 // privatizer @box.privatizer on the device side. Here we'd record {%6#0,
9315 // %arg0} in the mappedPrivateVars map.
9316 llvm::DenseMap<Value, Value> mappedPrivateVars;
9317 DataLayout dl = DataLayout(opInst.getParentOfType<ModuleOp>());
9318 SmallVector<Value> mapVars = targetOp.getMapVars();
9319 SmallVector<Value> hdaVars = targetOp.getHasDeviceAddrVars();
9320 ArrayRef<BlockArgument> mapBlockArgs = argIface.getMapBlockArgs();
9321 ArrayRef<BlockArgument> hdaBlockArgs = argIface.getHasDeviceAddrBlockArgs();
9322 llvm::Function *llvmOutlinedFn = nullptr;
9323 TargetDirectiveEnumTy targetDirective =
9324 getTargetDirectiveEnumTyFromOp(&opInst);
9325
9326 // TODO: It can also be false if a compile-time constant `false` IF clause is
9327 // specified.
9328 bool isOffloadEntry =
9329 isTargetDevice || !ompBuilder->Config.TargetTriples.empty();
9330
9331 // Resolve in_reduction clauses on omp.target for the host. From the target
9332 // device's perspective an in_reduction list item behaves as a regular
9333 // map(tofrom) variable, so no special handling is needed there; only the
9334 // host redirects the mapped value to the per-task reduction-private storage
9335 // returned by __kmpc_task_reduction_get_th_data (emitted inside the
9336 // to-be-outlined target task body). This applies to both offloading and
9337 // non-offloading host modules.
9338 //
9339 // The target body has no dedicated in_reduction block argument: each
9340 // in_reduction variable is accessed through its map_entries block argument.
9341 // So each in_reduction variable must also be captured by a matching
9342 // map_entries entry (guaranteed by the verifier); without one the outlined
9343 // body would reference a value defined in the host function. Record, for each
9344 // in_reduction variable, the position of that map entry so the corresponding
9345 // map block argument can be redirected inside the body. The in_reduction
9346 // operand itself is used as the `orig` argument of the runtime lookup.
9347 SmallVector<llvm::Value *> inRedOrigPtrs;
9348 SmallVector<unsigned> inRedMapArgIdx;
9349 if (!targetOp.getInReductionVars().empty() && !isTargetDevice) {
9350 inRedOrigPtrs.reserve(targetOp.getInReductionVars().size());
9351 inRedMapArgIdx.reserve(targetOp.getInReductionVars().size());
9352 for (Value v : targetOp.getInReductionVars()) {
9353 // Select the map_entries entry that captures this in_reduction operand.
9354 // The verifier guarantees at least one match exists; more than one
9355 // matching entry is a lowering ambiguity (the redirect cannot pick which
9356 // map argument to rebind).
9357 std::optional<unsigned> matchIdx;
9358 for (auto [idx, mapV] : llvm::enumerate(targetOp.getMapVars())) {
9359 auto mapInfo = mapV.getDefiningOp<omp::MapInfoOp>();
9360 if (v != mapInfo.getVarPtr())
9361 continue;
9362 if (matchIdx)
9363 return targetOp.emitError()
9364 << "in_reduction variable on omp.target has multiple matching "
9365 "map_entries entries; the redirect target is ambiguous";
9366 matchIdx = idx;
9367 }
9368 // The verifier requires a capturing map entry for every in_reduction
9369 // operand, so a match must exist here.
9370 assert(matchIdx &&
9371 "TargetOp verifier guarantees a matching map_entries entry for "
9372 "each in_reduction variable");
9373 inRedMapArgIdx.push_back(*matchIdx);
9374 // The runtime `orig` pointer is the in_reduction operand itself, the
9375 // reduction variable the enclosing taskgroup registered.
9376 inRedOrigPtrs.push_back(moduleTranslation.lookupValue(v));
9377 }
9378 }
9379
9380 // For some private variables, the MapsForPrivatizedVariablesPass
9381 // creates MapInfoOp instances. Go through the private variables and
9382 // the mapped variables so that during codegeneration we are able
9383 // to quickly look up the corresponding map variable, if any for each
9384 // private variable.
9385 if (!targetOp.getPrivateVars().empty() && !targetOp.getMapVars().empty()) {
9386 OperandRange privateVars = targetOp.getPrivateVars();
9387 std::optional<ArrayAttr> privateSyms = targetOp.getPrivateSyms();
9388 std::optional<DenseI64ArrayAttr> privateMapIndices =
9389 targetOp.getPrivateMapsAttr();
9390
9391 for (auto [privVarIdx, privVarSymPair] :
9392 llvm::enumerate(llvm::zip_equal(privateVars, *privateSyms))) {
9393 auto privVar = std::get<0>(privVarSymPair);
9394 auto privSym = std::get<1>(privVarSymPair);
9395
9396 SymbolRefAttr privatizerName = llvm::cast<SymbolRefAttr>(privSym);
9397 omp::PrivateClauseOp privatizer =
9398 findPrivatizer(targetOp, privatizerName);
9399
9400 if (!privatizer.needsMap())
9401 continue;
9402
9403 mlir::Value mappedValue =
9404 targetOp.getMappedValueForPrivateVar(privVarIdx);
9405 assert(mappedValue && "Expected to find mapped value for a privatized "
9406 "variable that needs mapping");
9407
9408 // The MapInfoOp defining the map var isn't really needed later.
9409 // So, we don't store it in any datastructure. Instead, we just
9410 // do some sanity checks on it right now.
9411 auto mapInfoOp = mappedValue.getDefiningOp<omp::MapInfoOp>();
9412 [[maybe_unused]] Type varType = mapInfoOp.getVarPtrType();
9413
9414 // Check #1: Check that the type of the private variable matches
9415 // the type of the variable being mapped.
9416 if (!isa<LLVM::LLVMPointerType>(privVar.getType()))
9417 assert(
9418 varType == privVar.getType() &&
9419 "Type of private var doesn't match the type of the mapped value");
9420
9421 // Ok, only 1 sanity check for now.
9422 // Record the block argument corresponding to this mapvar.
9423 mappedPrivateVars.insert(
9424 {privVar,
9425 targetRegion.getArgument(argIface.getMapBlockArgsStart() +
9426 (*privateMapIndices)[privVarIdx])});
9427 }
9428 }
9429
9430 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
9431 auto bodyCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
9432 ArrayRef<llvm::BasicBlock *> deallocBlocks)
9433 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
9434 llvm::IRBuilderBase::InsertPointGuard guard(builder);
9435 builder.SetCurrentDebugLocation(llvm::DebugLoc());
9436 // Forward target-cpu and target-features function attributes from the
9437 // original function to the new outlined function.
9438 llvm::Function *llvmParentFn =
9439 moduleTranslation.lookupFunction(parentFn.getName());
9440 llvmOutlinedFn = codeGenIP.getBlock()->getParent();
9441 assert(llvmParentFn && llvmOutlinedFn &&
9442 "Both parent and outlined functions must exist at this point");
9443
9444 if (outlinedFnLoc && llvmParentFn->getSubprogram())
9445 llvmOutlinedFn->setSubprogram(outlinedFnLoc->getScope()->getSubprogram());
9446
9447 if (auto attr = llvmParentFn->getFnAttribute("target-cpu");
9448 attr.isStringAttribute())
9449 llvmOutlinedFn->addFnAttr(attr);
9450
9451 if (auto attr = llvmParentFn->getFnAttribute("target-features");
9452 attr.isStringAttribute())
9453 llvmOutlinedFn->addFnAttr(attr);
9454
9455 for (auto [idx, arg] : llvm::enumerate(mapBlockArgs)) {
9456 // in_reduction list items on omp.target are accessed through their
9457 // map_entries block argument, which is redirected below to the per-task
9458 // reduction-private storage returned by the runtime. Skip the default
9459 // host-value mapping for those block arguments so the write-once
9460 // mapValue mapping is free to be set to the private pointer.
9461 if (llvm::is_contained(inRedMapArgIdx, idx))
9462 continue;
9463 auto mapInfoOp = cast<omp::MapInfoOp>(mapVars[idx].getDefiningOp());
9464 llvm::Value *mapOpValue =
9465 moduleTranslation.lookupValue(mapInfoOp.getVarPtr());
9466 moduleTranslation.mapValue(arg, mapOpValue);
9467 }
9468 for (auto [arg, mapOp] : llvm::zip_equal(hdaBlockArgs, hdaVars)) {
9469 auto mapInfoOp = cast<omp::MapInfoOp>(mapOp.getDefiningOp());
9470 llvm::Value *mapOpValue =
9471 moduleTranslation.lookupValue(mapInfoOp.getVarPtr());
9472 moduleTranslation.mapValue(arg, mapOpValue);
9473 }
9474
9475 // Do privatization after moduleTranslation has already recorded
9476 // mapped values.
9477 PrivateVarsInfo privateVarsInfo(targetOp);
9478
9480 allocatePrivateVars(targetOp, builder, moduleTranslation,
9481 privateVarsInfo, allocaIP, &mappedPrivateVars);
9482
9483 if (failed(handleError(afterAllocas, *targetOp)))
9484 return llvm::make_error<PreviouslyReportedError>();
9485
9486 builder.restoreIP(codeGenIP);
9487 if (handleError(initPrivateVars(builder, moduleTranslation, privateVarsInfo,
9488 &mappedPrivateVars),
9489 *targetOp)
9490 .failed())
9491 return llvm::make_error<PreviouslyReportedError>();
9492
9493 if (failed(copyFirstPrivateVars(
9494 targetOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
9495 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
9496 targetOp.getPrivateNeedsBarrier(), &mappedPrivateVars)))
9497 return llvm::make_error<PreviouslyReportedError>();
9498
9499 // The target body accesses each in_reduction variable through its
9500 // map_entries block argument. Redirect that block argument to the per-task
9501 // private storage returned by __kmpc_task_reduction_get_th_data so the body
9502 // accumulates into the reduction-private copy rather than the mapped
9503 // original. The lookup must run inside the target task body so the gtid
9504 // corresponds to the executing thread. The descriptor argument is NULL: the
9505 // runtime walks enclosing taskgroups to locate the matching task_reduction
9506 // registration for `origPtr`. Mirrors the in_reduction handling on
9507 // omp.taskloop.context.
9508 if (!inRedOrigPtrs.empty()) {
9509 // Collect, per item, the type the private pointer must have (the map
9510 // block argument's type), and, through the callback, rebind the map block
9511 // argument that stands in for each in_reduction list item to the per-task
9512 // reduction-private storage the runtime returns.
9513 SmallVector<llvm::Type *> inRedResultPtrTys;
9514 inRedResultPtrTys.reserve(inRedMapArgIdx.size());
9515 for (unsigned mapArgIdx : inRedMapArgIdx)
9516 inRedResultPtrTys.push_back(
9517 moduleTranslation.convertType(mapBlockArgs[mapArgIdx].getType()));
9518
9519 llvm::OpenMPIRBuilder::LocationDescription bodyLoc(builder);
9520 llvm::OpenMPIRBuilder::InsertPointTy redIP =
9521 ompBuilder->createTargetInReduction(
9522 bodyLoc, inRedOrigPtrs, inRedResultPtrTys,
9523 [&](unsigned idx, llvm::Value *priv) {
9524 moduleTranslation.mapValue(mapBlockArgs[inRedMapArgIdx[idx]],
9525 priv);
9526 });
9527 builder.restoreIP(redIP);
9528 }
9529
9531 moduleTranslation, allocaIP, deallocBlocks);
9533 targetRegion, "omp.target", builder, moduleTranslation);
9534
9535 if (failed(handleError(exitBlock, *targetOp)))
9536 return llvm::make_error<PreviouslyReportedError>();
9537
9538 builder.SetInsertPoint(exitBlock.get()->getTerminator());
9539
9540 if (failed(cleanupPrivateVars(targetOp, builder, moduleTranslation,
9541 targetOp.getLoc(), privateVarsInfo)))
9542 return llvm::make_error<PreviouslyReportedError>();
9543
9544 return builder.saveIP();
9545 };
9546
9547 StringRef parentName = parentFn.getName();
9548
9549 llvm::TargetRegionEntryInfo entryInfo;
9550
9551 getTargetEntryUniqueInfo(entryInfo, targetOp,
9552 *moduleTranslation.getOpenMPBuilder(),
9553 moduleTranslation.getFileSystem(), parentName);
9554
9555 MapInfoData mapData;
9556 collectMapDataFromMapOperands(mapData, mapVars, moduleTranslation, dl,
9557 builder, /*useDevPtrOperands=*/{},
9558 /*useDevAddrOperands=*/{}, hdaVars);
9559
9560 MapInfosTy combinedInfos;
9561 auto genMapInfoCB =
9562 [&](llvm::OpenMPIRBuilder::InsertPointTy codeGenIP) -> MapInfosTy & {
9563 builder.restoreIP(codeGenIP);
9564 genMapInfos(builder, moduleTranslation, dl, combinedInfos, mapData,
9565 targetDirective);
9566
9567 // Append a null entry for the implicit dyn_ptr argument so the argument
9568 // count sent to the runtime already includes it.
9569 auto *nullPtr = llvm::Constant::getNullValue(builder.getPtrTy());
9570 combinedInfos.BasePointers.push_back(nullPtr);
9571 combinedInfos.Pointers.push_back(nullPtr);
9572 combinedInfos.DevicePointers.push_back(
9573 llvm::OpenMPIRBuilder::DeviceInfoTy::None);
9574 combinedInfos.Sizes.push_back(builder.getInt64(0));
9575 combinedInfos.Types.push_back(
9576 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TARGET_PARAM |
9577 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL);
9578 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
9579 combinedInfos.HasAttachPtr.push_back(false);
9580 if (!combinedInfos.Names.empty())
9581 combinedInfos.Names.push_back(nullPtr);
9582 combinedInfos.Mappers.push_back(nullptr);
9583
9584 return combinedInfos;
9585 };
9586
9587 auto argAccessorCB = [&](llvm::Argument &arg, llvm::Value *input,
9588 llvm::Value *&retVal, InsertPointTy allocaIP,
9589 InsertPointTy codeGenIP,
9591 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
9592 llvm::IRBuilderBase::InsertPointGuard guard(builder);
9593 builder.SetCurrentDebugLocation(llvm::DebugLoc());
9594 // We just return the unaltered argument for the host function
9595 // for now, some alterations may be required in the future to
9596 // keep host fallback functions working identically to the device
9597 // version (e.g. pass ByCopy values should be treated as such on
9598 // host and device, currently not always the case)
9599 if (!isTargetDevice) {
9600 retVal = cast<llvm::Value>(&arg);
9601 return codeGenIP;
9602 }
9603
9604 return createDeviceArgumentAccessor(targetOp, mapData, arg, input, retVal,
9605 builder, *ompBuilder, moduleTranslation,
9606 allocaIP, codeGenIP, deallocIPs);
9607 };
9608
9609 llvm::OpenMPIRBuilder::TargetKernelRuntimeAttrs runtimeAttrs;
9610 llvm::OpenMPIRBuilder::TargetKernelDefaultAttrs defaultAttrs;
9611 Operation *targetCapturedOp =
9612 cast<omp::ComposableOpInterface>(*targetOp).findCapturedOp();
9613 initTargetDefaultAttrs(targetOp, targetCapturedOp, defaultAttrs,
9614 isTargetDevice, isGPU);
9615
9616 // Collect host-evaluated values needed to properly launch the kernel from the
9617 // host.
9618 if (!isTargetDevice)
9619 initTargetRuntimeAttrs(builder, moduleTranslation, targetOp,
9620 targetCapturedOp, runtimeAttrs);
9621
9622 // Pass host-evaluated values as parameters to the kernel / host fallback,
9623 // except if they are constants. In any case, map the MLIR block argument to
9624 // the corresponding LLVM values.
9626 SmallVector<Value> hostEvalVars = targetOp.getHostEvalVars();
9627 ArrayRef<BlockArgument> hostEvalBlockArgs = argIface.getHostEvalBlockArgs();
9628 for (auto [arg, var] : llvm::zip_equal(hostEvalBlockArgs, hostEvalVars)) {
9629 llvm::Value *value = moduleTranslation.lookupValue(var);
9630 moduleTranslation.mapValue(arg, value);
9631
9632 if (!llvm::isa<llvm::Constant>(value))
9633 kernelInput.push_back(value);
9634 }
9635
9636 for (size_t i = 0, e = mapData.OriginalValue.size(); i != e; ++i) {
9637 // 1) Declare target arguments are not passed to kernels as arguments.
9638 // 2) Attach maps are not passed in as arguments to kernels.
9639 // 3) Children of record objects are not passed in as arguments.
9640 // TODO: We currently do not handle cases where a member is explicitly
9641 // passed in as an argument, this will likley need to be handled in
9642 // the near future, rather than using IsAMember, it may be better to
9643 // test if the relevant BlockArg is used within the target region and
9644 // then use that as a basis for exclusion in the kernel inputs.
9645 bool isAttachMap = (mapData.Types[i] &
9646 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) ==
9647 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH;
9648 if (!mapData.IsDeclareTarget[i] && !mapData.IsAMember[i] && !isAttachMap)
9649 kernelInput.push_back(mapData.OriginalValue[i]);
9650 }
9651
9653 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
9654 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
9655
9656 llvm::OpenMPIRBuilder::DependenciesInfo dds;
9657 if (failed(buildDependData(
9658 targetOp.getDependVars(), targetOp.getDependKinds(),
9659 targetOp.getDependIterated(), targetOp.getDependIteratedKinds(),
9660 builder, moduleTranslation, dds)))
9661 return failure();
9662
9663 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
9664
9665 llvm::OpenMPIRBuilder::TargetDataInfo info(
9666 /*RequiresDevicePointerInfo=*/false,
9667 /*SeparateBeginEndCalls=*/true);
9668
9669 auto customMapperCB =
9670 [&](unsigned int i) -> llvm::Expected<llvm::Function *> {
9671 if (!combinedInfos.Mappers[i])
9672 return nullptr;
9673 info.HasMapper = true;
9674 return getOrCreateUserDefinedMapperFunc(combinedInfos.Mappers[i], builder,
9675 moduleTranslation, targetDirective);
9676 };
9677
9678 llvm::Value *ifCond = nullptr;
9679 if (Value targetIfCond = targetOp.getIfExpr())
9680 ifCond = moduleTranslation.lookupValue(targetIfCond);
9681
9682 Value dynGroupPrivateSize = targetOp.getDynGroupprivateSize();
9683 llvm::Value *dynSizeVal = nullptr;
9684 if (dynGroupPrivateSize) {
9685 dynSizeVal = moduleTranslation.lookupValue(dynGroupPrivateSize);
9686 dynSizeVal = builder.CreateIntCast(dynSizeVal, builder.getInt32Ty(),
9687 /*isSigned=*/false);
9688 }
9689
9690 llvm::omp::OMPDynGroupprivateFallbackType fallbackType =
9691 getDynGroupprivateFallbackType(targetOp.getDynGroupprivateFallbackAttr());
9692
9693 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
9694 moduleTranslation.getOpenMPBuilder()->createTarget(
9695 ompLoc, isOffloadEntry, allocaIP, builder.saveIP(), deallocBlocks,
9696 info, entryInfo, defaultAttrs, runtimeAttrs, ifCond, kernelInput,
9697 genMapInfoCB, bodyCB, argAccessorCB, customMapperCB, dds,
9698 targetOp.getNowait(), dynSizeVal, fallbackType);
9699
9700 if (failed(handleError(afterIP, opInst)))
9701 return failure();
9702
9703 builder.restoreIP(*afterIP);
9704
9705 if (dds.DepArray)
9706 builder.CreateFree(dds.DepArray);
9707
9708 return success();
9709}
9710
9711static LogicalResult
9712convertDeclareTargetAttr(Operation *op, mlir::omp::DeclareTargetAttr attribute,
9713 llvm::OpenMPIRBuilder *ompBuilder,
9714 LLVM::ModuleTranslation &moduleTranslation) {
9715 // Amend omp.declare_target by deleting the IR of the outlined functions
9716 // created for target regions. They cannot be filtered out from MLIR earlier
9717 // because the omp.target operation inside must be translated to LLVM, but
9718 // the wrapper functions themselves must not remain at the end of the
9719 // process. We know that functions where omp.declare_target does not match
9720 // omp.is_target_device at this stage can only be wrapper functions because
9721 // those that aren't are removed earlier as an MLIR transformation pass.
9722 if (FunctionOpInterface funcOp = dyn_cast<FunctionOpInterface>(op)) {
9723 if (auto offloadMod = dyn_cast<omp::OffloadModuleInterface>(
9724 op->getParentOfType<ModuleOp>().getOperation())) {
9725 if (!offloadMod.getIsTargetDevice())
9726 return success();
9727
9728 omp::DeclareTargetDeviceType declareType =
9729 attribute.getDeviceType().getValue();
9730
9731 if (declareType == omp::DeclareTargetDeviceType::host) {
9732 llvm::Function *llvmFunc =
9733 moduleTranslation.lookupFunction(funcOp.getName());
9734 llvmFunc->dropAllReferences();
9735 llvmFunc->eraseFromParent();
9736
9737 // Invalidate the builder's current insertion point, as it now points to
9738 // a deleted block.
9739 ompBuilder->Builder.ClearInsertionPoint();
9740 ompBuilder->Builder.SetCurrentDebugLocation(llvm::DebugLoc());
9741 } else if (llvm::Function *llvmFunc =
9742 moduleTranslation.lookupFunction(funcOp.getName())) {
9743 // Device-side declare target functions are externally visible by
9744 // default so they can be referenced from other device translation
9745 // units. That also prevents the offload LTO from internalizing and
9746 // deleting them when they end up unused in the final device image.
9747 // Such dead functions can still reference internal LDS and trigger
9748 // spurious "local memory global used by non-kernel function" backend
9749 // warnings. Marking them hidden keeps the symbol usable within the
9750 // device image's linkage unit while letting LTO drop it when nothing
9751 // references it; symbols that must stay reachable (e.g. via an offload
9752 // entry that takes their address) are kept alive by that reference.
9753 if (!llvmFunc->isDeclaration() && llvmFunc->hasExternalLinkage() &&
9754 llvmFunc->getVisibility() == llvm::GlobalValue::DefaultVisibility)
9755 llvmFunc->setVisibility(llvm::GlobalValue::HiddenVisibility);
9756 }
9757 }
9758 return success();
9759 }
9760
9761 if (LLVM::GlobalOp gOp = dyn_cast<LLVM::GlobalOp>(op)) {
9762 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
9763 if (auto *gVal = llvmModule->getNamedValue(gOp.getSymName())) {
9764 auto *gVar = cast<llvm::GlobalVariable>(gVal);
9765 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9766 bool isDeclaration = gOp.isDeclaration();
9767 bool isExternallyVisible =
9768 gOp.getVisibility() != mlir::SymbolTable::Visibility::Private;
9769 auto loc = op->getLoc()->findInstanceOf<FileLineColLoc>();
9770 llvm::StringRef mangledName = gOp.getSymName();
9771 mlir::omp::DeclareTargetCaptureClause captureClause =
9772 attribute.getCaptureClause().getValue();
9773 auto captureClauseKind = convertToCaptureClauseKind(captureClause);
9774 auto deviceClause =
9775 convertToDeviceClauseKind(attribute.getDeviceType().getValue());
9776 llvm::StringRef entryMangledName = mangledName;
9777 llvm::Constant *entryAddr = llvm::cast<llvm::Constant>(gVal);
9778 std::function<llvm::GlobalValue::LinkageTypes()> variableLinkage;
9779 llvm::SmallString<128> entryNameStorage;
9780 bool requiresUSM = ompBuilder->Config.hasRequiresUnifiedSharedMemory();
9781 bool isToOrEnter =
9782 captureClause == omp::DeclareTargetCaptureClause::to ||
9783 captureClause == omp::DeclareTargetCaptureClause::enter;
9784 bool isHostOnly = attribute.getDeviceType().getValue() ==
9785 omp::DeclareTargetDeviceType::host;
9786
9787 // A to/enter declare-target variable needs a device-resident,
9788 // name-resolvable copy and a host offloading entry. A local-linkage
9789 // global provides neither, so we promote it to external.
9790 if (isToOrEnter && !isHostOnly && !requiresUSM &&
9791 gVar->hasLocalLinkage()) {
9792 gVar->setLinkage(llvm::GlobalValue::ExternalLinkage);
9793 isExternallyVisible = true;
9794
9795 // Clear the stale dso_local flag so it is referenced like a
9796 // module-scope declare target global.
9797 if (ompBuilder->Config.isTargetDevice())
9798 gVar->setDSOLocal(false);
9799 }
9800
9801 if (isToOrEnter &&
9802 deviceClause ==
9803 llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseAny &&
9804 !requiresUSM && !isDeclaration &&
9805 (gVal->hasLocalLinkage() || gVal->hasHiddenVisibility())) {
9806 // Keep the original symbol as-is for target code, but create a visible
9807 // alias for the offload entry so libomptarget can associate the host
9808 // global with the actual device global.
9809 entryNameStorage = (mangledName + llvm::Twine("_decl_tgt_entry")).str();
9810 entryMangledName = entryNameStorage;
9811 if (llvm::GlobalValue *existing =
9812 llvmModule->getNamedValue(entryMangledName)) {
9813 entryAddr = llvm::cast<llvm::Constant>(existing);
9814 } else {
9815 entryAddr = llvm::GlobalAlias::create(
9816 gVal->getValueType(), gVal->getAddressSpace(),
9817 llvm::GlobalValue::WeakAnyLinkage, entryMangledName, entryAddr,
9818 llvmModule);
9819 llvm::cast<llvm::GlobalAlias>(entryAddr)->setVisibility(
9820 llvm::GlobalValue::DefaultVisibility);
9821 }
9822 variableLinkage = [] { return llvm::GlobalValue::WeakAnyLinkage; };
9823 }
9824 // unused for MLIR at the moment, required in Clang for book
9825 // keeping
9826 std::vector<llvm::GlobalVariable *> generatedRefs;
9827
9828 std::vector<llvm::Triple> targetTriple;
9829 auto targetTripleAttr = dyn_cast_or_null<mlir::StringAttr>(
9830 op->getParentOfType<mlir::ModuleOp>()->getAttr(
9831 LLVM::LLVMDialect::getTargetTripleAttrName()));
9832 if (targetTripleAttr)
9833 targetTriple.emplace_back(targetTripleAttr.data());
9834
9835 auto fileInfoCallBack = [&loc]() {
9836 std::string filename = "";
9837 std::uint64_t lineNo = 0;
9838
9839 if (loc) {
9840 filename = loc.getFilename().str();
9841 lineNo = loc.getLine();
9842 }
9843
9844 return std::pair<std::string, std::uint64_t>(llvm::StringRef(filename),
9845 lineNo);
9846 };
9847
9848 llvm::vfs::FileSystem &vfs = moduleTranslation.getFileSystem();
9849 ompBuilder->registerTargetGlobalVariable(
9850 captureClauseKind, deviceClause, isDeclaration, isExternallyVisible,
9851 ompBuilder->getTargetEntryUniqueInfo(fileInfoCallBack, vfs),
9852 entryMangledName, generatedRefs, /*OpenMPSimd*/ false, targetTriple,
9853 /*GlobalInitializer*/ nullptr, variableLinkage, gVal->getType(),
9854 entryAddr);
9855
9856 if (ompBuilder->Config.isTargetDevice() &&
9857 (captureClause == omp::DeclareTargetCaptureClause::link ||
9858 requiresUSM)) {
9859 // For USM and link we generate a global reference pointer in the
9860 // default address space (e.g address space 0), as opposed to the
9861 // globals original type and address space.
9862 llvm::Type *ptrTy = llvm::PointerType::get(llvmModule->getContext(), 0);
9863 llvm::Constant *refPtr = ompBuilder->getAddrOfDeclareTargetVar(
9864 captureClauseKind, deviceClause, isDeclaration, isExternallyVisible,
9865 ompBuilder->getTargetEntryUniqueInfo(fileInfoCallBack, vfs),
9866 mangledName, generatedRefs, /*OpenMPSimd*/ false, targetTriple,
9867 ptrTy, /*GlobalInitializer*/ nullptr,
9868 /*VariableLinkage*/ nullptr);
9869
9870 // For indirectly-accessed global pointers, we rely on "internal"
9871 // linkage to optimize out the unneeded full-variable storage later,
9872 // since we can't prevent the LLVM dialect from generating globals
9873 // without also breaking target lowering.
9874 if (refPtr) {
9875 gVar->setLinkage(llvm::GlobalValue::InternalLinkage);
9876
9877 // Register the (original global, reference pointer) pair so that the
9878 // OpenMPIRBuilder can rewrite uses of the original global during
9879 // finalization.
9880 if (auto *newGV =
9881 dyn_cast<llvm::GlobalValue>(refPtr->stripPointerCasts()))
9882 ompBuilder->registerDeclareTargetGlobalReplacement(gVal, newGV);
9883 }
9884 }
9885
9886 // Mark 'device_type(host) enter(...)' variables as external in the device
9887 // since they're not supposed to have their own copy. This will cause
9888 // linker errors if accesses are attempted from the target device.
9889 if (ompBuilder->Config.isTargetDevice() && isHostOnly && isToOrEnter) {
9890 gVar->setLinkage(llvm::GlobalValue::ExternalLinkage);
9891 gVar->setInitializer(nullptr);
9892 }
9893 }
9894 }
9895
9896 return success();
9897}
9898
9899namespace {
9900
9901/// Implementation of the dialect interface that converts operations belonging
9902/// to the OpenMP dialect to LLVM IR.
9903class OpenMPDialectLLVMIRTranslationInterface
9904 : public LLVMTranslationDialectInterface {
9905public:
9906 using LLVMTranslationDialectInterface::LLVMTranslationDialectInterface;
9907
9908 /// Translates the given operation to LLVM IR using the provided IR builder
9909 /// and saving the state in `moduleTranslation`.
9910 LogicalResult
9911 convertOperation(Operation *op, llvm::IRBuilderBase &builder,
9912 LLVM::ModuleTranslation &moduleTranslation) const final;
9913
9914 /// Given an OpenMP MLIR attribute, create the corresponding LLVM-IR,
9915 /// runtime calls, or operation amendments
9916 LogicalResult
9917 amendOperation(Operation *op, ArrayRef<llvm::Instruction *> instructions,
9918 NamedAttribute attribute,
9919 LLVM::ModuleTranslation &moduleTranslation) const final;
9920
9921 /// Records the LLVM alloc pointer produced for an OMP ALLOCATE variable so
9922 /// that the paired omp.allocate_free op can generate the matching
9923 /// __kmpc_free call.
9924 void registerAllocatedPtr(Value var, llvm::Value *ptr) const {
9925 ompAllocatedPtrs[var] = ptr;
9926 }
9927
9928 /// Returns the LLVM alloc pointer previously registered for var, or
9929 /// nullptr if no allocation was recorded.
9930 llvm::Value *lookupAllocatedPtr(Value var) const {
9931 auto it = ompAllocatedPtrs.find(var);
9932 return it != ompAllocatedPtrs.end() ? it->second : nullptr;
9933 }
9934
9935private:
9936 /// Maps each MLIR variable value that appeared in an omp.allocate_dir op to
9937 /// the LLVM pointer returned by the corresponding __kmpc_alloc call. The
9938 /// paired omp.allocate_free op looks up these pointers to emit __kmpc_free.
9939 mutable DenseMap<Value, llvm::Value *> ompAllocatedPtrs;
9940};
9941
9942} // namespace
9943
9944LogicalResult OpenMPDialectLLVMIRTranslationInterface::amendOperation(
9945 Operation *op, ArrayRef<llvm::Instruction *> instructions,
9946 NamedAttribute attribute,
9947 LLVM::ModuleTranslation &moduleTranslation) const {
9948 return llvm::StringSwitch<llvm::function_ref<LogicalResult(Attribute)>>(
9949 attribute.getName())
9950 .Case("omp.is_target_device",
9951 [&](Attribute attr) {
9952 if (auto deviceAttr = dyn_cast<BoolAttr>(attr)) {
9953 llvm::OpenMPIRBuilderConfig &config =
9954 moduleTranslation.getOpenMPBuilder()->Config;
9955 config.setIsTargetDevice(deviceAttr.getValue());
9956 return success();
9957 }
9958 return failure();
9959 })
9960 .Case("omp.is_gpu",
9961 [&](Attribute attr) {
9962 if (auto gpuAttr = dyn_cast<BoolAttr>(attr)) {
9963 llvm::OpenMPIRBuilderConfig &config =
9964 moduleTranslation.getOpenMPBuilder()->Config;
9965 config.setIsGPU(gpuAttr.getValue());
9966 return success();
9967 }
9968 return failure();
9969 })
9970 .Case("omp.host_ir_filepath",
9971 [&](Attribute attr) {
9972 if (auto filepathAttr = dyn_cast<StringAttr>(attr)) {
9973 llvm::OpenMPIRBuilder *ompBuilder =
9974 moduleTranslation.getOpenMPBuilder();
9975 ompBuilder->loadOffloadInfoMetadata(
9976 moduleTranslation.getFileSystem(), filepathAttr.getValue());
9977 return success();
9978 }
9979 return failure();
9980 })
9981 .Case("omp.flags",
9982 [&](Attribute attr) {
9983 if (auto rtlAttr = dyn_cast<omp::FlagsAttr>(attr))
9984 return convertFlagsAttr(op, rtlAttr, moduleTranslation);
9985 return failure();
9986 })
9987 .Case("omp.version",
9988 [&](Attribute attr) {
9989 if (auto versionAttr = dyn_cast<omp::VersionAttr>(attr)) {
9990 llvm::OpenMPIRBuilder *ompBuilder =
9991 moduleTranslation.getOpenMPBuilder();
9992 ompBuilder->M.addModuleFlag(llvm::Module::Max, "openmp",
9993 versionAttr.getVersion());
9994 return success();
9995 }
9996 return failure();
9997 })
9998 .Case("omp.declare_target",
9999 [&](Attribute attr) {
10000 if (auto declareTargetAttr =
10001 dyn_cast<omp::DeclareTargetAttr>(attr)) {
10002 llvm::OpenMPIRBuilder *ompBuilder =
10003 moduleTranslation.getOpenMPBuilder();
10004 return convertDeclareTargetAttr(op, declareTargetAttr,
10005 ompBuilder, moduleTranslation);
10006 }
10007 return failure();
10008 })
10009 .Case("omp.requires",
10010 [&](Attribute attr) {
10011 if (auto requiresAttr = dyn_cast<omp::ClauseRequiresAttr>(attr)) {
10012 using Requires = omp::ClauseRequires;
10013 Requires flags = requiresAttr.getValue();
10014 llvm::OpenMPIRBuilderConfig &config =
10015 moduleTranslation.getOpenMPBuilder()->Config;
10016 config.setHasRequiresReverseOffload(
10017 bitEnumContainsAll(flags, Requires::reverse_offload));
10018 config.setHasRequiresUnifiedAddress(
10019 bitEnumContainsAll(flags, Requires::unified_address));
10020 config.setHasRequiresUnifiedSharedMemory(
10021 bitEnumContainsAll(flags, Requires::unified_shared_memory));
10022 config.setHasRequiresDynamicAllocators(
10023 bitEnumContainsAll(flags, Requires::dynamic_allocators));
10024 return success();
10025 }
10026 return failure();
10027 })
10028 .Case("omp.target_triples",
10029 [&](Attribute attr) {
10030 if (auto triplesAttr = dyn_cast<ArrayAttr>(attr)) {
10031 llvm::OpenMPIRBuilderConfig &config =
10032 moduleTranslation.getOpenMPBuilder()->Config;
10033 config.TargetTriples.clear();
10034 config.TargetTriples.reserve(triplesAttr.size());
10035 for (Attribute tripleAttr : triplesAttr) {
10036 if (auto tripleStrAttr = dyn_cast<StringAttr>(tripleAttr))
10037 config.TargetTriples.emplace_back(tripleStrAttr.getValue());
10038 else
10039 return failure();
10040 }
10041 return success();
10042 }
10043 return failure();
10044 })
10045 .Case("omp.integer_wrap_around",
10046 [&](Attribute attr) {
10047 if (auto wrapAttr = dyn_cast<omp::IntegerWrapAroundAttr>(attr)) {
10048 llvm::OpenMPIRBuilderConfig &config =
10049 moduleTranslation.getOpenMPBuilder()->Config;
10050 config.setNoSignedWrap(!wrapAttr.getIntegerWrapAround());
10051 return success();
10052 }
10053 return failure();
10054 })
10055 .Default([](Attribute) {
10056 // Fall through for omp attributes that do not require lowering.
10057 return success();
10058 })(attribute.getValue());
10059
10060 return failure();
10061}
10062
10063// Returns true if the operation is not inside a TargetOp, it is part of a
10064// function and that function is not declare target.
10065static bool isHostDeviceOp(Operation *op) {
10066 // Assumes no reverse offloading
10067 if (op->getParentOfType<omp::TargetOp>())
10068 return false;
10069
10070 if (auto parentFn = op->getParentOfType<LLVM::LLVMFuncOp>()) {
10071 if (auto declareTargetIface =
10072 llvm::dyn_cast<mlir::omp::DeclareTargetInterface>(
10073 parentFn.getOperation()))
10074 if (declareTargetIface.isDeclareTarget() &&
10075 declareTargetIface.getDeclareTargetDeviceType() !=
10076 mlir::omp::DeclareTargetDeviceType::host)
10077 return false;
10078
10079 return true;
10080 }
10081
10082 return false;
10083}
10084
10085static llvm::Function *getOmpTargetAlloc(llvm::IRBuilderBase &builder,
10086 llvm::Module *llvmModule) {
10087 llvm::Type *i64Ty = builder.getInt64Ty();
10088 llvm::Type *i32Ty = builder.getInt32Ty();
10089 llvm::Type *returnType = builder.getPtrTy(0);
10090 llvm::FunctionType *fnType =
10091 llvm::FunctionType::get(returnType, {i64Ty, i32Ty}, false);
10092 llvm::Function *func = cast<llvm::Function>(
10093 llvmModule->getOrInsertFunction("omp_target_alloc", fnType).getCallee());
10094 return func;
10095}
10096
10097template <typename T>
10098static llvm::Value *
10099getAllocationSize(llvm::IRBuilderBase &builder,
10100 LLVM::ModuleTranslation &moduleTranslation, T op) {
10101 llvm::DataLayout dataLayout =
10102 moduleTranslation.getLLVMModule()->getDataLayout();
10103 llvm::Type *llvmHeapTy =
10104 moduleTranslation.convertType(op.getMemElemTypeAttr().getValue());
10105
10106 auto alignment = op.getMemAlignment();
10107 llvm::TypeSize typeSize = llvm::alignTo(
10108 dataLayout.getTypeStoreSize(llvmHeapTy),
10109 alignment ? *alignment : dataLayout.getABITypeAlign(llvmHeapTy).value());
10110
10111 llvm::Value *allocSize = builder.getInt64(typeSize.getFixedValue());
10112 return builder.CreateMul(
10113 allocSize,
10114 builder.CreateIntCast(moduleTranslation.lookupValue(op.getMemArraySize()),
10115 builder.getInt64Ty(),
10116 /*isSigned=*/false));
10117}
10118
10119template <>
10120llvm::Value *getAllocationSize(llvm::IRBuilderBase &builder,
10121 LLVM::ModuleTranslation &moduleTranslation,
10122 omp::TargetAllocMemOp op) {
10123 llvm::DataLayout dataLayout =
10124 moduleTranslation.getLLVMModule()->getDataLayout();
10125 llvm::Type *llvmHeapTy = moduleTranslation.convertType(op.getAllocatedType());
10126 llvm::TypeSize typeSize = dataLayout.getTypeAllocSize(llvmHeapTy);
10127 llvm::Value *allocSize = builder.getInt64(typeSize.getFixedValue());
10128 for (auto typeParam : op.getTypeparams()) {
10129 allocSize = builder.CreateMul(
10130 allocSize,
10131 builder.CreateIntCast(moduleTranslation.lookupValue(typeParam),
10132 builder.getInt64Ty(),
10133 /*isSigned=*/false));
10134 }
10135 return allocSize;
10136}
10137
10138static LogicalResult
10139convertTargetAllocMemOp(Operation &opInst, llvm::IRBuilderBase &builder,
10140 LLVM::ModuleTranslation &moduleTranslation) {
10141 auto allocMemOp = cast<omp::TargetAllocMemOp>(opInst);
10142 if (!allocMemOp)
10143 return failure();
10144
10145 // Get "omp_target_alloc" function
10146 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
10147 llvm::Function *ompTargetAllocFunc = getOmpTargetAlloc(builder, llvmModule);
10148 // Get the corresponding device value in llvm
10149 mlir::Value deviceNum = allocMemOp.getDevice();
10150 llvm::Value *llvmDeviceNum = moduleTranslation.lookupValue(deviceNum);
10151 // Get the allocation size.
10152 llvm::Value *allocSize =
10153 getAllocationSize(builder, moduleTranslation, allocMemOp);
10154 // Create call to "omp_target_alloc" with the args as translated llvm values.
10155 llvm::CallInst *call =
10156 builder.CreateCall(ompTargetAllocFunc, {allocSize, llvmDeviceNum});
10157 llvm::Value *resultI64 = builder.CreatePtrToInt(call, builder.getInt64Ty());
10158
10159 // Map the result
10160 moduleTranslation.mapValue(allocMemOp.getResult(), resultI64);
10161 return success();
10162}
10163
10164static LogicalResult
10165convertAllocSharedMemOp(omp::AllocSharedMemOp allocMemOp,
10166 llvm::IRBuilderBase &builder,
10167 LLVM::ModuleTranslation &moduleTranslation) {
10168 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
10169 llvm::Value *size = getAllocationSize(builder, moduleTranslation, allocMemOp);
10170 moduleTranslation.mapValue(allocMemOp.getResult(),
10171 ompBuilder->createOMPAllocShared(builder, size));
10172 return success();
10173}
10174
10175static LogicalResult
10176convertAllocateDirOp(Operation &opInst, llvm::IRBuilderBase &builder,
10177 LLVM::ModuleTranslation &moduleTranslation,
10178 const OpenMPDialectLLVMIRTranslationInterface &ompIface) {
10179 auto allocateDirOp = cast<omp::AllocateDirOp>(opInst);
10180 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
10181
10182 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
10183 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
10184 llvm::DataLayout dataLayout = llvmModule->getDataLayout();
10185 SmallVector<Value> vars = allocateDirOp.getVarList();
10186 std::optional<int64_t> alignAttr = allocateDirOp.getAlign();
10187
10188 llvm::Value *allocator;
10189 if (auto allocatorVar = allocateDirOp.getAllocator()) {
10190 allocator = moduleTranslation.lookupValue(allocatorVar);
10191 if (allocator->getType()->isIntegerTy())
10192 allocator = builder.CreateIntToPtr(allocator, builder.getPtrTy());
10193 else if (allocator->getType()->isPointerTy())
10194 allocator = builder.CreatePointerBitCastOrAddrSpaceCast(
10195 allocator, builder.getPtrTy());
10196 } else {
10197 allocator = llvm::ConstantPointerNull::get(builder.getPtrTy());
10198 }
10199
10200 for (Value var : vars) {
10201 llvm::Type *llvmVarTy = moduleTranslation.convertType(var.getType());
10202
10203 // Opaque pointers lose element type. Trace to GlobalOp for type
10204 // Falls back to llvmVarTy when not from a global.
10205 llvm::Type *typeToInspect = llvmVarTy;
10206 if (llvmVarTy->isPointerTy()) {
10207 Value baseVar = getBaseValueForTypeLookup(var);
10208 if (Operation *globalOp = getGlobalOpFromValue(baseVar)) {
10209 if (auto gop = dyn_cast<LLVM::GlobalOp>(globalOp))
10210 typeToInspect = moduleTranslation.convertType(gop.getGlobalType());
10211 }
10212 }
10213
10214 llvm::Value *size;
10215 if (auto arrTy = llvm::dyn_cast<llvm::ArrayType>(typeToInspect)) {
10216 llvm::Value *elementCount = builder.getInt64(1);
10217 llvm::Type *currentType = arrTy;
10218 while (auto nestedArrTy = llvm::dyn_cast<llvm::ArrayType>(currentType)) {
10219 elementCount = builder.CreateMul(
10220 elementCount, builder.getInt64(nestedArrTy->getNumElements()));
10221 currentType = nestedArrTy->getElementType();
10222 }
10223 uint64_t elemSizeInBits = dataLayout.getTypeSizeInBits(currentType);
10224 size =
10225 builder.CreateMul(elementCount, builder.getInt64(elemSizeInBits / 8));
10226 } else {
10227 size = builder.getInt64(
10228 dataLayout.getTypeStoreSize(typeToInspect).getFixedValue());
10229 }
10230
10231 uint64_t alignValue =
10232 alignAttr ? alignAttr.value()
10233 : dataLayout.getABITypeAlign(typeToInspect).value();
10234 llvm::Value *alignConst = builder.getInt64(alignValue);
10235 // Align the size: ((size + align - 1) / align) * align
10236 size = builder.CreateAdd(size, builder.getInt64(alignValue - 1), "", true);
10237 size = builder.CreateUDiv(size, alignConst);
10238 size = builder.CreateMul(size, alignConst, "", true);
10239
10240 std::string allocName =
10241 ompBuilder->createPlatformSpecificName({".void.addr"});
10242 llvm::CallInst *allocCall;
10243 if (alignAttr.has_value()) {
10244 allocCall = ompBuilder->createOMPAlignedAlloc(
10245 ompLoc, builder.getInt64(alignAttr.value()), size, allocator,
10246 allocName);
10247 } else {
10248 allocCall =
10249 ompBuilder->createOMPAlloc(ompLoc, size, allocator, allocName);
10250 }
10251 // Record the alloc pointer keyed by the MLIR variable value.
10252 ompIface.registerAllocatedPtr(var, allocCall);
10253 }
10254
10255 return success();
10256}
10257
10258static LogicalResult
10259convertAllocateFreeOp(Operation &opInst, llvm::IRBuilderBase &builder,
10260 LLVM::ModuleTranslation &moduleTranslation,
10261 const OpenMPDialectLLVMIRTranslationInterface &ompIface) {
10262 auto freeOp = cast<omp::AllocateFreeOp>(opInst);
10263 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
10264 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
10265
10266 llvm::Value *allocator;
10267 if (auto allocatorVar = freeOp.getAllocator()) {
10268 allocator = moduleTranslation.lookupValue(allocatorVar);
10269 if (allocator->getType()->isIntegerTy())
10270 allocator = builder.CreateIntToPtr(allocator, builder.getPtrTy());
10271 else if (allocator->getType()->isPointerTy())
10272 allocator = builder.CreatePointerBitCastOrAddrSpaceCast(
10273 allocator, builder.getPtrTy());
10274 } else {
10275 allocator = llvm::ConstantPointerNull::get(builder.getPtrTy());
10276 }
10277
10278 // Emit __kmpc_free for each variable in reverse allocation order.
10279 SmallVector<Value> vars = freeOp.getVarList();
10280 for (Value var : llvm::reverse(vars)) {
10281 llvm::Value *allocPtr = ompIface.lookupAllocatedPtr(var);
10282 if (!allocPtr)
10283 return opInst.emitError("omp.allocate_free: no allocation recorded");
10284 ompBuilder->createOMPFree(ompLoc, allocPtr, allocator, "");
10285 }
10286
10287 return success();
10288}
10289
10290static llvm::Function *getOmpTargetFree(llvm::IRBuilderBase &builder,
10291 llvm::Module *llvmModule) {
10292 llvm::Type *ptrTy = builder.getPtrTy(0);
10293 llvm::Type *i32Ty = builder.getInt32Ty();
10294 llvm::Type *voidTy = builder.getVoidTy();
10295 llvm::FunctionType *fnType =
10296 llvm::FunctionType::get(voidTy, {ptrTy, i32Ty}, false);
10297 llvm::Function *func = dyn_cast<llvm::Function>(
10298 llvmModule->getOrInsertFunction("omp_target_free", fnType).getCallee());
10299 return func;
10300}
10301
10302static LogicalResult
10303convertTargetFreeMemOp(Operation &opInst, llvm::IRBuilderBase &builder,
10304 LLVM::ModuleTranslation &moduleTranslation) {
10305 auto freeMemOp = cast<omp::TargetFreeMemOp>(opInst);
10306 if (!freeMemOp)
10307 return failure();
10308
10309 // Get "omp_target_free" function
10310 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
10311 llvm::Function *ompTragetFreeFunc = getOmpTargetFree(builder, llvmModule);
10312 // Get the corresponding device value in llvm
10313 mlir::Value deviceNum = freeMemOp.getDevice();
10314 llvm::Value *llvmDeviceNum = moduleTranslation.lookupValue(deviceNum);
10315 // Get the corresponding heapref value in llvm
10316 mlir::Value heapref = freeMemOp.getHeapref();
10317 llvm::Value *llvmHeapref = moduleTranslation.lookupValue(heapref);
10318 // Convert heapref int to ptr and call "omp_target_free"
10319 llvm::Value *intToPtr =
10320 builder.CreateIntToPtr(llvmHeapref, builder.getPtrTy(0));
10321 builder.CreateCall(ompTragetFreeFunc, {intToPtr, llvmDeviceNum});
10322 return success();
10323}
10324
10325static LogicalResult
10326convertFreeSharedMemOp(omp::FreeSharedMemOp freeMemOp,
10327 llvm::IRBuilderBase &builder,
10328 LLVM::ModuleTranslation &moduleTranslation) {
10329 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
10330 llvm::Value *size = getAllocationSize(builder, moduleTranslation, freeMemOp);
10331 ompBuilder->createOMPFreeShared(
10332 builder, moduleTranslation.lookupValue(freeMemOp.getHeapref()), size);
10333 return success();
10334}
10335
10336/// Converts an OpenMP groupprivate operation into LLVM IR.
10337static LogicalResult
10338convertOmpGroupprivate(Operation &opInst, llvm::IRBuilderBase &builder,
10339 LLVM::ModuleTranslation &moduleTranslation) {
10340 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
10341 auto groupprivateOp = cast<omp::GroupprivateOp>(opInst);
10342
10343 if (failed(checkImplementationStatus(opInst)))
10344 return failure();
10345
10346 bool isTargetDevice = ompBuilder->Config.isTargetDevice();
10347
10348 // Determine whether group-private storage should be allocated based on
10349 // device_type. When not specified, default to 'any' (allocate on both).
10350 bool shouldAllocate = true;
10351 switch (groupprivateOp.getDeviceType().value_or(
10352 mlir::omp::DeclareTargetDeviceType::any)) {
10353 case mlir::omp::DeclareTargetDeviceType::host:
10354 shouldAllocate = !isTargetDevice;
10355 break;
10356 case mlir::omp::DeclareTargetDeviceType::nohost:
10357 shouldAllocate = isTargetDevice;
10358 break;
10359 case mlir::omp::DeclareTargetDeviceType::any:
10360 shouldAllocate = true;
10361 break;
10362 }
10363
10364 // Look up the global variable directly by symbol name.
10366 &opInst, groupprivateOp.getSymNameAttr());
10367 if (!global)
10368 return opInst.emitError()
10369 << "expected symbol '" << groupprivateOp.getSymName()
10370 << "' to reference an LLVM global variable";
10371
10372 llvm::GlobalValue *globalValue = moduleTranslation.lookupGlobal(global);
10373 llvm::Type *varType = moduleTranslation.convertType(global.getType());
10374 std::string varName = globalValue->getName().str();
10375
10376 llvm::Value *resultPtr;
10377 if (shouldAllocate && isTargetDevice) {
10378 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
10379 llvm::Triple targetTriple(llvmModule->getTargetTriple());
10380 unsigned sharedAddressSpace;
10381 if (targetTriple.isAMDGCN())
10382 sharedAddressSpace = llvm::AMDGPUAS::LOCAL_ADDRESS;
10383 else if (targetTriple.isNVPTX())
10384 sharedAddressSpace = llvm::NVPTXAS::ADDRESS_SPACE_SHARED;
10385 else
10386 return opInst.emitError() << "groupprivate is not supported for target: "
10387 << targetTriple.str();
10388 llvm::GlobalVariable *sharedVar = new llvm::GlobalVariable(
10389 *llvmModule, varType, /*isConstant=*/false,
10390 llvm::GlobalValue::InternalLinkage, llvm::PoisonValue::get(varType),
10391 varName, /*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal,
10392 sharedAddressSpace,
10393 /*isExternallyInitialized=*/false);
10394 resultPtr = sharedVar;
10395 } else {
10396 if (shouldAllocate && !isTargetDevice)
10397 opInst.emitWarning("groupprivate directive is currently ignored on the "
10398 "host, using original global");
10399 resultPtr = globalValue;
10400 }
10401
10402 moduleTranslation.mapValue(opInst.getResult(0), resultPtr);
10403 return success();
10404}
10405
10406/// Given an OpenMP MLIR operation, create the corresponding LLVM IR (including
10407/// OpenMP runtime calls).
10408LogicalResult OpenMPDialectLLVMIRTranslationInterface::convertOperation(
10409 Operation *op, llvm::IRBuilderBase &builder,
10410 LLVM::ModuleTranslation &moduleTranslation) const {
10411 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
10412
10413 if (ompBuilder->Config.isTargetDevice() &&
10414 !isa<omp::TargetOp, omp::MapInfoOp, omp::TerminatorOp, omp::YieldOp>(
10415 op) &&
10416 isHostDeviceOp(op))
10417 return op->emitOpError() << "unsupported host op found in device";
10418
10419 // For each loop, introduce one stack frame to hold loop information. Ensure
10420 // this is only done for the outermost loop wrapper to prevent introducing
10421 // multiple stack frames for a single loop. Initially set to null, the loop
10422 // information structure is initialized during translation of the nested
10423 // omp.loop_nest operation, making it available to translation of all loop
10424 // wrappers after their body has been successfully translated.
10425 bool isOutermostLoopWrapper =
10426 isa_and_present<omp::LoopWrapperInterface>(op) &&
10427 !dyn_cast_if_present<omp::LoopWrapperInterface>(op->getParentOp());
10428
10429 // The TASKLOOP construct is implemented with an outer taskloop.context
10430 // operation which is not a loop wrapper, containing an inner taskloop
10431 // operation which is a loop wrapper. The stack frame should be pushed when
10432 // translating the outer taskloop.context and popped when translating the
10433 // inner taskloop which is a loop wrapper. We need access to the loop
10434 // information in the outer taskloop context so we need to create it and pop
10435 // it around the taskloop context not the inner loop wrapper.
10436 if (isa<omp::TaskloopContextOp>(op))
10437 isOutermostLoopWrapper = true;
10438 else if (isa<omp::TaskloopWrapperOp>(op))
10439 isOutermostLoopWrapper = false;
10440
10441 if (isOutermostLoopWrapper)
10442 moduleTranslation.stackPush<OpenMPLoopInfoStackFrame>();
10443
10444 auto result =
10445 llvm::TypeSwitch<Operation *, LogicalResult>(op)
10446 .Case([&](omp::BarrierOp op) -> LogicalResult {
10448 return failure();
10449
10450 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
10451 ompBuilder->createBarrier(builder, llvm::omp::OMPD_barrier);
10452 LogicalResult res = handleError(afterIP, *op);
10453 if (res.succeeded()) {
10454 // If the barrier generated a cancellation check, the insertion
10455 // point might now need to be changed to a new continuation block
10456 builder.restoreIP(*afterIP);
10457 }
10458 return res;
10459 })
10460 .Case([&](omp::TaskyieldOp op) {
10462 return failure();
10463
10464 ompBuilder->createTaskyield(builder);
10465 return success();
10466 })
10467 .Case([&](omp::FlushOp op) {
10469 return failure();
10470
10471 // No support in Openmp runtime function (__kmpc_flush) to accept
10472 // the argument list.
10473 // OpenMP standard states the following:
10474 // "An implementation may implement a flush with a list by ignoring
10475 // the list, and treating it the same as a flush without a list."
10476 //
10477 // The argument list is discarded so that, flush with a list is
10478 // treated same as a flush without a list.
10479 ompBuilder->createFlush(builder);
10480 return success();
10481 })
10482 .Case([&](omp::ErrorOp op) {
10484 return failure();
10485
10486 llvm::Value *message = nullptr;
10487 if (mlir::Value messageExpr = op.getMessageExpr())
10488 message = moduleTranslation.lookupValue(messageExpr);
10489 else if (std::optional<StringRef> msg = op.getMessage();
10490 msg && !msg->empty())
10491 message = builder.CreateGlobalString(*msg);
10492 ompBuilder->createError(
10493 llvm::OpenMPIRBuilder::LocationDescription(builder),
10494 op.getSeverity() == omp::ClauseSeverity::fatal, message);
10495 return success();
10496 })
10497 .Case([&](omp::ParallelOp op) {
10498 return convertOmpParallel(op, builder, moduleTranslation);
10499 })
10500 .Case([&](omp::MaskedOp) {
10501 return convertOmpMasked(*op, builder, moduleTranslation);
10502 })
10503 .Case([&](omp::MasterOp) {
10504 return convertOmpMaster(*op, builder, moduleTranslation);
10505 })
10506 .Case([&](omp::CriticalOp) {
10507 return convertOmpCritical(*op, builder, moduleTranslation);
10508 })
10509 .Case([&](omp::OrderedRegionOp) {
10510 return convertOmpOrderedRegion(*op, builder, moduleTranslation);
10511 })
10512 .Case([&](omp::OrderedOp) {
10513 return convertOmpOrdered(*op, builder, moduleTranslation);
10514 })
10515 .Case([&](omp::WsloopOp) {
10516 return convertOmpWsloop(*op, builder, moduleTranslation);
10517 })
10518 .Case([&](omp::SimdOp) {
10519 return convertOmpSimd(*op, builder, moduleTranslation);
10520 })
10521 .Case([&](omp::AtomicReadOp) {
10522 return convertOmpAtomicRead(*op, builder, moduleTranslation);
10523 })
10524 .Case([&](omp::AtomicWriteOp) {
10525 return convertOmpAtomicWrite(*op, builder, moduleTranslation);
10526 })
10527 .Case([&](omp::AtomicUpdateOp op) {
10528 return convertOmpAtomicUpdate(op, builder, moduleTranslation);
10529 })
10530 .Case([&](omp::AtomicCaptureOp op) {
10531 return convertOmpAtomicCapture(op, builder, moduleTranslation);
10532 })
10533 .Case([&](omp::AtomicCompareOp op) {
10534 return convertOmpAtomicCompare(op, builder, moduleTranslation);
10535 })
10536 .Case([&](omp::CancelOp op) {
10537 return convertOmpCancel(op, builder, moduleTranslation);
10538 })
10539 .Case([&](omp::CancellationPointOp op) {
10540 return convertOmpCancellationPoint(op, builder, moduleTranslation);
10541 })
10542 .Case([&](omp::SectionsOp) {
10543 return convertOmpSections(*op, builder, moduleTranslation);
10544 })
10545 .Case([&](omp::ScopeOp op) {
10546 return convertOmpScope(op, builder, moduleTranslation);
10547 })
10548 .Case([&](omp::SingleOp op) {
10549 return convertOmpSingle(op, builder, moduleTranslation);
10550 })
10551 .Case([&](omp::TeamsOp op) {
10552 return convertOmpTeams(op, builder, moduleTranslation);
10553 })
10554 .Case([&](omp::TaskOp op) {
10555 return convertOmpTaskOp(op, builder, moduleTranslation);
10556 })
10557 .Case([&](omp::TaskloopWrapperOp op) {
10558 return convertOmpTaskloopWrapperOp(op, builder, moduleTranslation);
10559 })
10560 .Case([&](omp::TaskloopContextOp op) {
10561 return convertOmpTaskloopContextOp(op, builder, moduleTranslation);
10562 })
10563 .Case([&](omp::TaskgroupOp op) {
10564 return convertOmpTaskgroupOp(op, builder, moduleTranslation);
10565 })
10566 .Case([&](omp::TaskwaitOp op) {
10567 return convertOmpTaskwaitOp(op, builder, moduleTranslation);
10568 })
10569 .Case([&](omp::InteropInitOp op) {
10570 return convertOmpInteropInitOp(op, builder, moduleTranslation);
10571 })
10572 .Case([&](omp::InteropDestroyOp op) {
10573 return convertOmpInteropDestroyOp(op, builder, moduleTranslation);
10574 })
10575 .Case([&](omp::InteropUseOp op) {
10576 return convertOmpInteropUseOp(op, builder, moduleTranslation);
10577 })
10578 .Case<omp::YieldOp, omp::TerminatorOp, omp::DeclareMapperOp,
10579 omp::DeclareMapperInfoOp, omp::DeclareReductionOp,
10580 omp::CriticalDeclareOp>([](auto op) {
10581 // `yield` and `terminator` can be just omitted. The block structure
10582 // was created in the region that handles their parent operation.
10583 // `declare_reduction` will be used by reductions and is not
10584 // converted directly, skip it.
10585 // `declare_mapper` and `declare_mapper.info` are handled whenever
10586 // they are referred to through a `map` clause.
10587 // `critical.declare` is only used to declare names of critical
10588 // sections which will be used by `critical` ops and hence can be
10589 // ignored for lowering. The OpenMP IRBuilder will create unique
10590 // name for critical section names.
10591 return success();
10592 })
10593 .Case([&](omp::ThreadprivateOp) {
10594 return convertOmpThreadprivate(*op, builder, moduleTranslation);
10595 })
10596 .Case<omp::TargetDataOp, omp::TargetEnterDataOp,
10597 omp::TargetExitDataOp, omp::TargetUpdateOp>([&](auto op) {
10598 return convertOmpTargetData(op, builder, moduleTranslation);
10599 })
10600 .Case([&](omp::TargetOp) {
10601 return convertOmpTarget(*op, builder, moduleTranslation);
10602 })
10603 .Case([&](omp::DistributeOp) {
10604 return convertOmpDistribute(*op, builder, moduleTranslation);
10605 })
10606 .Case([&](omp::LoopNestOp) {
10607 return convertOmpLoopNest(*op, builder, moduleTranslation);
10608 })
10609 .Case<omp::MapInfoOp, omp::MapBoundsOp, omp::PrivateClauseOp,
10610 omp::AffinityEntryOp, omp::IteratorOp>([&](auto op) {
10611 // No-op, should be handled by relevant owning operations e.g.
10612 // TargetOp, TargetEnterDataOp, TargetExitDataOp, TargetDataOp
10613 // etc. and then discarded
10614 return success();
10615 })
10616 .Case([&](omp::NewCliOp op) {
10617 // Meta-operation: Doesn't do anything by itself, but used to
10618 // identify a loop.
10619 return success();
10620 })
10621 .Case([&](omp::CanonicalLoopOp op) {
10622 return convertOmpCanonicalLoopOp(op, builder, moduleTranslation);
10623 })
10624 .Case([&](omp::UnrollHeuristicOp op) {
10625 // FIXME: Handling omp.unroll_heuristic as an executable requires
10626 // that the generator (e.g. omp.canonical_loop) has been seen first.
10627 // For construct that require all codegen to occur inside a callback
10628 // (e.g. OpenMPIRBilder::createParallel), all codegen of that
10629 // contained region including their transformations must occur at
10630 // the omp.canonical_loop.
10631 return applyUnrollHeuristic(op, builder, moduleTranslation);
10632 })
10633 .Case([&](omp::UnrollFullOp op) {
10634 return applyUnrollFull(op, builder, moduleTranslation);
10635 })
10636 .Case([&](omp::UnrollPartialOp op) {
10637 return applyUnrollPartial(op, builder, moduleTranslation);
10638 })
10639 .Case([&](omp::TileOp op) {
10640 return applyTile(op, builder, moduleTranslation);
10641 })
10642 .Case([&](omp::FuseOp op) {
10643 return applyFuse(op, builder, moduleTranslation);
10644 })
10645 .Case([&](omp::TargetAllocMemOp) {
10646 return convertTargetAllocMemOp(*op, builder, moduleTranslation);
10647 })
10648 .Case([&](omp::TargetFreeMemOp) {
10649 return convertTargetFreeMemOp(*op, builder, moduleTranslation);
10650 })
10651 .Case([&](omp::AllocateDirOp) {
10652 return convertAllocateDirOp(*op, builder, moduleTranslation, *this);
10653 })
10654 .Case([&](omp::AllocateFreeOp) {
10655 return convertAllocateFreeOp(*op, builder, moduleTranslation,
10656 *this);
10657 })
10658 .Case([&](omp::AllocSharedMemOp op) {
10659 return convertAllocSharedMemOp(op, builder, moduleTranslation);
10660 })
10661 .Case([&](omp::FreeSharedMemOp op) {
10662 return convertFreeSharedMemOp(op, builder, moduleTranslation);
10663 })
10664 .Case([&](omp::GroupprivateOp) {
10665 return convertOmpGroupprivate(*op, builder, moduleTranslation);
10666 })
10667 .Default([&](Operation *inst) {
10668 return inst->emitError()
10669 << "not yet implemented: " << inst->getName();
10670 });
10671
10672 if (isOutermostLoopWrapper)
10673 moduleTranslation.stackPop();
10674
10675 return result;
10676}
10677
10679 registry.insert<omp::OpenMPDialect>();
10680 registry.addExtension(+[](MLIRContext *ctx, omp::OpenMPDialect *dialect) {
10681 dialect->addInterfaces<OpenMPDialectLLVMIRTranslationInterface>();
10682 });
10683}
10684
10686 DialectRegistry registry;
10688 context.appendDialectRegistry(registry);
10689}
for(Operation *op :ops)
return success()
lhs
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
ArrayAttr()
if(!isCopyOut)
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 mlir::LogicalResult convertIteratorRegion(llvm::Value *linearIV, IteratorInfo &iterInfo, mlir::Block &iteratorRegionBlock, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static omp::MapInfoOp getFirstOrLastMappedMemberPtr(omp::MapInfoOp mapInfo, bool first)
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 void popCancelFinalizationCB(const ArrayRef< llvm::UncondBrInst * > cancelTerminators, llvm::OpenMPIRBuilder &ompBuilder, const llvm::OpenMPIRBuilder::InsertPointTy &afterIP)
If we cancelled the construct, we should branch to the finalization block of that construct....
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 llvm::Expected< llvm::BasicBlock * > allocatePrivateVars(T op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, PrivateVarsInfo &privateVarsInfo, const llvm::OpenMPIRBuilder::InsertPointTy &allocaIP, llvm::DenseMap< Value, Value > *mappedPrivateVars=nullptr)
Allocate and initialize delayed private variables. Returns the basic block which comes after all of t...
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)
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 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 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 std::vector< llvm::Value * > calculateBoundsOffset(LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder, bool isArrayTy, OperandRange bounds)
This function calculates the array/pointer offset for map data provided with bounds operations,...
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 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 copyFirstPrivateVars(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 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 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 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 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 bool opIsInSingleThread(mlir::Operation *op)
This can't always be determined statically, but when we can, it is good to avoid generating compiler-...
static uint64_t getReductionDataSize(OpTy &op)
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 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 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 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 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 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
#define div(a, b)
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:33
BlockArgument getArgument(unsigned i)
Definition Block.h:153
unsigned getNumArguments()
Definition Block.h:152
OpListType & getOperations()
Definition Block.h:161
Operation & front()
Definition Block.h:177
Operation & back()
Definition Block.h:176
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:236
iterator begin()
Definition Block.h:167
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.
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:63
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:731
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:719
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:842
user_range getUsers()
Returns a range of all users.
Definition Operation.h:918
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:213
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:97
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)
Create a constant string representing the mapping information extracted from the MLIR location inform...
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:107
bool allocaUsesRequireSharedMem(Value alloc)
Check whether the value representing an allocation, assumed to have been defined in a shared device c...
Definition Utils.cpp:92
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
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:307
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:125
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:1341
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
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.
SmallVector< mlir::Value > mlirVars
SmallVector< omp::PrivateClauseOp > privatizers
MutableArrayRef< BlockArgument > blockArgs
llvm::DenseMap< Value, llvm::Value * > convertedAllocators
SmallVector< llvm::Value * > llvmVars
SmallVector< AllocatorPrivateInfo > allocatorPrivates
Eliminates variable at the specified position using Fourier-Motzkin variable elimination.