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::DebugLoc reductionLoc = builder.getCurrentDebugLocation();
1723 llvm::OpenMPIRBuilder::InsertPointOrErrorTy contInsertPoint =
1724 ompBuilder->createReductions(builder, allocaIP, reductionInfos, isByRef,
1725 isNowait, isTeamsReduction);
1726
1727 if (failed(handleError(contInsertPoint, *op)))
1728 return failure();
1729
1730 if (!contInsertPoint->getBlock())
1731 return op->emitOpError() << "failed to convert reductions";
1732
1733 llvm::OpenMPIRBuilder::InsertPointTy afterIP = *contInsertPoint;
1734 if (!isTeamsReduction) {
1735 llvm::OpenMPIRBuilder::InsertPointOrErrorTy barrierIP =
1736 ompBuilder->createBarrier({*contInsertPoint, reductionLoc},
1737 llvm::omp::OMPD_for);
1738
1739 if (failed(handleError(barrierIP, *op)))
1740 return failure();
1741 afterIP = *barrierIP;
1742 }
1743
1744 tempTerminator->eraseFromParent();
1745 builder.restoreIP(afterIP);
1746
1747 // after the construct, deallocate private reduction variables
1748 SmallVector<Region *> reductionRegions;
1749 llvm::transform(reductionDecls, std::back_inserter(reductionRegions),
1750 [](omp::DeclareReductionOp reductionDecl) {
1751 return &reductionDecl.getCleanupRegion();
1752 });
1753 LogicalResult result = inlineOmpRegionCleanup(
1754 reductionRegions, privateReductionVariables, moduleTranslation, builder,
1755 "omp.reduction.cleanup");
1756
1757 bool useDeviceSharedMem = omp::opInSharedDeviceContext(*op);
1758 if (useDeviceSharedMem) {
1759 for (auto [var, reductionDecl] :
1760 llvm::zip_equal(privateReductionVariables, reductionDecls))
1761 ompBuilder->createOMPFreeShared(
1762 builder, var, moduleTranslation.convertType(reductionDecl.getType()));
1763 }
1764
1765 return result;
1766}
1767
1768static ArrayRef<bool> getIsByRef(std::optional<ArrayRef<bool>> attr) {
1769 if (!attr)
1770 return {};
1771 return *attr;
1772}
1773
1774// TODO: not used by omp.parallel
1775template <typename OP>
1777 OP op, ArrayRef<BlockArgument> reductionArgs, llvm::IRBuilderBase &builder,
1778 LLVM::ModuleTranslation &moduleTranslation,
1779 llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
1781 SmallVectorImpl<llvm::Value *> &privateReductionVariables,
1782 DenseMap<Value, llvm::Value *> &reductionVariableMap,
1783 llvm::ArrayRef<bool> isByRef) {
1784 if (op.getNumReductionVars() == 0)
1785 return success();
1786
1787 SmallVector<DeferredStore> deferredStores;
1788
1789 if (failed(allocReductionVars(op, reductionArgs, builder, moduleTranslation,
1790 allocaIP, reductionDecls,
1791 privateReductionVariables, reductionVariableMap,
1792 deferredStores, isByRef)))
1793 return failure();
1794
1795 return initReductionVars(op, reductionArgs, builder, moduleTranslation,
1796 allocaIP.getBlock(), reductionDecls,
1797 privateReductionVariables, reductionVariableMap,
1798 isByRef, deferredStores);
1799}
1800
1801/// Return the llvm::Value * corresponding to the `privateVar` that
1802/// is being privatized. It isn't always as simple as looking up
1803/// moduleTranslation with privateVar. For instance, in case of
1804/// an allocatable, the descriptor for the allocatable is privatized.
1805/// This descriptor is mapped using an MapInfoOp. So, this function
1806/// will return a pointer to the llvm::Value corresponding to the
1807/// block argument for the mapped descriptor.
1808static llvm::Value *
1809findAssociatedValue(Value privateVar, llvm::IRBuilderBase &builder,
1810 LLVM::ModuleTranslation &moduleTranslation,
1811 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1812 if (mappedPrivateVars == nullptr || !mappedPrivateVars->contains(privateVar))
1813 return moduleTranslation.lookupValue(privateVar);
1814
1815 Value blockArg = (*mappedPrivateVars)[privateVar];
1816 Type privVarType = privateVar.getType();
1817 Type blockArgType = blockArg.getType();
1818 assert(isa<LLVM::LLVMPointerType>(blockArgType) &&
1819 "A block argument corresponding to a mapped var should have "
1820 "!llvm.ptr type");
1821
1822 if (privVarType == blockArgType)
1823 return moduleTranslation.lookupValue(blockArg);
1824
1825 // This typically happens when the privatized type is lowered from
1826 // boxchar<KIND> and gets lowered to !llvm.struct<(ptr, i64)>. That is the
1827 // struct/pair is passed by value. But, mapped values are passed only as
1828 // pointers, so before we privatize, we must load the pointer.
1829 if (!isa<LLVM::LLVMPointerType>(privVarType))
1830 return builder.CreateLoad(moduleTranslation.convertType(privVarType),
1831 moduleTranslation.lookupValue(blockArg));
1832
1833 return moduleTranslation.lookupValue(privateVar);
1834}
1835
1836// Privatizer region arguments may be by-value even when the available LLVM
1837// value is storage for that value, e.g. lowered Fortran boxchar descriptors in
1838// task context structs. Materialize the value expected by the region argument
1839// while preserving the existing pointer mapping for pointer arguments.
1840static llvm::Value *
1841materializeRegionArgValue(llvm::IRBuilderBase &builder,
1842 LLVM::ModuleTranslation &moduleTranslation,
1843 BlockArgument regionArg, llvm::Value *value) {
1844 if (!regionArg)
1845 return value;
1846
1847 llvm::Type *regionArgType =
1848 moduleTranslation.convertType(regionArg.getType());
1849 if (regionArgType->isPointerTy() || !value->getType()->isPointerTy())
1850 return value;
1851
1852 return builder.CreateLoad(regionArgType, value);
1853}
1854
1855/// Initialize a single (first)private variable. You probably want to use
1856/// allocateAndInitPrivateVars instead of this.
1857/// This returns the private variable which has been initialized. This
1858/// variable should be mapped before constructing the body of the Op.
1860initPrivateVar(llvm::IRBuilderBase &builder,
1861 LLVM::ModuleTranslation &moduleTranslation,
1862 omp::PrivateClauseOp &privDecl, llvm::Value *nonPrivateVar,
1863 BlockArgument &blockArg, llvm::Value *llvmPrivateVar,
1864 llvm::BasicBlock *privInitBlock,
1865 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1866 Region &initRegion = privDecl.getInitRegion();
1867 if (initRegion.empty())
1868 return llvmPrivateVar;
1869
1870 assert(nonPrivateVar);
1871 moduleTranslation.mapValue(privDecl.getInitMoldArg(), nonPrivateVar);
1872 moduleTranslation.mapValue(privDecl.getInitPrivateArg(), llvmPrivateVar);
1873
1874 // in-place convert the private initialization region
1876 if (failed(inlineConvertOmpRegions(initRegion, "omp.private.init", builder,
1877 moduleTranslation, &phis)))
1878 return llvm::createStringError(
1879 "failed to inline `init` region of `omp.private`");
1880
1881 assert(phis.size() == 1 && "expected one allocation to be yielded");
1882
1883 // clear init region block argument mapping in case it needs to be
1884 // re-created with a different source for another use of the same
1885 // reduction decl
1886 moduleTranslation.forgetMapping(initRegion);
1887
1888 // Prefer the value yielded from the init region to the allocated private
1889 // variable in case the region is operating on arguments by-value (e.g.
1890 // Fortran character boxes).
1891 return phis[0];
1892}
1893
1894/// Version of initPrivateVar which looks up the nonPrivateVar from mlirPrivVar.
1896 llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation,
1897 omp::PrivateClauseOp &privDecl, Value mlirPrivVar, BlockArgument &blockArg,
1898 llvm::Value *llvmPrivateVar, llvm::BasicBlock *privInitBlock,
1899 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1900 return initPrivateVar(
1901 builder, moduleTranslation, privDecl,
1902 findAssociatedValue(mlirPrivVar, builder, moduleTranslation,
1903 mappedPrivateVars),
1904 blockArg, llvmPrivateVar, privInitBlock, mappedPrivateVars);
1905}
1906
1907static llvm::Error
1908initPrivateVars(llvm::IRBuilderBase &builder,
1909 LLVM::ModuleTranslation &moduleTranslation,
1910 PrivateVarsInfo &privateVarsInfo,
1911 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1912 if (privateVarsInfo.blockArgs.empty())
1913 return llvm::Error::success();
1914
1915 llvm::BasicBlock *privInitBlock = splitBB(builder, true, "omp.private.init");
1916 setInsertPointForPossiblyEmptyBlock(builder, privInitBlock);
1917
1918 for (auto [idx, zip] : llvm::enumerate(llvm::zip_equal(
1919 privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
1920 privateVarsInfo.blockArgs, privateVarsInfo.llvmVars))) {
1921 auto [privDecl, mlirPrivVar, blockArg, llvmPrivateVar] = zip;
1923 builder, moduleTranslation, privDecl, mlirPrivVar, blockArg,
1924 llvmPrivateVar, privInitBlock, mappedPrivateVars);
1925
1926 if (!privVarOrErr)
1927 return privVarOrErr.takeError();
1928
1929 llvmPrivateVar = privVarOrErr.get();
1930 moduleTranslation.mapValue(blockArg, llvmPrivateVar);
1931
1933 }
1934
1935 return llvm::Error::success();
1936}
1937
1938/// Allocate and initialize delayed private variables. Returns the basic block
1939/// which comes after all of these allocations. llvm::Value * for each of these
1940/// private variables are populated in llvmPrivateVars.
1941template <typename T>
1943allocatePrivateVars(T op, llvm::IRBuilderBase &builder,
1944 LLVM::ModuleTranslation &moduleTranslation,
1945 PrivateVarsInfo &privateVarsInfo,
1946 const llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
1947 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1948 // Allocate private vars
1949 llvm::Instruction *allocaTerminator = allocaIP.getBlock()->getTerminator();
1950 splitBB(llvm::OpenMPIRBuilder::InsertPointTy(allocaIP.getBlock(),
1951 allocaTerminator->getIterator()),
1952 true, allocaTerminator->getStableDebugLoc(),
1953 "omp.region.after_alloca");
1954
1955 llvm::IRBuilderBase::InsertPointGuard guard(builder);
1956 // Update the allocaTerminator since the alloca block was split above.
1957 allocaTerminator = allocaIP.getBlock()->getTerminator();
1958 builder.SetInsertPoint(allocaTerminator);
1959 // The new terminator is an uncondition branch created by the splitBB above.
1960 assert(allocaTerminator->getNumSuccessors() == 1 &&
1961 "This is an unconditional branch created by splitBB");
1962
1963 llvm::DataLayout dataLayout = builder.GetInsertBlock()->getDataLayout();
1964 llvm::BasicBlock *afterAllocas = allocaTerminator->getSuccessor(0);
1965
1966 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
1967 bool mightUseDeviceSharedMem = omp::opInSharedDeviceContext(*op);
1968 unsigned int allocaAS =
1969 moduleTranslation.getLLVMModule()->getDataLayout().getAllocaAddrSpace();
1970 unsigned int defaultAS = moduleTranslation.getLLVMModule()
1971 ->getDataLayout()
1972 .getProgramAddressSpace();
1973
1974 SmallVector<int64_t> allocateItemForPrivate(privateVarsInfo.blockArgs.size(),
1975 -1);
1976 ValueRange allocatorVars;
1977 DenseI64ArrayAttr allocateAlignments;
1978 if constexpr (std::is_same_v<T, omp::ParallelOp>) {
1979 allocatorVars = op.getAllocatorVars();
1980 allocateAlignments = op.getAllocateAlignmentsAttr();
1981 if (auto privateIndices = op.getAllocatePrivateIndicesAttr())
1982 for (auto [allocateIndex, privateIndex] :
1983 llvm::enumerate(privateIndices.asArrayRef()))
1984 allocateItemForPrivate[privateIndex] = allocateIndex;
1985 }
1986
1987 for (auto [privateIndex, tuple] : llvm::enumerate(llvm::zip_equal(
1988 privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
1989 privateVarsInfo.blockArgs))) {
1990 auto [privDecl, mlirPrivVar, blockArg] = tuple;
1991 llvm::Type *llvmAllocType =
1992 moduleTranslation.convertType(privDecl.getType());
1993 builder.SetInsertPoint(allocaIP.getBlock()->getTerminator());
1994 llvm::Value *llvmPrivateVar = nullptr;
1995 int64_t allocateIndex = allocateItemForPrivate[privateIndex];
1996 if (allocateIndex >= 0) {
1997 if (mightUseDeviceSharedMem ||
1998 op->template getParentOfType<omp::TargetOp>())
1999 return llvm::createStringError(
2000 "allocate clause on a device parallel region is not supported");
2001 if (!llvmAllocType->isSized())
2002 return llvm::createStringError(
2003 "allocate clause private type must have a fixed size");
2004 llvm::TypeSize size = dataLayout.getTypeAllocSize(llvmAllocType);
2005 if (size.isScalable())
2006 return llvm::createStringError(
2007 "allocate clause private type must have a fixed size");
2008 llvm::IntegerType *sizeTy =
2009 moduleTranslation.getLLVMModule()->getDataLayout().getIntPtrType(
2010 moduleTranslation.getLLVMModule()->getContext());
2011 if (!llvm::isUIntN(sizeTy->getBitWidth(), size.getFixedValue()))
2012 return llvm::createStringError(
2013 "OpenMP allocation size cannot be represented by the target size "
2014 "type");
2015 llvm::Value *sizeValue =
2016 llvm::ConstantInt::get(sizeTy, size.getFixedValue());
2017
2018 Value allocatorVar = allocatorVars[allocateIndex];
2019 auto allocator = privateVarsInfo.convertedAllocators.find(allocatorVar);
2020 if (allocator == privateVarsInfo.convertedAllocators.end())
2021 return llvm::createStringError(
2022 "failed to find converted OpenMP allocator operand");
2023 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2024 int64_t alignment =
2025 allocateAlignments ? allocateAlignments[allocateIndex] : 0;
2026 if (alignment != 0) {
2027 // The allocation must be aligned to at least the maximum of the
2028 // requested alignment and the alignment the base language requires
2029 // for the type being allocated.
2030 uint64_t alignmentValue = std::max<uint64_t>(
2031 static_cast<uint64_t>(alignment),
2032 dataLayout.getABITypeAlign(llvmAllocType).value());
2033 if (!llvm::isUIntN(sizeTy->getBitWidth(), alignmentValue))
2034 return llvm::createStringError(
2035 "OpenMP allocation alignment cannot be represented by the "
2036 "target size type");
2037 llvmPrivateVar = ompBuilder->createOMPAlignedAlloc(
2038 ompLoc, llvm::ConstantInt::get(sizeTy, alignmentValue), sizeValue,
2039 allocator->second, "omp.private.alloc");
2040 } else {
2041 llvmPrivateVar = ompBuilder->createOMPAlloc(
2042 ompLoc, sizeValue, allocator->second, "omp.private.alloc");
2043 }
2044 if (!llvmPrivateVar)
2045 return llvm::createStringError(
2046 "failed to create OpenMP private allocation");
2047 privateVarsInfo.allocatorPrivates.push_back(
2048 {llvmPrivateVar, allocator->second});
2049 } else if (mightUseDeviceSharedMem &&
2051 llvmPrivateVar = ompBuilder->createOMPAllocShared(builder, llvmAllocType);
2052 } else {
2053 llvmPrivateVar = builder.CreateAlloca(
2054 llvmAllocType, /*ArraySize=*/nullptr, "omp.private.alloc");
2055 if (allocaAS != defaultAS)
2056 llvmPrivateVar = builder.CreateAddrSpaceCast(
2057 llvmPrivateVar, builder.getPtrTy(defaultAS));
2058 }
2059
2060 privateVarsInfo.llvmVars.push_back(llvmPrivateVar);
2061 }
2062
2063 return afterAllocas;
2064}
2065
2066/// This can't always be determined statically, but when we can, it is good to
2067/// avoid generating compiler-added barriers which will deadlock the program.
2069 for (mlir::Operation *parent = op->getParentOp(); parent != nullptr;
2070 parent = parent->getParentOp()) {
2071 if (mlir::isa<omp::SingleOp, omp::CriticalOp>(parent))
2072 return true;
2073
2074 // e.g.
2075 // omp.single {
2076 // omp.parallel {
2077 // op
2078 // }
2079 // }
2080 if (mlir::isa<omp::ParallelOp>(parent))
2081 return false;
2082 }
2083 return false;
2084}
2085
2086static LogicalResult copyFirstPrivateVars(
2087 mlir::Operation *op, llvm::IRBuilderBase &builder,
2088 LLVM::ModuleTranslation &moduleTranslation,
2090 ArrayRef<llvm::Value *> llvmPrivateVars,
2091 SmallVectorImpl<omp::PrivateClauseOp> &privateDecls, bool insertBarrier,
2092 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
2093 // Apply copy region for firstprivate.
2094 bool needsFirstprivate =
2095 llvm::any_of(privateDecls, [](omp::PrivateClauseOp &privOp) {
2096 return privOp.getDataSharingType() ==
2097 omp::DataSharingClauseType::FirstPrivate;
2098 });
2099
2100 if (!needsFirstprivate)
2101 return success();
2102
2103 llvm::BasicBlock *copyBlock =
2104 splitBB(builder, /*CreateBranch=*/true, "omp.private.copy");
2105 setInsertPointForPossiblyEmptyBlock(builder, copyBlock);
2106
2107 for (auto [decl, moldVar, llvmVar] :
2108 llvm::zip_equal(privateDecls, moldVars, llvmPrivateVars)) {
2109 if (decl.getDataSharingType() != omp::DataSharingClauseType::FirstPrivate)
2110 continue;
2111
2112 // copyRegion implements `lhs = rhs`
2113 Region &copyRegion = decl.getCopyRegion();
2114
2115 llvm::Value *copyMoldVar = materializeRegionArgValue(
2116 builder, moduleTranslation, decl.getCopyMoldArg(), moldVar);
2117 llvm::Value *copyPrivateVar = materializeRegionArgValue(
2118 builder, moduleTranslation, decl.getCopyPrivateArg(), llvmVar);
2119
2120 moduleTranslation.mapValue(decl.getCopyMoldArg(), copyMoldVar);
2121
2122 // map copyRegion lhs arg
2123 moduleTranslation.mapValue(decl.getCopyPrivateArg(), copyPrivateVar);
2124
2125 // in-place convert copy region
2126 if (failed(inlineConvertOmpRegions(copyRegion, "omp.private.copy", builder,
2127 moduleTranslation)))
2128 return decl.emitError("failed to inline `copy` region of `omp.private`");
2129
2131
2132 // ignore unused value yielded from copy region
2133
2134 // clear copy region block argument mapping in case it needs to be
2135 // re-created with different sources for reuse of the same reduction
2136 // decl
2137 moduleTranslation.forgetMapping(copyRegion);
2138 }
2139
2140 if (insertBarrier && !opIsInSingleThread(op)) {
2141 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
2142 llvm::OpenMPIRBuilder::InsertPointOrErrorTy res =
2143 ompBuilder->createBarrier(builder, llvm::omp::OMPD_barrier);
2144 if (failed(handleError(res, *op)))
2145 return failure();
2146 }
2147
2148 return success();
2149}
2150
2151static LogicalResult copyFirstPrivateVars(
2152 mlir::Operation *op, llvm::IRBuilderBase &builder,
2153 LLVM::ModuleTranslation &moduleTranslation,
2154 SmallVectorImpl<mlir::Value> &mlirPrivateVars,
2155 ArrayRef<llvm::Value *> llvmPrivateVars,
2156 SmallVectorImpl<omp::PrivateClauseOp> &privateDecls, bool insertBarrier,
2157 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
2158 llvm::SmallVector<llvm::Value *> moldVars(mlirPrivateVars.size());
2159 llvm::transform(mlirPrivateVars, moldVars.begin(), [&](mlir::Value mlirVar) {
2160 // map copyRegion rhs arg
2161 llvm::Value *moldVar = findAssociatedValue(
2162 mlirVar, builder, moduleTranslation, mappedPrivateVars);
2163 assert(moldVar);
2164 return moldVar;
2165 });
2166 return copyFirstPrivateVars(op, builder, moduleTranslation, moldVars,
2167 llvmPrivateVars, privateDecls, insertBarrier,
2168 mappedPrivateVars);
2169}
2170
2171template <typename T>
2172static LogicalResult
2173cleanupPrivateVars(T op, llvm::IRBuilderBase &builder,
2174 LLVM::ModuleTranslation &moduleTranslation, Location loc,
2175 PrivateVarsInfo &privateVarsInfo) {
2176 // private variable deallocation
2177 SmallVector<Region *> privateCleanupRegions;
2178 llvm::transform(privateVarsInfo.privatizers,
2179 std::back_inserter(privateCleanupRegions),
2180 [](omp::PrivateClauseOp privatizer) {
2181 return &privatizer.getDeallocRegion();
2182 });
2183
2184 if (failed(inlineOmpRegionCleanup(privateCleanupRegions,
2185 privateVarsInfo.llvmVars, moduleTranslation,
2186 builder, "omp.private.dealloc",
2187 /*shouldLoadCleanupRegionArg=*/false)))
2188 return mlir::emitError(loc, "failed to inline `dealloc` region of an "
2189 "`omp.private` op in");
2191
2192 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
2193 bool mightUseDeviceSharedMem = omp::opInSharedDeviceContext(*op);
2194 for (auto [privDecl, llvmPrivVar, blockArg] :
2195 llvm::zip_equal(privateVarsInfo.privatizers, privateVarsInfo.llvmVars,
2196 privateVarsInfo.blockArgs)) {
2197 if (mightUseDeviceSharedMem && omp::allocaUsesRequireSharedMem(blockArg)) {
2198 ompBuilder->createOMPFreeShared(
2199 builder, llvmPrivVar,
2200 moduleTranslation.convertType(privDecl.getType()));
2201 }
2202 }
2203
2204 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2205 for (const PrivateVarsInfo::AllocatorPrivateInfo &allocation :
2206 llvm::reverse(privateVarsInfo.allocatorPrivates))
2207 ompBuilder->createOMPFree(ompLoc, allocation.allocatedPtr,
2208 allocation.allocator);
2209
2210 return success();
2211}
2212
2213/// Returns true if the construct contains omp.cancel or omp.cancellation_point
2215 // omp.cancel and omp.cancellation_point must be "closely nested" so they will
2216 // be visible and not inside of function calls. This is enforced by the
2217 // verifier.
2218 return op
2219 ->walk([](Operation *child) {
2220 if (mlir::isa<omp::CancelOp, omp::CancellationPointOp>(child))
2221 return WalkResult::interrupt();
2222 return WalkResult::advance();
2223 })
2224 .wasInterrupted();
2225}
2226
2227// Forward declarations for the task-reduction helpers defined alongside the
2228// omp.taskgroup lowering further down in this file. These are shared by the
2229// `reduction(task, ...)` modifier lowering on the parallel/worksharing
2230// constructs and by the omp.taskgroup / omp.taskloop.context task_reduction
2231// lowering. When \p isModifier is set, `__kmpc_taskred_modifier_init` is
2232// emitted (opening a task-reduction scope) instead of `__kmpc_taskred_init`,
2233// with \p isWorksharing selecting the runtime `is_ws` argument.
2234static llvm::Value *emitTaskReductionInitCall(
2236 ArrayRef<llvm::Value *> origPtrs, StringRef helperNamePrefix,
2237 llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder::InsertPointTy allocaIP,
2238 LLVM::ModuleTranslation &moduleTranslation, bool isModifier = false,
2239 bool isWorksharing = false);
2240static void
2241emitTaskReductionModifierFini(bool isWorksharing, llvm::IRBuilderBase &builder,
2242 LLVM::ModuleTranslation &moduleTranslation);
2243
2244static LogicalResult
2245convertOmpSections(Operation &opInst, llvm::IRBuilderBase &builder,
2246 LLVM::ModuleTranslation &moduleTranslation) {
2247 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2248 using StorableBodyGenCallbackTy =
2249 llvm::OpenMPIRBuilder::StorableBodyGenCallbackTy;
2250
2251 auto sectionsOp = cast<omp::SectionsOp>(opInst);
2252
2253 if (failed(checkImplementationStatus(opInst)))
2254 return failure();
2255
2256 llvm::ArrayRef<bool> isByRef = getIsByRef(sectionsOp.getReductionByref());
2257 assert(isByRef.size() == sectionsOp.getNumReductionVars());
2258
2260 collectReductionDecls(sectionsOp, reductionDecls);
2261 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
2262 findAllocInsertPoints(builder, moduleTranslation);
2263
2264 SmallVector<llvm::Value *> privateReductionVariables(
2265 sectionsOp.getNumReductionVars());
2266 DenseMap<Value, llvm::Value *> reductionVariableMap;
2267
2268 MutableArrayRef<BlockArgument> reductionArgs =
2269 cast<omp::BlockArgOpenMPOpInterface>(opInst).getReductionBlockArgs();
2270
2272 sectionsOp, reductionArgs, builder, moduleTranslation, allocaIP,
2273 reductionDecls, privateReductionVariables, reductionVariableMap,
2274 isByRef)))
2275 return failure();
2276
2277 bool isTaskReductionMod =
2278 sectionsOp.getReductionMod() == omp::ReductionModifier::task &&
2279 sectionsOp.getNumReductionVars() > 0;
2280
2282
2283 for (Operation &op : *sectionsOp.getRegion().begin()) {
2284 auto sectionOp = dyn_cast<omp::SectionOp>(op);
2285 if (!sectionOp) // omp.terminator
2286 continue;
2287
2288 Region &region = sectionOp.getRegion();
2289 auto sectionCB = [&sectionsOp, &region, &builder, &moduleTranslation](
2290 InsertPointTy allocaIP, InsertPointTy codeGenIP,
2291 ArrayRef<llvm::BasicBlock *> deallocBlocks) {
2292 builder.restoreIP(codeGenIP);
2293
2294 // map the omp.section reduction block argument to the omp.sections block
2295 // arguments
2296 // TODO: this assumes that the only block arguments are reduction
2297 // variables
2298 assert(region.getNumArguments() ==
2299 sectionsOp.getRegion().getNumArguments());
2300 for (auto [sectionsArg, sectionArg] : llvm::zip_equal(
2301 sectionsOp.getRegion().getArguments(), region.getArguments())) {
2302 llvm::Value *llvmVal = moduleTranslation.lookupValue(sectionsArg);
2303 assert(llvmVal);
2304 moduleTranslation.mapValue(sectionArg, llvmVal);
2305 }
2306
2307 return convertOmpOpRegions(region, "omp.section.region", builder,
2308 moduleTranslation)
2309 .takeError();
2310 };
2311 sectionCBs.push_back(sectionCB);
2312 }
2313
2314 // No sections within omp.sections operation - skip generation. This situation
2315 // is only possible if there is only a terminator operation inside the
2316 // sections operation
2317 if (sectionCBs.empty())
2318 return success();
2319
2320 // For `reduction(task, ...)` open a task-reduction scope for the worksharing
2321 // region. Participating explicit tasks accumulate into the per-thread private
2322 // copies, which the worksharing reduction then combines across threads. This
2323 // is emitted only after the empty-sections early return above, so it stays
2324 // balanced with the matching fini emitted after the sections region.
2325 if (isTaskReductionMod &&
2326 !emitTaskReductionInitCall(reductionDecls, privateReductionVariables,
2327 "__omp_taskred_mod_", builder, allocaIP,
2328 moduleTranslation, /*isModifier=*/true,
2329 /*isWorksharing=*/true))
2330 return sectionsOp.emitError(
2331 "failed to emit task reduction modifier initialization");
2332
2333 assert(isa<omp::SectionOp>(*sectionsOp.getRegion().op_begin()));
2334
2335 // TODO: Perform appropriate actions according to the data-sharing
2336 // attribute (shared, private, firstprivate, ...) of variables.
2337 // Currently defaults to shared.
2338 auto privCB = [&](InsertPointTy, InsertPointTy codeGenIP, llvm::Value &,
2339 llvm::Value &vPtr, llvm::Value *&replacementValue)
2340 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
2341 replacementValue = &vPtr;
2342 return codeGenIP;
2343 };
2344
2345 // TODO: Perform finalization actions for variables. This has to be
2346 // called for variables which have destructors/finalizers.
2347 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
2348
2349 allocaIP = findAllocInsertPoints(builder, moduleTranslation);
2350 bool isCancellable = constructIsCancellable(sectionsOp);
2351 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2352 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2353 moduleTranslation.getOpenMPBuilder()->createSections(
2354 ompLoc, allocaIP, sectionCBs, privCB, finiCB, isCancellable,
2355 sectionsOp.getNowait());
2356
2357 if (failed(handleError(afterIP, opInst)))
2358 return failure();
2359
2360 builder.restoreIP(*afterIP);
2361
2362 // Close the task-reduction scope before combining the worksharing copies.
2363 if (isTaskReductionMod)
2364 emitTaskReductionModifierFini(/*isWorksharing=*/true, builder,
2365 moduleTranslation);
2366
2367 // Process the reductions if required.
2369 sectionsOp, builder, moduleTranslation, allocaIP, reductionDecls,
2370 privateReductionVariables, isByRef, sectionsOp.getNowait());
2371}
2372
2373/// Converts an OpenMP scope construct into LLVM IR.
2374static LogicalResult
2375convertOmpScope(omp::ScopeOp &scopeOp, llvm::IRBuilderBase &builder,
2376 LLVM::ModuleTranslation &moduleTranslation) {
2377 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2378 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
2379
2380 if (failed(checkImplementationStatus(*scopeOp)))
2381 return failure();
2382
2383 llvm::ArrayRef<bool> isByRef = getIsByRef(scopeOp.getReductionByref());
2384 assert(isByRef.size() == scopeOp.getNumReductionVars());
2385
2386 PrivateVarsInfo privateVarsInfo(scopeOp);
2387
2389 collectReductionDecls(scopeOp, reductionDecls);
2390 InsertPointTy allocaIP = findAllocInsertPoints(builder, moduleTranslation);
2391
2392 SmallVector<llvm::Value *> privateReductionVariables(
2393 scopeOp.getNumReductionVars());
2394 DenseMap<Value, llvm::Value *> reductionVariableMap;
2395
2396 MutableArrayRef<BlockArgument> reductionArgs =
2397 cast<omp::BlockArgOpenMPOpInterface>(*scopeOp).getReductionBlockArgs();
2398
2399 // Allocate private vars before the scope body
2401 scopeOp, builder, moduleTranslation, privateVarsInfo, allocaIP);
2402 if (failed(handleError(afterAllocas, *scopeOp)))
2403 return failure();
2404
2406 scopeOp, reductionArgs, builder, moduleTranslation, allocaIP,
2407 reductionDecls, privateReductionVariables, reductionVariableMap,
2408 isByRef)))
2409 return failure();
2410
2411 auto bodyCB =
2412 [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
2413 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
2414 builder.restoreIP(codeGenIP);
2415
2416 if (handleError(
2417 initPrivateVars(builder, moduleTranslation, privateVarsInfo),
2418 *scopeOp)
2419 .failed())
2420 return llvm::make_error<PreviouslyReportedError>();
2421
2422 if (failed(copyFirstPrivateVars(
2423 scopeOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
2424 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
2425 scopeOp.getPrivateNeedsBarrier())))
2426 return llvm::make_error<PreviouslyReportedError>();
2427
2428 return convertOmpOpRegions(scopeOp.getRegion(), "omp.scope.region", builder,
2429 moduleTranslation)
2430 .takeError();
2431 };
2432
2433 auto finiCB = [&](InsertPointTy codeGenIP) -> llvm::Error {
2434 InsertPointTy oldIP = builder.saveIP();
2435 builder.restoreIP(codeGenIP);
2436 if (failed(cleanupPrivateVars(scopeOp, builder, moduleTranslation,
2437 scopeOp.getLoc(), privateVarsInfo)))
2438 return llvm::make_error<PreviouslyReportedError>();
2439 builder.restoreIP(oldIP);
2440 return llvm::Error::success();
2441 };
2442
2443 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2444 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2445 ompBuilder->createScope(ompLoc, bodyCB, finiCB, scopeOp.getNowait());
2446
2447 if (failed(handleError(afterIP, *scopeOp)))
2448 return failure();
2449
2450 builder.restoreIP(*afterIP);
2451
2452 // Process the reductions if required.
2454 scopeOp, builder, moduleTranslation, allocaIP, reductionDecls,
2455 privateReductionVariables, isByRef, scopeOp.getNowait(),
2456 /*isTeamsReduction=*/false);
2457}
2458
2459/// Converts an OpenMP single construct into LLVM IR using OpenMPIRBuilder.
2460static LogicalResult
2461convertOmpSingle(omp::SingleOp &singleOp, llvm::IRBuilderBase &builder,
2462 LLVM::ModuleTranslation &moduleTranslation) {
2463 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2464 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2465
2466 if (failed(checkImplementationStatus(*singleOp)))
2467 return failure();
2468
2469 auto bodyCB = [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
2470 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) {
2471 builder.restoreIP(codegenIP);
2472 return convertOmpOpRegions(singleOp.getRegion(), "omp.single.region",
2473 builder, moduleTranslation)
2474 .takeError();
2475 };
2476 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
2477
2478 // Handle copyprivate
2479 Operation::operand_range cpVars = singleOp.getCopyprivateVars();
2480 std::optional<ArrayAttr> cpFuncs = singleOp.getCopyprivateSyms();
2483 for (size_t i = 0, e = cpVars.size(); i < e; ++i) {
2484 llvmCPVars.push_back(moduleTranslation.lookupValue(cpVars[i]));
2486 singleOp, cast<SymbolRefAttr>((*cpFuncs)[i]));
2487 llvmCPFuncs.push_back(
2488 moduleTranslation.lookupFunction(llvmFuncOp.getName()));
2489 }
2490
2491 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2492 moduleTranslation.getOpenMPBuilder()->createSingle(
2493 ompLoc, bodyCB, finiCB, singleOp.getNowait(), llvmCPVars,
2494 llvmCPFuncs);
2495
2496 if (failed(handleError(afterIP, *singleOp)))
2497 return failure();
2498
2499 builder.restoreIP(*afterIP);
2500 return success();
2501}
2502
2503static omp::DistributeOp
2505 // Early return if we found more than one distribute op or if we can't find
2506 // any distribute op in the teams region.
2507 omp::DistributeOp distOp;
2508 WalkResult walk = teamsOp.getRegion().walk([&](omp::DistributeOp op) {
2509 if (distOp)
2510 return WalkResult::interrupt();
2511 distOp = op;
2512 return WalkResult::skip();
2513 });
2514 if (walk.wasInterrupted() || !distOp)
2515 return {};
2516
2517 auto iface =
2518 llvm::cast<mlir::omp::BlockArgOpenMPOpInterface>(teamsOp.getOperation());
2519 // Check that all uses of the reduction block arg has the same distribute op
2520 // parent.
2522 for (auto ra : iface.getReductionBlockArgs())
2523 for (auto &use : ra.getUses()) {
2524 auto *useOp = use.getOwner();
2525 // Ignore debug uses.
2526 if (mlir::isa<LLVM::DbgDeclareOp, LLVM::DbgValueOp>(useOp)) {
2527 debugUses.push_back(useOp);
2528 continue;
2529 }
2530 if (!distOp->isProperAncestor(useOp))
2531 return {};
2532 }
2533
2534 // If we are going to use distribute reduction then remove any debug uses of
2535 // the reduction parameters in teamsOp. Otherwise they will be left without
2536 // any mapped value in moduleTranslation and will eventually error out.
2537 for (auto *use : debugUses)
2538 use->erase();
2539 return distOp;
2540}
2541
2542// Convert an OpenMP Teams construct to LLVM IR using OpenMPIRBuilder
2543static LogicalResult
2544convertOmpTeams(omp::TeamsOp op, llvm::IRBuilderBase &builder,
2545 LLVM::ModuleTranslation &moduleTranslation) {
2546 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2547 if (failed(checkImplementationStatus(*op)))
2548 return failure();
2549
2550 DenseMap<Value, llvm::Value *> reductionVariableMap;
2551 unsigned numReductionVars = op.getNumReductionVars();
2553 SmallVector<llvm::Value *> privateReductionVariables(numReductionVars);
2554 llvm::ArrayRef<bool> isByRef;
2555 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
2556 findAllocInsertPoints(builder, moduleTranslation);
2557
2558 // Only do teams reduction if there is no distribute op that captures the
2559 // reduction instead.
2560 bool doTeamsReduction = !getDistributeCapturingTeamsReduction(op);
2561 if (doTeamsReduction) {
2562 isByRef = getIsByRef(op.getReductionByref());
2563
2564 assert(isByRef.size() == op.getNumReductionVars());
2565
2566 MutableArrayRef<BlockArgument> reductionArgs =
2567 llvm::cast<omp::BlockArgOpenMPOpInterface>(*op).getReductionBlockArgs();
2568
2569 collectReductionDecls(op, reductionDecls);
2570
2572 op, reductionArgs, builder, moduleTranslation, allocaIP,
2573 reductionDecls, privateReductionVariables, reductionVariableMap,
2574 isByRef)))
2575 return failure();
2576 }
2577
2578 auto bodyCB = [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
2579 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) {
2581 moduleTranslation, allocaIP, deallocBlocks);
2582 builder.restoreIP(codegenIP);
2583 return convertOmpOpRegions(op.getRegion(), "omp.teams.region", builder,
2584 moduleTranslation)
2585 .takeError();
2586 };
2587
2588 llvm::Value *numTeamsLower = nullptr;
2589 if (Value numTeamsLowerVar = op.getNumTeamsLower())
2590 numTeamsLower = moduleTranslation.lookupValue(numTeamsLowerVar);
2591
2592 llvm::Value *numTeamsUpper = nullptr;
2593 if (!op.getNumTeamsUpperVars().empty())
2594 numTeamsUpper = moduleTranslation.lookupValue(op.getNumTeams(0));
2595
2596 llvm::Value *threadLimit = nullptr;
2597 if (!op.getThreadLimitVars().empty())
2598 threadLimit = moduleTranslation.lookupValue(op.getThreadLimit(0));
2599
2600 llvm::Value *ifExpr = nullptr;
2601 if (Value ifVar = op.getIfExpr())
2602 ifExpr = moduleTranslation.lookupValue(ifVar);
2603
2604 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2605 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2606 moduleTranslation.getOpenMPBuilder()->createTeams(
2607 ompLoc, bodyCB, numTeamsLower, numTeamsUpper, threadLimit, ifExpr);
2608
2609 if (failed(handleError(afterIP, *op)))
2610 return failure();
2611
2612 builder.restoreIP(*afterIP);
2613 if (doTeamsReduction) {
2614 // Process the reductions if required.
2616 op, builder, moduleTranslation, allocaIP, reductionDecls,
2617 privateReductionVariables, isByRef,
2618 /*isNoWait*/ false, /*isTeamsReduction*/ true);
2619 }
2620 return success();
2621}
2622
2623static llvm::omp::RTLDependenceKindTy
2624convertDependKind(mlir::omp::ClauseTaskDepend kind) {
2625 switch (kind) {
2626 case mlir::omp::ClauseTaskDepend::taskdependin:
2627 return llvm::omp::RTLDependenceKindTy::DepIn;
2628 // The OpenMP runtime requires that the codegen for 'depend' clause for
2629 // 'out' dependency kind must be the same as codegen for 'depend' clause
2630 // with 'inout' dependency.
2631 case mlir::omp::ClauseTaskDepend::taskdependout:
2632 case mlir::omp::ClauseTaskDepend::taskdependinout:
2633 return llvm::omp::RTLDependenceKindTy::DepInOut;
2634 case mlir::omp::ClauseTaskDepend::taskdependmutexinoutset:
2635 return llvm::omp::RTLDependenceKindTy::DepMutexInOutSet;
2636 case mlir::omp::ClauseTaskDepend::taskdependinoutset:
2637 return llvm::omp::RTLDependenceKindTy::DepInOutSet;
2638 }
2639 llvm_unreachable("unhandled depend kind");
2640}
2641
2643 std::optional<ArrayAttr> dependKinds, OperandRange dependVars,
2644 LLVM::ModuleTranslation &moduleTranslation,
2646 if (dependVars.empty())
2647 return;
2648 for (auto dep : llvm::zip(dependVars, dependKinds->getValue())) {
2649 auto kind =
2650 cast<mlir::omp::ClauseTaskDependAttr>(std::get<1>(dep)).getValue();
2651 llvm::omp::RTLDependenceKindTy type = convertDependKind(kind);
2652 llvm::Value *depVal = moduleTranslation.lookupValue(std::get<0>(dep));
2653 llvm::OpenMPIRBuilder::DependData dd(type, depVal->getType(), depVal);
2654 dds.emplace_back(dd);
2655 }
2656}
2657
2658/// Shared implementation of a callback which adds a termiator for the new block
2659/// created for the branch taken when an openmp construct is cancelled. The
2660/// terminator is saved in \p cancelTerminators. This callback is invoked only
2661/// if there is cancellation inside of the taskgroup body.
2662/// The terminator will need to be fixed to branch to the correct block to
2663/// cleanup the construct.
2665 SmallVectorImpl<llvm::UncondBrInst *> &cancelTerminators,
2666 llvm::IRBuilderBase &llvmBuilder, llvm::OpenMPIRBuilder &ompBuilder,
2667 mlir::Operation *op, llvm::omp::Directive cancelDirective) {
2668 auto finiCB = [&](llvm::OpenMPIRBuilder::InsertPointTy ip) -> llvm::Error {
2669 llvm::IRBuilderBase::InsertPointGuard guard(llvmBuilder);
2670
2671 // ip is currently in the block branched to if cancellation occurred.
2672 // We need to create a branch to terminate that block.
2673 llvmBuilder.restoreIP(ip);
2674
2675 // We must still clean up the construct after cancelling it, so we need to
2676 // branch to the block that finalizes the taskgroup.
2677 // That block has not been created yet so use this block as a dummy for now
2678 // and fix this after creating the operation.
2679 cancelTerminators.push_back(llvmBuilder.CreateBr(ip.getBlock()));
2680 return llvm::Error::success();
2681 };
2682 // We have to add the cleanup to the OpenMPIRBuilder before the body gets
2683 // created in case the body contains omp.cancel (which will then expect to be
2684 // able to find this cleanup callback).
2685 ompBuilder.pushFinalizationCB(
2686 {finiCB, cancelDirective, constructIsCancellable(op)});
2687}
2688
2689/// If we cancelled the construct, we should branch to the finalization block of
2690/// that construct. OMPIRBuilder structures the CFG such that the cleanup block
2691/// is immediately before the continuation block. Now this finalization has
2692/// been created we can fix the branch.
2693static void
2695 llvm::OpenMPIRBuilder &ompBuilder,
2696 const llvm::OpenMPIRBuilder::InsertPointTy &afterIP) {
2697 ompBuilder.popFinalizationCB();
2698 llvm::BasicBlock *constructFini = afterIP.getBlock()->getSinglePredecessor();
2699 for (llvm::UncondBrInst *cancelBranch : cancelTerminators)
2700 cancelBranch->setSuccessor(constructFini);
2701}
2702
2703namespace {
2704/// TaskContextStructManager takes care of creating and freeing a structure
2705/// containing information needed by the task body to execute.
2706class TaskContextStructManager {
2707public:
2708 TaskContextStructManager(llvm::IRBuilderBase &builder,
2709 LLVM::ModuleTranslation &moduleTranslation,
2710 MutableArrayRef<omp::PrivateClauseOp> privateDecls)
2711 : builder{builder}, moduleTranslation{moduleTranslation},
2712 privateDecls{privateDecls} {}
2713
2714 /// Creates a heap allocated struct containing space for each private
2715 /// variable. Invariant: privateVarTypes, privateDecls, and the elements of
2716 /// the structure should all have the same order (although privateDecls which
2717 /// do not read from the mold argument are skipped).
2718 void generateTaskContextStruct();
2719
2720 /// Create GEPs to access each member of the structure representing a private
2721 /// variable, adding them to llvmPrivateVars. Null values are added where
2722 /// private decls were skipped so that the ordering continues to match the
2723 /// private decls.
2724 void createGEPsToPrivateVars();
2725
2726 /// Given the address of the structure, return a GEP for each private variable
2727 /// in the structure. Null values are added where private decls were skipped
2728 /// so that the ordering continues to match the private decls.
2729 /// Must be called after generateTaskContextStruct().
2730 SmallVector<llvm::Value *>
2731 createGEPsToPrivateVars(llvm::Value *altStructPtr) const;
2732
2733 /// De-allocate the task context structure.
2734 void freeStructPtr();
2735
2736 MutableArrayRef<llvm::Value *> getLLVMPrivateVarGEPs() {
2737 return llvmPrivateVarGEPs;
2738 }
2739
2740 llvm::Value *getStructPtr() { return structPtr; }
2741
2742private:
2743 llvm::IRBuilderBase &builder;
2744 LLVM::ModuleTranslation &moduleTranslation;
2745 MutableArrayRef<omp::PrivateClauseOp> privateDecls;
2746
2747 /// The type of each member of the structure, in order.
2748 SmallVector<llvm::Type *> privateVarTypes;
2749
2750 /// LLVM values for each private variable, or null if that private variable is
2751 /// not included in the task context structure
2752 SmallVector<llvm::Value *> llvmPrivateVarGEPs;
2753
2754 /// A pointer to the structure containing context for this task.
2755 llvm::Value *structPtr = nullptr;
2756 /// The type of the structure
2757 llvm::Type *structTy = nullptr;
2758};
2759
2760/// IteratorInfo extracts and prepares loop bounds information from an
2761/// mlir::omp::IteratorOp for lowering to LLVM IR.
2762///
2763/// It computes the per-dimension trip counts and the total linearized trip
2764/// count, casted to i64. These are used to build a canonical loop and to
2765/// reconstruct the physical induction variables inside the loop body.
2766class IteratorInfo {
2767private:
2768 llvm::SmallVector<llvm::Value *> lowerBounds;
2769 llvm::SmallVector<llvm::Value *> upperBounds;
2770 llvm::SmallVector<llvm::Value *> steps;
2771 llvm::SmallVector<llvm::Value *> trips;
2772 unsigned dims;
2773 llvm::Value *totalTrips;
2774
2775 llvm::Value *lookUpAsI64(mlir::Value val, const LLVM::ModuleTranslation &mt,
2776 llvm::IRBuilderBase &builder) {
2777 llvm::Value *v = mt.lookupValue(val);
2778 if (!v)
2779 return nullptr;
2780 if (v->getType()->isIntegerTy(64))
2781 return v;
2782 if (v->getType()->isIntegerTy())
2783 return builder.CreateSExtOrTrunc(v, builder.getInt64Ty());
2784 return nullptr;
2785 }
2786
2787public:
2788 IteratorInfo(mlir::omp::IteratorOp itersOp,
2789 mlir::LLVM::ModuleTranslation &moduleTranslation,
2790 llvm::IRBuilderBase &builder) {
2791 dims = itersOp.getLoopLowerBounds().size();
2792 lowerBounds.resize(dims);
2793 upperBounds.resize(dims);
2794 steps.resize(dims);
2795 trips.resize(dims);
2796
2797 for (unsigned d = 0; d < dims; ++d) {
2798 llvm::Value *lb = lookUpAsI64(itersOp.getLoopLowerBounds()[d],
2799 moduleTranslation, builder);
2800 llvm::Value *ub = lookUpAsI64(itersOp.getLoopUpperBounds()[d],
2801 moduleTranslation, builder);
2802 llvm::Value *st =
2803 lookUpAsI64(itersOp.getLoopSteps()[d], moduleTranslation, builder);
2804 assert(lb && ub && st &&
2805 "Expect lowerBounds, upperBounds, and steps in IteratorOp");
2806 assert((!llvm::isa<llvm::ConstantInt>(st) ||
2807 !llvm::cast<llvm::ConstantInt>(st)->isZero()) &&
2808 "Expect non-zero step in IteratorOp");
2809
2810 lowerBounds[d] = lb;
2811 upperBounds[d] = ub;
2812 steps[d] = st;
2813
2814 // trips = ((ub - lb) / step) + 1 (inclusive ub, assume positive step)
2815 llvm::Value *diff = builder.CreateSub(ub, lb);
2816 llvm::Value *div = builder.CreateSDiv(diff, st);
2817 trips[d] = builder.CreateAdd(
2818 div, llvm::ConstantInt::get(builder.getInt64Ty(), 1));
2819 }
2820
2821 totalTrips = llvm::ConstantInt::get(builder.getInt64Ty(), 1);
2822 for (unsigned d = 0; d < dims; ++d)
2823 totalTrips = builder.CreateMul(totalTrips, trips[d]);
2824 }
2825
2826 unsigned getDims() const { return dims; }
2827 llvm::ArrayRef<llvm::Value *> getLowerBounds() const { return lowerBounds; }
2828 llvm::ArrayRef<llvm::Value *> getUpperBounds() const { return upperBounds; }
2829 llvm::ArrayRef<llvm::Value *> getSteps() const { return steps; }
2830 llvm::ArrayRef<llvm::Value *> getTrips() const { return trips; }
2831 llvm::Value *getTotalTrips() const { return totalTrips; }
2832};
2833
2834} // namespace
2835
2836void TaskContextStructManager::generateTaskContextStruct() {
2837 if (privateDecls.empty())
2838 return;
2839 privateVarTypes.reserve(privateDecls.size());
2840
2841 for (omp::PrivateClauseOp &privOp : privateDecls) {
2842 // Skip private variables which can safely be allocated and initialised
2843 // inside of the task
2844 if (!privOp.readsFromMold())
2845 continue;
2846 Type mlirType = privOp.getType();
2847 privateVarTypes.push_back(moduleTranslation.convertType(mlirType));
2848 }
2849
2850 if (privateVarTypes.empty())
2851 return;
2852
2853 structTy = llvm::StructType::get(moduleTranslation.getLLVMContext(),
2854 privateVarTypes);
2855
2856 llvm::DataLayout dataLayout =
2857 builder.GetInsertBlock()->getModule()->getDataLayout();
2858 llvm::Type *intPtrTy = builder.getIntPtrTy(dataLayout);
2859 llvm::Constant *allocSize = llvm::ConstantExpr::getSizeOf(structTy);
2860
2861 // Heap allocate the structure
2862 structPtr = builder.CreateMalloc(intPtrTy, allocSize,
2863 /*ArraySize=*/nullptr, /*MallocF=*/nullptr,
2864 "omp.task.context_ptr");
2865}
2866
2867SmallVector<llvm::Value *> TaskContextStructManager::createGEPsToPrivateVars(
2868 llvm::Value *altStructPtr) const {
2869 SmallVector<llvm::Value *> ret;
2870
2871 // Create GEPs for each struct member
2872 ret.reserve(privateDecls.size());
2873 llvm::Value *zero = builder.getInt32(0);
2874 unsigned i = 0;
2875 for (auto privDecl : privateDecls) {
2876 if (!privDecl.readsFromMold()) {
2877 // Handle this inside of the task so we don't pass unnessecary vars in
2878 ret.push_back(nullptr);
2879 continue;
2880 }
2881 llvm::Value *iVal = builder.getInt32(i);
2882 llvm::Value *gep = builder.CreateGEP(structTy, altStructPtr, {zero, iVal});
2883 ret.push_back(gep);
2884 i += 1;
2885 }
2886 return ret;
2887}
2888
2889void TaskContextStructManager::createGEPsToPrivateVars() {
2890 if (!structPtr)
2891 assert(privateVarTypes.empty());
2892 // Still need to run createGEPsToPrivateVars to populate llvmPrivateVarGEPs
2893 // with null values for skipped private decls
2894
2895 llvmPrivateVarGEPs = createGEPsToPrivateVars(structPtr);
2896}
2897
2898void TaskContextStructManager::freeStructPtr() {
2899 if (!structPtr)
2900 return;
2901
2902 llvm::IRBuilderBase::InsertPointGuard guard{builder};
2903 // Ensure we don't put the call to free() after the terminator
2904 builder.SetInsertPoint(builder.GetInsertBlock()->getTerminator());
2905 builder.CreateFree(structPtr);
2906}
2907
2908static void storeAffinityEntry(llvm::IRBuilderBase &builder,
2909 llvm::OpenMPIRBuilder &ompBuilder,
2910 llvm::Value *affinityList, llvm::Value *index,
2911 llvm::Value *addr, llvm::Value *len) {
2912 llvm::StructType *kmpTaskAffinityInfoTy =
2913 ompBuilder.getKmpTaskAffinityInfoTy();
2914 llvm::Value *entry = builder.CreateInBoundsGEP(
2915 kmpTaskAffinityInfoTy, affinityList, index, "omp.affinity.entry");
2916
2917 addr = builder.CreatePtrToInt(addr, kmpTaskAffinityInfoTy->getElementType(0));
2918 len = builder.CreateIntCast(len, kmpTaskAffinityInfoTy->getElementType(1),
2919 /*isSigned=*/false);
2920 llvm::Value *flags = builder.getInt32(0);
2921
2922 builder.CreateStore(addr,
2923 builder.CreateStructGEP(kmpTaskAffinityInfoTy, entry, 0));
2924 builder.CreateStore(len,
2925 builder.CreateStructGEP(kmpTaskAffinityInfoTy, entry, 1));
2926 builder.CreateStore(flags,
2927 builder.CreateStructGEP(kmpTaskAffinityInfoTy, entry, 2));
2928}
2929
2931 llvm::IRBuilderBase &builder,
2932 LLVM::ModuleTranslation &moduleTranslation,
2933 llvm::Value *affinityList) {
2934 for (auto [i, affinityVar] : llvm::enumerate(affinityVars)) {
2935 auto entryOp = affinityVar.getDefiningOp<mlir::omp::AffinityEntryOp>();
2936 assert(entryOp && "affinity item must be omp.affinity_entry");
2937
2938 llvm::Value *addr = moduleTranslation.lookupValue(entryOp.getAddr());
2939 llvm::Value *len = moduleTranslation.lookupValue(entryOp.getLen());
2940 assert(addr && len && "expect affinity addr and len to be non-null");
2941 storeAffinityEntry(builder, *moduleTranslation.getOpenMPBuilder(),
2942 affinityList, builder.getInt64(i), addr, len);
2943 }
2944}
2945
2946static mlir::LogicalResult
2947convertIteratorRegion(llvm::Value *linearIV, IteratorInfo &iterInfo,
2948 mlir::Block &iteratorRegionBlock,
2949 llvm::IRBuilderBase &builder,
2950 LLVM::ModuleTranslation &moduleTranslation) {
2951 llvm::Value *tmp = linearIV;
2952 for (int d = (int)iterInfo.getDims() - 1; d >= 0; --d) {
2953 llvm::Value *trip = iterInfo.getTrips()[d];
2954 // idx_d = tmp % trip_d
2955 llvm::Value *idx = builder.CreateURem(tmp, trip);
2956 // tmp = tmp / trip_d
2957 tmp = builder.CreateUDiv(tmp, trip);
2958
2959 // physIV_d = lb_d + idx_d * step_d
2960 llvm::Value *physIV = builder.CreateAdd(
2961 iterInfo.getLowerBounds()[d],
2962 builder.CreateMul(idx, iterInfo.getSteps()[d]), "omp.it.phys_iv");
2963
2964 moduleTranslation.mapValue(iteratorRegionBlock.getArgument(d), physIV);
2965 }
2966
2967 // Translate the iterator region into the loop body.
2968 moduleTranslation.mapBlock(&iteratorRegionBlock, builder.GetInsertBlock());
2969 if (mlir::failed(moduleTranslation.convertBlock(iteratorRegionBlock,
2970 /*ignoreArguments=*/true,
2971 builder))) {
2972 return mlir::failure();
2973 }
2974 return mlir::success();
2975}
2976
2978 llvm::function_ref<void(llvm::Value *linearIV, mlir::omp::YieldOp yield)>;
2979
2980static mlir::LogicalResult
2981fillIteratorLoop(mlir::omp::IteratorOp itersOp, llvm::IRBuilderBase &builder,
2982 mlir::LLVM::ModuleTranslation &moduleTranslation,
2983 IteratorInfo &iterInfo, llvm::StringRef loopName,
2984 IteratorStoreEntryTy genStoreEntry) {
2985 mlir::Region &itersRegion = itersOp.getRegion();
2986 mlir::Block &iteratorRegionBlock = itersRegion.front();
2987
2988 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
2989
2990 auto bodyGen = [&](llvm::OpenMPIRBuilder::InsertPointTy bodyIP,
2991 llvm::Value *linearIV) -> llvm::Error {
2992 llvm::IRBuilderBase::InsertPointGuard guard(builder);
2993 builder.restoreIP(bodyIP);
2994
2995 if (failed(convertIteratorRegion(linearIV, iterInfo, iteratorRegionBlock,
2996 builder, moduleTranslation))) {
2997 return llvm::make_error<llvm::StringError>(
2998 "failed to convert iterator region", llvm::inconvertibleErrorCode());
2999 }
3000
3001 auto yield =
3002 mlir::dyn_cast<mlir::omp::YieldOp>(iteratorRegionBlock.getTerminator());
3003 assert(yield && yield.getResults().size() == 1 &&
3004 "expect omp.yield in iterator region to have one result");
3005
3006 genStoreEntry(linearIV, yield);
3007
3008 // Iterator-region block/value mappings are temporary for this conversion,
3009 // clear them to avoid stale entries in ModuleTranslation.
3010 moduleTranslation.forgetMapping(itersRegion);
3011
3012 return llvm::Error::success();
3013 };
3014
3015 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
3016 moduleTranslation.getOpenMPBuilder()->createIteratorLoop(
3017 loc, iterInfo.getTotalTrips(), bodyGen, loopName);
3018 if (failed(handleError(afterIP, *itersOp)))
3019 return failure();
3020
3021 builder.restoreIP(*afterIP);
3022
3023 return mlir::success();
3024}
3025
3026static mlir::LogicalResult
3027buildAffinityData(mlir::omp::TaskOp &taskOp, llvm::IRBuilderBase &builder,
3028 mlir::LLVM::ModuleTranslation &moduleTranslation,
3029 llvm::OpenMPIRBuilder::AffinityData &ad) {
3030
3031 if (taskOp.getAffinityVars().empty() && taskOp.getIterated().empty()) {
3032 ad.Count = nullptr;
3033 ad.Info = nullptr;
3034 return mlir::success();
3035 }
3036
3038 llvm::StructType *kmpTaskAffinityInfoTy =
3039 moduleTranslation.getOpenMPBuilder()->getKmpTaskAffinityInfoTy();
3040
3041 auto allocateAffinityList = [&](llvm::Value *count) -> llvm::Value * {
3042 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3043 if (llvm::isa<llvm::Constant>(count) || llvm::isa<llvm::Argument>(count))
3044 builder.restoreIP(findAllocInsertPoints(builder, moduleTranslation));
3045 return builder.CreateAlloca(kmpTaskAffinityInfoTy, count,
3046 "omp.affinity_list");
3047 };
3048
3049 auto createAffinity =
3050 [&](llvm::Value *count,
3051 llvm::Value *info) -> llvm::OpenMPIRBuilder::AffinityData {
3052 llvm::OpenMPIRBuilder::AffinityData ad{};
3053 ad.Count = builder.CreateTrunc(count, builder.getInt32Ty());
3054 ad.Info =
3055 builder.CreatePointerBitCastOrAddrSpaceCast(info, builder.getPtrTy(0));
3056 return ad;
3057 };
3058
3059 if (!taskOp.getAffinityVars().empty()) {
3060 llvm::Value *count = llvm::ConstantInt::get(
3061 builder.getInt64Ty(), taskOp.getAffinityVars().size());
3062 llvm::Value *list = allocateAffinityList(count);
3063 fillAffinityLocators(taskOp.getAffinityVars(), builder, moduleTranslation,
3064 list);
3065 ads.emplace_back(createAffinity(count, list));
3066 }
3067
3068 if (!taskOp.getIterated().empty()) {
3069 for (auto [i, iter] : llvm::enumerate(taskOp.getIterated())) {
3070 auto itersOp = iter.getDefiningOp<omp::IteratorOp>();
3071 assert(itersOp && "iterated value must be defined by omp.iterator");
3072 IteratorInfo iterInfo(itersOp, moduleTranslation, builder);
3073 llvm::Value *affList = allocateAffinityList(iterInfo.getTotalTrips());
3074 if (failed(fillIteratorLoop(
3075 itersOp, builder, moduleTranslation, iterInfo, "iterator",
3076 [&](llvm::Value *linearIV, mlir::omp::YieldOp yield) {
3077 auto entryOp = yield.getResults()[0]
3078 .getDefiningOp<mlir::omp::AffinityEntryOp>();
3079 assert(entryOp && "expect yield produce an affinity entry");
3080 llvm::Value *addr =
3081 moduleTranslation.lookupValue(entryOp.getAddr());
3082 llvm::Value *len =
3083 moduleTranslation.lookupValue(entryOp.getLen());
3084 storeAffinityEntry(builder,
3085 *moduleTranslation.getOpenMPBuilder(),
3086 affList, linearIV, addr, len);
3087 })))
3088 return llvm::failure();
3089 ads.emplace_back(createAffinity(iterInfo.getTotalTrips(), affList));
3090 }
3091 }
3092
3093 llvm::Value *totalAffinityCount = builder.getInt32(0);
3094 for (const auto &affinity : ads)
3095 totalAffinityCount = builder.CreateAdd(
3096 totalAffinityCount,
3097 builder.CreateIntCast(affinity.Count, builder.getInt32Ty(),
3098 /*isSigned=*/false));
3099
3100 llvm::Value *affinityInfo = ads.front().Info;
3101 if (ads.size() > 1) {
3102 llvm::StructType *kmpTaskAffinityInfoTy =
3103 moduleTranslation.getOpenMPBuilder()->getKmpTaskAffinityInfoTy();
3104 llvm::Value *affinityInfoElemSize = builder.getInt64(
3105 moduleTranslation.getLLVMModule()->getDataLayout().getTypeAllocSize(
3106 kmpTaskAffinityInfoTy));
3107
3108 llvm::Value *packedAffinityInfo = allocateAffinityList(totalAffinityCount);
3109 llvm::Value *packedAffinityInfoOffset = builder.getInt32(0);
3110 for (const auto &affinity : ads) {
3111 llvm::Value *affinityCount = builder.CreateIntCast(
3112 affinity.Count, builder.getInt32Ty(), /*isSigned=*/false);
3113 llvm::Value *affinityCountInt64 = builder.CreateIntCast(
3114 affinityCount, builder.getInt64Ty(), /*isSigned=*/false);
3115 llvm::Value *affinityInfoSize =
3116 builder.CreateMul(affinityCountInt64, affinityInfoElemSize);
3117
3118 llvm::Value *packedAffinityInfoIndex = builder.CreateIntCast(
3119 packedAffinityInfoOffset, kmpTaskAffinityInfoTy->getElementType(0),
3120 /*isSigned=*/false);
3121 packedAffinityInfoIndex = builder.CreateInBoundsGEP(
3122 kmpTaskAffinityInfoTy, packedAffinityInfo, packedAffinityInfoIndex);
3123
3124 builder.CreateMemCpy(
3125 packedAffinityInfoIndex, llvm::Align(1),
3126 builder.CreatePointerBitCastOrAddrSpaceCast(
3127 affinity.Info, builder.getPtrTy(packedAffinityInfoIndex->getType()
3128 ->getPointerAddressSpace())),
3129 llvm::Align(1), affinityInfoSize);
3130
3131 packedAffinityInfoOffset =
3132 builder.CreateAdd(packedAffinityInfoOffset, affinityCount);
3133 }
3134
3135 affinityInfo = packedAffinityInfo;
3136 }
3137
3138 ad.Count = totalAffinityCount;
3139 ad.Info = affinityInfo;
3140
3141 return mlir::success();
3142}
3143
3144// Allocates a single kmp_dep_info array sized to hold both locator
3145// (non-iterated) and iterated entries, fills the locator entries first, then
3146// runs an iterator loop for each iterator modifier object.
3147static mlir::LogicalResult
3148buildDependData(OperandRange dependVars, std::optional<ArrayAttr> dependKinds,
3149 OperandRange dependIterated,
3150 std::optional<ArrayAttr> dependIteratedKinds,
3151 llvm::IRBuilderBase &builder,
3152 mlir::LLVM::ModuleTranslation &moduleTranslation,
3153 llvm::OpenMPIRBuilder::DependenciesInfo &taskDeps) {
3154 if (dependIterated.empty()) {
3155 buildDependDataLocator(dependKinds, dependVars, moduleTranslation,
3156 taskDeps.Deps);
3157 return mlir::success();
3158 }
3159
3160 llvm::OpenMPIRBuilder &ompBuilder = *moduleTranslation.getOpenMPBuilder();
3161 llvm::Type *dependInfoTy = ompBuilder.DependInfo;
3162 unsigned numLocator = dependVars.size();
3163
3164 // Compute total count: locator deps + sum of iterator trip counts.
3165 llvm::Value *totalCount =
3166 llvm::ConstantInt::get(builder.getInt64Ty(), numLocator);
3167
3169 for (auto iter : dependIterated) {
3170 auto itersOp = iter.getDefiningOp<mlir::omp::IteratorOp>();
3171 assert(itersOp && "depend_iterated value must be defined by omp.iterator");
3172 iterInfos.emplace_back(itersOp, moduleTranslation, builder);
3173 totalCount =
3174 builder.CreateAdd(totalCount, iterInfos.back().getTotalTrips());
3175 }
3176
3177 // Heap-allocate the kmp_depend_info array so we don't risk
3178 // dynamic-sized alloca outside the entry block (e.g. inside loops).
3179 llvm::Constant *allocSize = llvm::ConstantExpr::getSizeOf(dependInfoTy);
3180 llvm::Value *depArray =
3181 builder.CreateMalloc(ompBuilder.SizeTy, allocSize, totalCount,
3182 /*MallocF=*/nullptr, ".dep.arr.addr");
3183
3184 // Fill non-iterated entries at indices [0, numLocator).
3185 if (numLocator > 0) {
3187 buildDependDataLocator(dependKinds, dependVars, moduleTranslation, dds);
3188 for (auto [i, dd] : llvm::enumerate(dds)) {
3189 llvm::Value *idx = llvm::ConstantInt::get(builder.getInt64Ty(), i);
3190 llvm::Value *entry =
3191 builder.CreateInBoundsGEP(dependInfoTy, depArray, idx);
3192 ompBuilder.emitTaskDependency(builder, entry, dd);
3193 }
3194 }
3195
3196 // Fill iterated entries starting at index numLocator.
3197 llvm::Value *offset =
3198 llvm::ConstantInt::get(builder.getInt64Ty(), numLocator);
3199 for (auto [i, iterInfo] : llvm::enumerate(iterInfos)) {
3200 auto kindAttr = cast<mlir::omp::ClauseTaskDependAttr>(
3201 dependIteratedKinds->getValue()[i]);
3202 llvm::omp::RTLDependenceKindTy rtlKind =
3203 convertDependKind(kindAttr.getValue());
3204
3205 auto itersOp = dependIterated[i].getDefiningOp<mlir::omp::IteratorOp>();
3206 if (failed(fillIteratorLoop(
3207 itersOp, builder, moduleTranslation, iterInfo, "dep_iterator",
3208 [&](llvm::Value *linearIV, mlir::omp::YieldOp yield) {
3209 llvm::Value *addr =
3210 moduleTranslation.lookupValue(yield.getResults()[0]);
3211 llvm::Value *idx = builder.CreateAdd(offset, linearIV);
3212 llvm::Value *entry =
3213 builder.CreateInBoundsGEP(dependInfoTy, depArray, idx);
3214 ompBuilder.emitTaskDependency(
3215 builder, entry,
3216 llvm::OpenMPIRBuilder::DependData{rtlKind, addr->getType(),
3217 addr});
3218 })))
3219 return mlir::failure();
3220
3221 // Advance offset by the trip count of this iterator.
3222 offset = builder.CreateAdd(offset, iterInfo.getTotalTrips());
3223 }
3224
3225 taskDeps.DepArray = depArray;
3226 taskDeps.NumDeps = builder.CreateTrunc(totalCount, builder.getInt32Ty());
3227 return mlir::success();
3228}
3229
3230/// Converts an OpenMP task construct into LLVM IR using OpenMPIRBuilder.
3231static LogicalResult
3232convertOmpTaskOp(omp::TaskOp taskOp, llvm::IRBuilderBase &builder,
3233 LLVM::ModuleTranslation &moduleTranslation) {
3234 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
3235 if (failed(checkImplementationStatus(*taskOp)))
3236 return failure();
3237
3238 PrivateVarsInfo privateVarsInfo(taskOp);
3239 TaskContextStructManager taskStructMgr{builder, moduleTranslation,
3240 privateVarsInfo.privatizers};
3241
3242 // Allocate and copy private variables before creating the task. This avoids
3243 // accessing invalid memory if (after this scope ends) the private variables
3244 // are initialized from host variables or if the variables are copied into
3245 // from host variables (firstprivate). The insertion point is just before
3246 // where the code for creating and scheduling the task will go. That puts this
3247 // code outside of the outlined task region, which is what we want because
3248 // this way the initialization and copy regions are executed immediately while
3249 // the host variable data are still live.
3251 InsertPointTy allocaIP =
3252 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
3253
3254 // Not using splitBB() because that requires the current block to have a
3255 // terminator.
3256 assert(builder.GetInsertPoint() == builder.GetInsertBlock()->end());
3257 llvm::BasicBlock *taskStartBlock = llvm::BasicBlock::Create(
3258 builder.getContext(), "omp.task.start",
3259 /*Parent=*/builder.GetInsertBlock()->getParent());
3260 llvm::Instruction *branchToTaskStartBlock = builder.CreateBr(taskStartBlock);
3261 builder.SetInsertPoint(branchToTaskStartBlock);
3262
3263 // Now do this again to make the initialization and copy blocks
3264 llvm::BasicBlock *copyBlock =
3265 splitBB(builder, /*CreateBranch=*/true, "omp.private.copy");
3266 llvm::BasicBlock *initBlock =
3267 splitBB(builder, /*CreateBranch=*/true, "omp.private.init");
3268
3269 // Now the control flow graph should look like
3270 // starter_block:
3271 // <---- where we started when convertOmpTaskOp was called
3272 // br %omp.private.init
3273 // omp.private.init:
3274 // br %omp.private.copy
3275 // omp.private.copy:
3276 // br %omp.task.start
3277 // omp.task.start:
3278 // <---- where we want the insertion point to be when we call createTask()
3279
3280 // Save the alloca insertion point on ModuleTranslation stack for use in
3281 // nested regions.
3283 moduleTranslation, allocaIP, deallocBlocks);
3284
3285 // Allocate and initialize private variables
3286 builder.SetInsertPoint(initBlock->getTerminator());
3287
3288 // Create task variable structure
3289 taskStructMgr.generateTaskContextStruct();
3290 // GEPs so that we can initialize the variables. Don't use these GEPs inside
3291 // of the body otherwise it will be the GEP not the struct which is fowarded
3292 // to the outlined function. GEPs forwarded in this way are passed in a
3293 // stack-allocated (by OpenMPIRBuilder) structure which is not safe for tasks
3294 // which may not be executed until after the current stack frame goes out of
3295 // scope.
3296 taskStructMgr.createGEPsToPrivateVars();
3297
3298 for (auto [privDecl, mlirPrivVar, blockArg, llvmPrivateVarAlloc] :
3299 llvm::zip_equal(privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
3300 privateVarsInfo.blockArgs,
3301 taskStructMgr.getLLVMPrivateVarGEPs())) {
3302 // To be handled inside the task.
3303 if (!privDecl.readsFromMold())
3304 continue;
3305 assert(llvmPrivateVarAlloc &&
3306 "reads from mold so shouldn't have been skipped");
3307
3308 llvm::Expected<llvm::Value *> privateVarOrErr =
3309 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
3310 blockArg, llvmPrivateVarAlloc, initBlock);
3311 if (!privateVarOrErr)
3312 return handleError(privateVarOrErr, *taskOp.getOperation());
3313
3315
3316 // TODO: this is a bit of a hack for Fortran character boxes.
3317 // Character boxes are passed by value into the init region and then the
3318 // initialized character box is yielded by value. Here we need to store the
3319 // yielded value into the private allocation, and load the private
3320 // allocation to match the type expected by region block arguments.
3321 [[maybe_unused]] llvm::Value *llvmPrivateVar = llvmPrivateVarAlloc;
3322 if ((privateVarOrErr.get() != llvmPrivateVarAlloc) &&
3323 !mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3324 builder.CreateStore(privateVarOrErr.get(), llvmPrivateVarAlloc);
3325 // Load it so we have the value pointed to by the GEP
3326 llvmPrivateVar = builder.CreateLoad(privateVarOrErr.get()->getType(),
3327 llvmPrivateVarAlloc);
3328 }
3329 assert(llvmPrivateVar->getType() ==
3330 moduleTranslation.convertType(blockArg.getType()));
3331
3332 // Mapping blockArg -> llvmPrivateVarAlloc is done inside the body callback
3333 // so that OpenMPIRBuilder doesn't try to pass each GEP address through a
3334 // stack allocated structure.
3335 }
3336
3337 // firstprivate copy region
3338 setInsertPointForPossiblyEmptyBlock(builder, copyBlock);
3339 if (failed(copyFirstPrivateVars(
3340 taskOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
3341 taskStructMgr.getLLVMPrivateVarGEPs(), privateVarsInfo.privatizers,
3342 taskOp.getPrivateNeedsBarrier())))
3343 return llvm::failure();
3344
3345 llvm::OpenMPIRBuilder::AffinityData ad;
3346 if (failed(buildAffinityData(taskOp, builder, moduleTranslation, ad)))
3347 return llvm::failure();
3348
3349 // Resolve and validate in_reduction declarations. Byref in_reduction has
3350 // already been rejected by checkImplementationStatus; the helper rejects the
3351 // remaining richer declare_reduction shapes (two-argument initializer,
3352 // cleanup region, missing combiner). This is pure MLIR symbol-table work and
3353 // emits no IR. The matching task_reduction descriptor is registered by an
3354 // enclosing taskgroup; here we only look the per-task storage up at runtime.
3357 taskOp.getOperation(), taskOp.getInReductionSyms(), "omp.task",
3358 "in_reduction", inRedDecls)))
3359 return failure();
3360 SmallVector<llvm::Value *> inRedOrigPtrs;
3361 inRedOrigPtrs.reserve(inRedDecls.size());
3362 for (Value v : taskOp.getInReductionVars())
3363 inRedOrigPtrs.push_back(moduleTranslation.lookupValue(v));
3364
3365 // Set up for call to createTask()
3366 builder.SetInsertPoint(taskStartBlock);
3367
3368 auto bodyCB =
3369 [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
3370 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
3371 // Save the alloca insertion point on ModuleTranslation stack for use in
3372 // nested regions.
3374 moduleTranslation, allocaIP, deallocBlocks);
3375
3376 // translate the body of the task:
3377 builder.restoreIP(codegenIP);
3378
3379 llvm::BasicBlock *privInitBlock = nullptr;
3380 privateVarsInfo.llvmVars.resize(privateVarsInfo.blockArgs.size());
3381 for (auto [i, zip] : llvm::enumerate(llvm::zip_equal(
3382 privateVarsInfo.blockArgs, privateVarsInfo.privatizers,
3383 privateVarsInfo.mlirVars))) {
3384 auto [blockArg, privDecl, mlirPrivVar] = zip;
3385 // This is handled before the task executes
3386 if (privDecl.readsFromMold())
3387 continue;
3388
3389 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3390 llvm::Type *llvmAllocType =
3391 moduleTranslation.convertType(privDecl.getType());
3392 builder.SetInsertPoint(allocaIP.getBlock()->getTerminator());
3393 llvm::Value *llvmPrivateVar = builder.CreateAlloca(
3394 llvmAllocType, /*ArraySize=*/nullptr, "omp.private.alloc");
3395
3396 llvm::Expected<llvm::Value *> privateVarOrError =
3397 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
3398 blockArg, llvmPrivateVar, privInitBlock);
3399 if (!privateVarOrError)
3400 return privateVarOrError.takeError();
3401 moduleTranslation.mapValue(blockArg, privateVarOrError.get());
3402 privateVarsInfo.llvmVars[i] = privateVarOrError.get();
3403 }
3404
3405 taskStructMgr.createGEPsToPrivateVars();
3406 for (auto [i, llvmPrivVar] :
3407 llvm::enumerate(taskStructMgr.getLLVMPrivateVarGEPs())) {
3408 if (!llvmPrivVar) {
3409 assert(privateVarsInfo.llvmVars[i] &&
3410 "This is added in the loop above");
3411 continue;
3412 }
3413 privateVarsInfo.llvmVars[i] = llvmPrivVar;
3414 }
3415
3416 // Find and map the addresses of each variable within the task context
3417 // structure
3418 for (auto [blockArg, llvmPrivateVar, privateDecl] :
3419 llvm::zip_equal(privateVarsInfo.blockArgs, privateVarsInfo.llvmVars,
3420 privateVarsInfo.privatizers)) {
3421 // This was handled above.
3422 if (!privateDecl.readsFromMold())
3423 continue;
3424 // Fix broken pass-by-value case for Fortran character boxes
3425 if (!mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3426 llvmPrivateVar = builder.CreateLoad(
3427 moduleTranslation.convertType(blockArg.getType()), llvmPrivateVar);
3428 }
3429 assert(llvmPrivateVar->getType() ==
3430 moduleTranslation.convertType(blockArg.getType()));
3431 moduleTranslation.mapValue(blockArg, llvmPrivateVar);
3432 }
3433
3434 // Map in_reduction block arguments to the per-task private storage returned
3435 // by __kmpc_task_reduction_get_th_data. This call must be emitted inside
3436 // the to-be-outlined task body so that it returns the *executing* thread's
3437 // gtid (not the encountering thread's). The descriptor is NULL: the runtime
3438 // walks up enclosing taskgroups to find the matching task_reduction
3439 // registration for `origPtr`. The original pointers are auto-captured into
3440 // the task shareds aggregate by CodeExtractor during
3441 // OpenMPIRBuilder::finalize.
3442 if (!inRedDecls.empty()) {
3443 auto iface = cast<omp::BlockArgOpenMPOpInterface>(taskOp.getOperation());
3444 llvm::OpenMPIRBuilder &ompB = *moduleTranslation.getOpenMPBuilder();
3445 llvm::Module *m = moduleTranslation.getLLVMModule();
3446 llvm::LLVMContext &llvmCtx = m->getContext();
3447 llvm::OpenMPIRBuilder::LocationDescription bodyLoc(builder);
3448 uint32_t srcLocSize;
3449 llvm::Constant *srcLocStr =
3450 ompB.getOrCreateSrcLocStr(bodyLoc, srcLocSize);
3451 llvm::Value *bodyIdent = ompB.getOrCreateIdent(srcLocStr, srcLocSize);
3452 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the gtid
3453 // call lands inside the to-be-outlined task body.
3454 ompB.updateToLocation(bodyLoc);
3455 llvm::Value *bodyGtid = ompB.getOrCreateThreadID(bodyIdent);
3456 llvm::FunctionCallee getThData = ompB.getOrCreateRuntimeFunction(
3457 *m, llvm::omp::OMPRTL___kmpc_task_reduction_get_th_data);
3458 llvm::Type *ptrTy = llvm::PointerType::getUnqual(llvmCtx);
3459 llvm::Value *nullDesc = llvm::ConstantPointerNull::get(ptrTy);
3460 ArrayRef<BlockArgument> inRedBlockArgs = iface.getInReductionBlockArgs();
3461 for (auto [blockArg, origPtr] :
3462 llvm::zip_equal(inRedBlockArgs, inRedOrigPtrs)) {
3463 // __kmpc_task_reduction_get_th_data takes and returns a generic,
3464 // default-address-space `ptr`. Normalize a non-default-address-space
3465 // original pointer to the generic address space before the call, and
3466 // cast the returned private pointer back to the block argument's
3467 // address space when it differs (mirrors the taskloop reduction
3468 // remapping in convertOmpTaskloopContextOp).
3469 llvm::Value *lookupPtr = origPtr;
3470 if (auto *origPtrTy =
3471 llvm::dyn_cast<llvm::PointerType>(lookupPtr->getType());
3472 origPtrTy && origPtrTy->getAddressSpace() != 0)
3473 lookupPtr = builder.CreateAddrSpaceCast(lookupPtr, ptrTy);
3474 llvm::Value *priv = builder.CreateCall(
3475 getThData, {bodyGtid, nullDesc, lookupPtr}, "omp.inred.priv");
3476 if (auto *argPtrTy = llvm::dyn_cast<llvm::PointerType>(
3477 moduleTranslation.convertType(blockArg.getType()));
3478 argPtrTy && argPtrTy->getAddressSpace() != 0)
3479 priv = builder.CreateAddrSpaceCast(priv, argPtrTy);
3480 moduleTranslation.mapValue(blockArg, priv);
3481 }
3482 }
3483
3484 auto continuationBlockOrError = convertOmpOpRegions(
3485 taskOp.getRegion(), "omp.task.region", builder, moduleTranslation);
3486 if (failed(handleError(continuationBlockOrError, *taskOp)))
3487 return llvm::make_error<PreviouslyReportedError>();
3488
3489 builder.SetInsertPoint(continuationBlockOrError.get()->getTerminator());
3490
3491 if (failed(cleanupPrivateVars(taskOp, builder, moduleTranslation,
3492 taskOp.getLoc(), privateVarsInfo)))
3493 return llvm::make_error<PreviouslyReportedError>();
3494
3495 // Free heap allocated task context structure at the end of the task.
3496 taskStructMgr.freeStructPtr();
3497
3498 return llvm::Error::success();
3499 };
3500
3501 llvm::OpenMPIRBuilder &ompBuilder = *moduleTranslation.getOpenMPBuilder();
3502 SmallVector<llvm::UncondBrInst *> cancelTerminators;
3503 // The directive to match here is OMPD_taskgroup because it is the taskgroup
3504 // which is canceled. This is handled here because it is the task's cleanup
3505 // block which should be branched to.
3506 pushCancelFinalizationCB(cancelTerminators, builder, ompBuilder, taskOp,
3507 llvm::omp::Directive::OMPD_taskgroup);
3508
3509 llvm::OpenMPIRBuilder::DependenciesInfo dependencies;
3510 if (failed(buildDependData(taskOp.getDependVars(), taskOp.getDependKinds(),
3511 taskOp.getDependIterated(),
3512 taskOp.getDependIteratedKinds(), builder,
3513 moduleTranslation, dependencies)))
3514 return failure();
3515
3516 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
3517 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
3518 moduleTranslation.getOpenMPBuilder()->createTask(
3519 ompLoc, allocaIP, deallocBlocks, bodyCB, !taskOp.getUntied(),
3520 moduleTranslation.lookupValue(taskOp.getFinal()),
3521 moduleTranslation.lookupValue(taskOp.getIfExpr()), dependencies, ad,
3522 taskOp.getMergeable(),
3523 moduleTranslation.lookupValue(taskOp.getEventHandle()),
3524 moduleTranslation.lookupValue(taskOp.getPriority()),
3525 taskOp.getThreadset() == omp::ThreadsetPolicy::omp_pool);
3526
3527 if (failed(handleError(afterIP, *taskOp)))
3528 return failure();
3529
3530 // Set the correct branch target for task cancellation
3531 popCancelFinalizationCB(cancelTerminators, ompBuilder, afterIP.get());
3532
3533 builder.restoreIP(*afterIP);
3534
3535 if (dependencies.DepArray)
3536 builder.CreateFree(dependencies.DepArray);
3537
3538 return success();
3539}
3540
3541/// The correct entry point is convertOmpTaskloopContextOp. This gets called
3542/// whilst lowering the body of the taskloop context (i.e. the task function).
3543static LogicalResult
3544convertOmpTaskloopWrapperOp(omp::TaskloopWrapperOp loopWrapperOp,
3545 llvm::IRBuilderBase &builder,
3546 LLVM::ModuleTranslation &moduleTranslation) {
3547 mlir::Operation &opInst = *loopWrapperOp.getOperation();
3548 if (failed(checkImplementationStatus(opInst)))
3549 return failure();
3550
3551 // Recurse into the loop body.
3552 auto continuationBlockOrError = convertOmpOpRegions(
3553 loopWrapperOp.getRegion(), "omp.taskloop.wrapper.region", builder,
3554 moduleTranslation);
3555
3556 if (failed(handleError(continuationBlockOrError, opInst)))
3557 return failure();
3558
3559 builder.SetInsertPoint(continuationBlockOrError.get());
3560 return success();
3561}
3562
3563/// Look up the given value in the mapping, and if it's not there, translate its
3564/// defining operation at the current builder insertion point. Only pure,
3565/// regionless operations are supported because the same operation will later be
3566/// translated again when the taskloop body itself is lowered.
3567static llvm::Expected<llvm::Value *>
3569 LLVM::ModuleTranslation &moduleTranslation,
3570 llvm::IRBuilderBase &builder) {
3571 if (llvm::Value *mapped = moduleTranslation.lookupValue(value))
3572 return mapped;
3573
3574 Operation *defOp = value.getDefiningOp();
3575 if (!defOp)
3576 return llvm::make_error<llvm::StringError>(
3577 "value is a block argument and is not mapped",
3578 llvm::inconvertibleErrorCode());
3579 if (defOp->getNumRegions() != 0 || !isPure(defOp))
3580 return llvm::make_error<llvm::StringError>(
3581 "unsupported op defining taskloop loop bound",
3582 llvm::inconvertibleErrorCode());
3583
3584 SmallVector<Value> mappingsToRemove;
3585 mappingsToRemove.reserve(defOp->getNumOperands() + defOp->getNumResults());
3586 for (Value operand : defOp->getOperands()) {
3587 if (moduleTranslation.lookupValue(operand))
3588 continue;
3589
3590 llvm::Expected<llvm::Value *> operandOrError =
3591 lookupOrTranslatePureValue(operand, moduleTranslation, builder);
3592 if (!operandOrError)
3593 return operandOrError.takeError();
3594 moduleTranslation.mapValue(operand, *operandOrError);
3595 mappingsToRemove.push_back(operand);
3596 }
3597
3598 if (failed(moduleTranslation.convertOperation(*defOp, builder)))
3599 return llvm::make_error<llvm::StringError>(
3600 "failed to convert op defining taskloop loop bound",
3601 llvm::inconvertibleErrorCode());
3602
3603 llvm::Value *result = moduleTranslation.lookupValue(value);
3604 assert(result && "expected conversion of loop bound op to produce a value");
3605
3606 for (Value resultValue : defOp->getResults()) {
3607 if (moduleTranslation.lookupValue(resultValue))
3608 mappingsToRemove.push_back(resultValue);
3609 }
3610 for (Value mappedValue : mappingsToRemove)
3611 moduleTranslation.forgetMapping(mappedValue);
3612
3613 return result;
3614}
3615
3616static llvm::Error
3617computeTaskloopBounds(omp::LoopNestOp loopOp, llvm::IRBuilderBase &builder,
3618 LLVM::ModuleTranslation &moduleTranslation,
3619 llvm::Value *&lbVal, llvm::Value *&ubVal,
3620 llvm::Value *&stepVal) {
3621 Operation::operand_range lowerBounds = loopOp.getLoopLowerBounds();
3622 Operation::operand_range upperBounds = loopOp.getLoopUpperBounds();
3623 Operation::operand_range steps = loopOp.getLoopSteps();
3624
3625 llvm::Expected<llvm::Value *> firstLbOrErr =
3626 lookupOrTranslatePureValue(lowerBounds[0], moduleTranslation, builder);
3627 if (!firstLbOrErr)
3628 return firstLbOrErr.takeError();
3629
3630 llvm::Type *boundType = (*firstLbOrErr)->getType();
3631 ubVal = builder.getIntN(boundType->getIntegerBitWidth(), 1);
3632 if (loopOp.getCollapseNumLoops() > 1) {
3633 // In cases where Collapse is used with Taskloop, the upper bound of the
3634 // iteration space needs to be recalculated to cater for the collapsed loop.
3635 // The Collapsed Loop UpperBound is the product of all collapsed
3636 // loop's tripcount.
3637 // The LowerBound for collapsed loops is always 1. When the loops are
3638 // collapsed, it will reset the bounds and introduce processing to ensure
3639 // the index's are presented as expected. As this happens after creating
3640 // Taskloop, these bounds need predicting. Example:
3641 // !$omp taskloop collapse(2)
3642 // do i = 1, 10
3643 // do j = 1, 5
3644 // ..
3645 // end do
3646 // end do
3647 // This loop above has a total of 50 iterations, so the lb will be 1, and
3648 // the ub will be 50. collapseLoops in OMPIRBuilder then handles ensuring
3649 // that i and j are properly presented when used in the loop.
3650 for (uint64_t i = 0; i < loopOp.getCollapseNumLoops(); i++) {
3652 i == 0 ? std::move(firstLbOrErr)
3653 : lookupOrTranslatePureValue(lowerBounds[i], moduleTranslation,
3654 builder);
3655 if (!lbOrErr)
3656 return lbOrErr.takeError();
3658 upperBounds[i], moduleTranslation, builder);
3659 if (!ubOrErr)
3660 return ubOrErr.takeError();
3662 lookupOrTranslatePureValue(steps[i], moduleTranslation, builder);
3663 if (!stepOrErr)
3664 return stepOrErr.takeError();
3665
3666 llvm::Value *loopLb = *lbOrErr;
3667 llvm::Value *loopUb = *ubOrErr;
3668 llvm::Value *loopStep = *stepOrErr;
3669 // In some cases, such as where the ub is less than the lb so the loop
3670 // steps down, the calculation for the loopTripCount is swapped. To ensure
3671 // the correct value is found, calculate both UB - LB and LB - UB then
3672 // select which value to use depending on how the loop has been
3673 // configured.
3674 llvm::Value *loopLbMinusOne = builder.CreateSub(
3675 loopLb, builder.getIntN(boundType->getIntegerBitWidth(), 1));
3676 llvm::Value *loopUbMinusOne = builder.CreateSub(
3677 loopUb, builder.getIntN(boundType->getIntegerBitWidth(), 1));
3678 llvm::Value *boundsCmp = builder.CreateICmpSLT(loopLb, loopUb);
3679 llvm::Value *ubMinusLb = builder.CreateSub(loopUb, loopLbMinusOne);
3680 llvm::Value *lbMinusUb = builder.CreateSub(loopLb, loopUbMinusOne);
3681 llvm::Value *loopTripCount =
3682 builder.CreateSelect(boundsCmp, ubMinusLb, lbMinusUb);
3683 loopTripCount = builder.CreateBinaryIntrinsic(
3684 llvm::Intrinsic::abs, loopTripCount, builder.getFalse());
3685 // For loops that have a step value not equal to 1, we need to adjust the
3686 // trip count to ensure the correct number of iterations for the loop is
3687 // captured.
3688 llvm::Value *loopTripCountDivStep =
3689 builder.CreateSDiv(loopTripCount, loopStep);
3690 loopTripCountDivStep = builder.CreateBinaryIntrinsic(
3691 llvm::Intrinsic::abs, loopTripCountDivStep, builder.getFalse());
3692 llvm::Value *loopTripCountRem =
3693 builder.CreateSRem(loopTripCount, loopStep);
3694 loopTripCountRem = builder.CreateBinaryIntrinsic(
3695 llvm::Intrinsic::abs, loopTripCountRem, builder.getFalse());
3696 llvm::Value *needsRoundUp = builder.CreateICmpNE(
3697 loopTripCountRem,
3698 builder.getIntN(loopTripCountRem->getType()->getIntegerBitWidth(),
3699 0));
3700 loopTripCount =
3701 builder.CreateAdd(loopTripCountDivStep,
3702 builder.CreateZExtOrTrunc(
3703 needsRoundUp, loopTripCountDivStep->getType()));
3704 ubVal = builder.CreateMul(ubVal, loopTripCount);
3705 }
3706 lbVal = builder.getIntN(boundType->getIntegerBitWidth(), 1);
3707 stepVal = builder.getIntN(boundType->getIntegerBitWidth(), 1);
3708 } else {
3710 lookupOrTranslatePureValue(upperBounds[0], moduleTranslation, builder);
3711 if (!ubOrErr)
3712 return ubOrErr.takeError();
3714 lookupOrTranslatePureValue(steps[0], moduleTranslation, builder);
3715 if (!stepOrErr)
3716 return stepOrErr.takeError();
3717 lbVal = *firstLbOrErr;
3718 ubVal = *ubOrErr;
3719 stepVal = *stepOrErr;
3720 }
3721
3722 assert(lbVal != nullptr && "Expected value for lbVal");
3723 assert(ubVal != nullptr && "Expected value for ubVal");
3724 assert(stepVal != nullptr && "Expected value for stepVal");
3725 return llvm::Error::success();
3726}
3727
3728// Converts an OpenMP taskloop construct into LLVM IR using OpenMPIRBuilder.
3729static LogicalResult
3730convertOmpTaskloopContextOp(omp::TaskloopContextOp contextOp,
3731 llvm::IRBuilderBase &builder,
3732 LLVM::ModuleTranslation &moduleTranslation) {
3733 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
3734 mlir::Operation &opInst = *contextOp.getOperation();
3735 omp::TaskloopWrapperOp loopWrapperOp = contextOp.getLoopOp();
3736 if (failed(checkImplementationStatus(opInst)))
3737 return failure();
3738
3739 // It stores the pointer of allocated firstprivate copies,
3740 // which can be used later for freeing the allocated space.
3741 SmallVector<llvm::Value *> llvmFirstPrivateVars;
3742 PrivateVarsInfo privateVarsInfo(contextOp);
3743 TaskContextStructManager taskStructMgr{builder, moduleTranslation,
3744 privateVarsInfo.privatizers};
3745
3747 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
3748 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
3749
3750 assert(builder.GetInsertPoint() == builder.GetInsertBlock()->end());
3751 llvm::BasicBlock *taskloopStartBlock = llvm::BasicBlock::Create(
3752 builder.getContext(), "omp.taskloop.wrapper.start",
3753 /*Parent=*/builder.GetInsertBlock()->getParent());
3754 llvm::Instruction *branchToTaskloopStartBlock =
3755 builder.CreateBr(taskloopStartBlock);
3756 builder.SetInsertPoint(branchToTaskloopStartBlock);
3757
3758 llvm::BasicBlock *copyBlock =
3759 splitBB(builder, /*CreateBranch=*/true, "omp.private.copy");
3760 llvm::BasicBlock *initBlock =
3761 splitBB(builder, /*CreateBranch=*/true, "omp.private.init");
3762
3764 moduleTranslation, allocaIP, deallocBlocks);
3765
3766 // Allocate and initialize private variables
3767 builder.SetInsertPoint(initBlock->getTerminator());
3768
3769 // TODO: don't allocate if the loop has zero iterations.
3770 taskStructMgr.generateTaskContextStruct();
3771 taskStructMgr.createGEPsToPrivateVars();
3772
3773 llvmFirstPrivateVars.resize(privateVarsInfo.blockArgs.size());
3774
3775 for (auto [i, zip] : llvm::enumerate(llvm::zip_equal(
3776 privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
3777 privateVarsInfo.blockArgs, taskStructMgr.getLLVMPrivateVarGEPs()))) {
3778 auto [privDecl, mlirPrivVar, blockArg, llvmPrivateVarAlloc] = zip;
3779 // To be handled inside the taskloop.
3780 if (!privDecl.readsFromMold())
3781 continue;
3782 assert(llvmPrivateVarAlloc &&
3783 "reads from mold so shouldn't have been skipped");
3784
3785 llvm::Expected<llvm::Value *> privateVarOrErr =
3786 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
3787 blockArg, llvmPrivateVarAlloc, initBlock);
3788 if (!privateVarOrErr)
3789 return handleError(privateVarOrErr, *contextOp.getOperation());
3790
3791 llvmFirstPrivateVars[i] = privateVarOrErr.get();
3792
3793 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3794 builder.SetInsertPoint(builder.GetInsertBlock()->getTerminator());
3795
3796 [[maybe_unused]] llvm::Value *llvmPrivateVar = llvmPrivateVarAlloc;
3797 if ((privateVarOrErr.get() != llvmPrivateVarAlloc) &&
3798 !mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3799 builder.CreateStore(privateVarOrErr.get(), llvmPrivateVarAlloc);
3800 // Load it so we have the value pointed to by the GEP
3801 llvmPrivateVar = builder.CreateLoad(privateVarOrErr.get()->getType(),
3802 llvmPrivateVarAlloc);
3803 }
3804 assert(llvmPrivateVar->getType() ==
3805 moduleTranslation.convertType(blockArg.getType()));
3806 }
3807
3808 // firstprivate copy region
3809 setInsertPointForPossiblyEmptyBlock(builder, copyBlock);
3810 if (failed(copyFirstPrivateVars(
3811 contextOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
3812 taskStructMgr.getLLVMPrivateVarGEPs(), privateVarsInfo.privatizers,
3813 contextOp.getPrivateNeedsBarrier())))
3814 return llvm::failure();
3815
3816 // Resolve and validate reduction / in_reduction declarations up front.
3817 // This is pure MLIR symbol-table work and does not emit IR, so do it
3818 // before moving the builder to the taskloop start block. Richer
3819 // declare_reduction shapes (byref) have been rejected already by
3820 // checkImplementationStatus; the rest (two-argument initializer, cleanup
3821 // region, missing combiner) are rejected by the helper.
3824 contextOp.getOperation(), contextOp.getReductionSyms(),
3825 "omp.taskloop.context", "reduction", redDecls)))
3826 return failure();
3829 contextOp.getOperation(), contextOp.getInReductionSyms(),
3830 "omp.taskloop.context", "in_reduction", inRedDecls)))
3831 return failure();
3832
3833 // The op verifier rejects nogroup + reduction, so no check is needed here.
3834
3835 SmallVector<llvm::Value *> redOrigPtrs;
3836 redOrigPtrs.reserve(redDecls.size());
3837 for (Value v : contextOp.getReductionVars())
3838 redOrigPtrs.push_back(moduleTranslation.lookupValue(v));
3839 SmallVector<llvm::Value *> inRedOrigPtrs;
3840 inRedOrigPtrs.reserve(inRedDecls.size());
3841 for (Value v : contextOp.getInReductionVars())
3842 inRedOrigPtrs.push_back(moduleTranslation.lookupValue(v));
3843
3844 // Set up insertion point for emitting the implicit-taskgroup reduction
3845 // setup (if any) and for the subsequent call to createTaskloop().
3846 builder.SetInsertPoint(taskloopStartBlock);
3847
3848 llvm::OpenMPIRBuilder &ompBuilderRef = *moduleTranslation.getOpenMPBuilder();
3849 llvm::Module *module = moduleTranslation.getLLVMModule();
3850
3851 // If we have task_reduction items, we must emit our own implicit
3852 // __kmpc_taskgroup so that the descriptor returned by __kmpc_taskred_init
3853 // is associated with that taskgroup. We then force NoGroup=true so that
3854 // OpenMPIRBuilder::createTaskloop does not emit a second taskgroup.
3855 bool implicitTaskgroup = !redDecls.empty();
3856 llvm::Value *redDesc = nullptr;
3857 if (implicitTaskgroup) {
3858 llvm::OpenMPIRBuilder::LocationDescription redLoc(builder);
3859 uint32_t srcLocSize;
3860 llvm::Constant *srcLocStr =
3861 ompBuilderRef.getOrCreateSrcLocStr(redLoc, srcLocSize);
3862 llvm::Value *ident = ompBuilderRef.getOrCreateIdent(srcLocStr, srcLocSize);
3863 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the
3864 // gtid call lands at our insertion point.
3865 ompBuilderRef.updateToLocation(redLoc);
3866 llvm::Value *outerGtid = ompBuilderRef.getOrCreateThreadID(ident);
3867 llvm::FunctionCallee taskgroupFn = ompBuilderRef.getOrCreateRuntimeFunction(
3868 *module, llvm::omp::OMPRTL___kmpc_taskgroup);
3869 builder.CreateCall(taskgroupFn, {ident, outerGtid});
3870
3871 redDesc = emitTaskReductionInitCall(redDecls, redOrigPtrs,
3872 "__omp_taskloop_taskred_", builder,
3873 allocaIP, moduleTranslation);
3874 if (!redDesc)
3875 return failure();
3876 }
3877
3878 auto loopOp = cast<omp::LoopNestOp>(loopWrapperOp.getWrappedLoop());
3879 llvm::Value *lbVal = nullptr;
3880 llvm::Value *ubVal = nullptr;
3881 llvm::Value *stepVal = nullptr;
3882 if (llvm::Error err = computeTaskloopBounds(
3883 loopOp, builder, moduleTranslation, lbVal, ubVal, stepVal))
3884 return handleError(std::move(err), opInst);
3885
3886 auto bodyCB =
3887 [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
3888 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
3889 // Save the alloca insertion point on ModuleTranslation stack for use in
3890 // nested regions.
3892 moduleTranslation, allocaIP, deallocBlocks);
3893
3894 // translate the body of the taskloop:
3895 builder.restoreIP(codegenIP);
3896
3897 llvm::BasicBlock *privInitBlock = nullptr;
3898 privateVarsInfo.llvmVars.resize(privateVarsInfo.blockArgs.size());
3899 for (auto [i, zip] : llvm::enumerate(llvm::zip_equal(
3900 privateVarsInfo.blockArgs, privateVarsInfo.privatizers,
3901 privateVarsInfo.mlirVars))) {
3902 auto [blockArg, privDecl, mlirPrivVar] = zip;
3903 // This is handled before the task executes
3904 if (privDecl.readsFromMold())
3905 continue;
3906
3907 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3908 llvm::Type *llvmAllocType =
3909 moduleTranslation.convertType(privDecl.getType());
3910 builder.SetInsertPoint(allocaIP.getBlock()->getTerminator());
3911 llvm::Value *llvmPrivateVar = builder.CreateAlloca(
3912 llvmAllocType, /*ArraySize=*/nullptr, "omp.private.alloc");
3913
3914 llvm::Expected<llvm::Value *> privateVarOrError =
3915 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
3916 blockArg, llvmPrivateVar, privInitBlock);
3917 if (!privateVarOrError)
3918 return privateVarOrError.takeError();
3919 moduleTranslation.mapValue(blockArg, privateVarOrError.get());
3920 privateVarsInfo.llvmVars[i] = privateVarOrError.get();
3921 }
3922
3923 taskStructMgr.createGEPsToPrivateVars();
3924 for (auto [i, llvmPrivVar] :
3925 llvm::enumerate(taskStructMgr.getLLVMPrivateVarGEPs())) {
3926 if (!llvmPrivVar) {
3927 assert(privateVarsInfo.llvmVars[i] &&
3928 "This is added in the loop above");
3929 continue;
3930 }
3931 privateVarsInfo.llvmVars[i] = llvmPrivVar;
3932 }
3933
3934 // Find and map the addresses of each variable within the taskloop context
3935 // structure
3936 for (auto [blockArg, llvmPrivateVar, privateDecl] :
3937 llvm::zip_equal(privateVarsInfo.blockArgs, privateVarsInfo.llvmVars,
3938 privateVarsInfo.privatizers)) {
3939 // This was handled above.
3940 if (!privateDecl.readsFromMold())
3941 continue;
3942 // Fix broken pass-by-value case for Fortran character boxes
3943 if (!mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3944 llvmPrivateVar = builder.CreateLoad(
3945 moduleTranslation.convertType(blockArg.getType()), llvmPrivateVar);
3946 }
3947 assert(llvmPrivateVar->getType() ==
3948 moduleTranslation.convertType(blockArg.getType()));
3949 moduleTranslation.mapValue(blockArg, llvmPrivateVar);
3950 }
3951
3952 // Map reduction and in_reduction block arguments to the per-task private
3953 // storage returned by __kmpc_task_reduction_get_th_data. This call must
3954 // be emitted inside the to-be-outlined task body so that it returns the
3955 // *executing* thread's gtid (not the encountering thread's). The
3956 // taskgroup descriptor `redDesc` is computed in the outer scope and is
3957 // auto-captured into the task shareds aggregate by CodeExtractor during
3958 // OpenMPIRBuilder::finalize. For in_reduction the descriptor is NULL:
3959 // the runtime walks up enclosing taskgroups to find the matching
3960 // task_reduction registration for `origPtr`.
3961 if (!redDecls.empty() || !inRedDecls.empty()) {
3962 auto iface =
3963 cast<omp::BlockArgOpenMPOpInterface>(contextOp.getOperation());
3964 llvm::OpenMPIRBuilder &ompB = *moduleTranslation.getOpenMPBuilder();
3965 llvm::Module *m = moduleTranslation.getLLVMModule();
3966 llvm::LLVMContext &llvmCtx = m->getContext();
3967 llvm::OpenMPIRBuilder::LocationDescription bodyLoc(builder);
3968 uint32_t srcLocSize;
3969 llvm::Constant *srcLocStr =
3970 ompB.getOrCreateSrcLocStr(bodyLoc, srcLocSize);
3971 llvm::Value *bodyIdent = ompB.getOrCreateIdent(srcLocStr, srcLocSize);
3972 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the
3973 // gtid call lands inside the to-be-outlined task body.
3974 ompB.updateToLocation(bodyLoc);
3975 llvm::Value *bodyGtid = ompB.getOrCreateThreadID(bodyIdent);
3976 llvm::FunctionCallee getThData = ompB.getOrCreateRuntimeFunction(
3977 *m, llvm::omp::OMPRTL___kmpc_task_reduction_get_th_data);
3978 llvm::Type *ptrTy = llvm::PointerType::getUnqual(llvmCtx);
3979
3980 // Emit one __kmpc_task_reduction_get_th_data lookup for a reduction /
3981 // in_reduction item and map its block argument to the per-task private
3982 // storage the runtime returns. The runtime entry point takes (and
3983 // returns) a generic, default-address-space `ptr`, so normalize a
3984 // non-default-address-space original pointer to the generic address
3985 // space before the call (mirroring the descriptor setup in
3986 // emitTaskReductionInitCall), and cast the returned private pointer back
3987 // to the block argument's address space when that differs.
3988 auto remapReductionArg = [&](BlockArgument blockArg, llvm::Value *desc,
3989 llvm::Value *origPtr,
3990 const llvm::Twine &name) {
3991 if (auto *origPtrTy =
3992 llvm::dyn_cast<llvm::PointerType>(origPtr->getType());
3993 origPtrTy && origPtrTy->getAddressSpace() != 0)
3994 origPtr = builder.CreateAddrSpaceCast(origPtr, ptrTy);
3995 llvm::Value *priv =
3996 builder.CreateCall(getThData, {bodyGtid, desc, origPtr}, name);
3997 if (auto *argPtrTy = llvm::dyn_cast<llvm::PointerType>(
3998 moduleTranslation.convertType(blockArg.getType()));
3999 argPtrTy && argPtrTy->getAddressSpace() != 0)
4000 priv = builder.CreateAddrSpaceCast(priv, argPtrTy);
4001 moduleTranslation.mapValue(blockArg, priv);
4002 };
4003
4004 ArrayRef<BlockArgument> redBlockArgs = iface.getReductionBlockArgs();
4005 for (auto [blockArg, origPtr] :
4006 llvm::zip_equal(redBlockArgs, redOrigPtrs))
4007 remapReductionArg(blockArg, redDesc, origPtr, "omp.taskred.priv");
4008 ArrayRef<BlockArgument> inRedBlockArgs = iface.getInReductionBlockArgs();
4009 llvm::Value *nullDesc = llvm::ConstantPointerNull::get(ptrTy);
4010 for (auto [blockArg, origPtr] :
4011 llvm::zip_equal(inRedBlockArgs, inRedOrigPtrs))
4012 remapReductionArg(blockArg, nullDesc, origPtr, "omp.inred.priv");
4013 }
4014
4015 // Lower the contents of the taskloop context region: this is the body of
4016 // the generated task, not the loop.
4017 auto continuationBlockOrError = convertOmpOpRegions(
4018 contextOp.getRegion(), "omp.taskloop.context.region", builder,
4019 moduleTranslation);
4020
4021 if (failed(handleError(continuationBlockOrError, opInst)))
4022 return llvm::make_error<PreviouslyReportedError>();
4023
4024 builder.SetInsertPoint(continuationBlockOrError.get()->getTerminator());
4025
4026 // This is freeing the private variables as mapped inside of the task: these
4027 // will be per-task private copies possibly after task duplication. This is
4028 // handled transparently by how these are passed to the structure passed
4029 // into the outlined function. When the task is duplicated, that structure
4030 // is duplicated too.
4031 if (failed(cleanupPrivateVars(contextOp, builder, moduleTranslation,
4032 contextOp.getLoc(), privateVarsInfo)))
4033 return llvm::make_error<PreviouslyReportedError>();
4034 // Similarly, the task context structure freed inside the task is the
4035 // per-task copy after task duplication.
4036 taskStructMgr.freeStructPtr();
4037
4038 return llvm::Error::success();
4039 };
4040
4041 // Taskloop divides into an appropriate number of tasks by repeatedly
4042 // duplicating the original task. Each time this is done, the task context
4043 // structure must be duplicated too.
4044 auto taskDupCB = [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
4045 llvm::Value *destPtr, llvm::Value *srcPtr)
4047 llvm::IRBuilderBase::InsertPointGuard guard(builder);
4048 builder.restoreIP(codegenIP);
4049
4050 llvm::Type *ptrTy =
4051 builder.getPtrTy(srcPtr->getType()->getPointerAddressSpace());
4052 llvm::Value *src =
4053 builder.CreateLoad(ptrTy, srcPtr, "omp.taskloop.context.src");
4054
4055 TaskContextStructManager &srcStructMgr = taskStructMgr;
4056 TaskContextStructManager destStructMgr(builder, moduleTranslation,
4057 privateVarsInfo.privatizers);
4058 destStructMgr.generateTaskContextStruct();
4059 llvm::Value *dest = destStructMgr.getStructPtr();
4060 dest->setName("omp.taskloop.context.dest");
4061 builder.CreateStore(dest, destPtr);
4062
4064 srcStructMgr.createGEPsToPrivateVars(src);
4066 destStructMgr.createGEPsToPrivateVars(dest);
4067
4068 // Inline init regions.
4069 for (auto [privDecl, mold, blockArg, llvmPrivateVarAlloc] :
4070 llvm::zip_equal(privateVarsInfo.privatizers, srcGEPs,
4071 privateVarsInfo.blockArgs, destGEPs)) {
4072 // To be handled inside task body.
4073 if (!privDecl.readsFromMold())
4074 continue;
4075 assert(llvmPrivateVarAlloc &&
4076 "reads from mold so shouldn't have been skipped");
4077
4078 llvm::Value *moldArg = materializeRegionArgValue(
4079 builder, moduleTranslation, privDecl.getInitMoldArg(), mold);
4081 builder, moduleTranslation, privDecl, moldArg, blockArg,
4082 llvmPrivateVarAlloc, builder.GetInsertBlock());
4083 if (!privateVarOrErr)
4084 return privateVarOrErr.takeError();
4085
4087
4088 // TODO: this is a bit of a hack for Fortran character boxes.
4089 // Character boxes are passed by value into the init region and then the
4090 // initialized character box is yielded by value. Here we need to store
4091 // the yielded value into the private allocation, and load the private
4092 // allocation to match the type expected by region block arguments.
4093 [[maybe_unused]] llvm::Value *llvmPrivateVar = llvmPrivateVarAlloc;
4094 if ((privateVarOrErr.get() != llvmPrivateVarAlloc) &&
4095 !mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
4096 builder.CreateStore(privateVarOrErr.get(), llvmPrivateVarAlloc);
4097 // Load it so we have the value pointed to by the GEP
4098 llvmPrivateVar = builder.CreateLoad(privateVarOrErr.get()->getType(),
4099 llvmPrivateVarAlloc);
4100 }
4101 assert(llvmPrivateVar->getType() ==
4102 moduleTranslation.convertType(blockArg.getType()));
4103
4104 // Mapping blockArg -> llvmPrivateVarAlloc is done inside the body
4105 // callback so that OpenMPIRBuilder doesn't try to pass each GEP address
4106 // through a stack allocated structure.
4107 }
4108
4109 if (failed(copyFirstPrivateVars(contextOp.getOperation(), builder,
4110 moduleTranslation, srcGEPs, destGEPs,
4111 privateVarsInfo.privatizers,
4112 contextOp.getPrivateNeedsBarrier())))
4113 return llvm::make_error<PreviouslyReportedError>();
4114
4115 return builder.saveIP();
4116 };
4117
4118 auto loopInfo = [&]() -> llvm::Expected<llvm::CanonicalLoopInfo *> {
4119 llvm::CanonicalLoopInfo *loopInfo = findCurrentLoopInfo(moduleTranslation);
4120 return loopInfo;
4121 };
4122
4123 llvm::Value *ifCond = nullptr;
4124 llvm::Value *grainsize = nullptr;
4125 int sched = 0; // default
4126 mlir::Value grainsizeVal = contextOp.getGrainsize();
4127 mlir::Value numTasksVal = contextOp.getNumTasks();
4128 if (Value ifVar = contextOp.getIfExpr())
4129 ifCond = moduleTranslation.lookupValue(ifVar);
4130 if (grainsizeVal) {
4131 grainsize = moduleTranslation.lookupValue(grainsizeVal);
4132 sched = 1; // grainsize
4133 } else if (numTasksVal) {
4134 grainsize = moduleTranslation.lookupValue(numTasksVal);
4135 sched = 2; // num_tasks
4136 }
4137
4138 llvm::OpenMPIRBuilder::TaskDupCallbackTy taskDupOrNull = nullptr;
4139 if (taskStructMgr.getStructPtr())
4140 taskDupOrNull = taskDupCB;
4141
4142 llvm::OpenMPIRBuilder &ompBuilder = *moduleTranslation.getOpenMPBuilder();
4143 SmallVector<llvm::UncondBrInst *> cancelTerminators;
4144 // The directive to match here is OMPD_taskgroup because it is the
4145 // taskgroup which is canceled. This is handled here because it is the
4146 // task's cleanup block which should be branched to. It doesn't depend upon
4147 // nogroup because even in that case the taskloop might still be inside an
4148 // explicit taskgroup.
4149 pushCancelFinalizationCB(cancelTerminators, builder, ompBuilder, contextOp,
4150 llvm::omp::Directive::OMPD_taskgroup);
4151
4152 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4153 bool effectiveNoGroup = contextOp.getNogroup() || implicitTaskgroup;
4154 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
4155 moduleTranslation.getOpenMPBuilder()->createTaskloop(
4156 ompLoc, allocaIP, deallocBlocks, bodyCB, loopInfo, lbVal, ubVal,
4157 stepVal, contextOp.getUntied(), ifCond, grainsize, effectiveNoGroup,
4158 sched, moduleTranslation.lookupValue(contextOp.getFinal()),
4159 contextOp.getMergeable(),
4160 moduleTranslation.lookupValue(contextOp.getPriority()),
4161 loopOp.getCollapseNumLoops(), taskDupOrNull,
4162 taskStructMgr.getStructPtr(),
4163 contextOp.getThreadset() == omp::ThreadsetPolicy::omp_pool);
4164
4165 if (failed(handleError(afterIP, opInst)))
4166 return failure();
4167
4168 popCancelFinalizationCB(cancelTerminators, ompBuilder, afterIP.get());
4169
4170 builder.restoreIP(*afterIP);
4171
4172 // Close the implicit taskgroup we opened for task_reduction. The end call
4173 // must execute on the encountering thread, so use the outer-scope gtid.
4174 if (implicitTaskgroup) {
4175 llvm::OpenMPIRBuilder::LocationDescription endLoc(builder);
4176 uint32_t srcLocSize;
4177 llvm::Constant *srcLocStr =
4178 ompBuilder.getOrCreateSrcLocStr(endLoc, srcLocSize);
4179 llvm::Value *ident = ompBuilder.getOrCreateIdent(srcLocStr, srcLocSize);
4180 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the
4181 // gtid call lands at our insertion point.
4182 ompBuilder.updateToLocation(endLoc);
4183 llvm::Value *outerGtid = ompBuilder.getOrCreateThreadID(ident);
4184 llvm::FunctionCallee endTgFn = ompBuilder.getOrCreateRuntimeFunction(
4185 *moduleTranslation.getLLVMModule(),
4186 llvm::omp::OMPRTL___kmpc_end_taskgroup);
4187 builder.CreateCall(endTgFn, {ident, outerGtid});
4188 }
4189 return success();
4190}
4191
4192/// Build an outlined init helper for a task_reduction declare_reduction op.
4193/// Signature: void(ptr %priv, ptr %orig). For non-byref reductions, the init
4194/// region's mold argument is mapped following the same rule as the regular
4195/// reduction path (`mapInitializationArgs`): a non-pointer mold loads the
4196/// value from %orig, while a pointer-typed mold receives %orig directly. The
4197/// yielded value is stored into %priv.
4198static llvm::Function *
4199emitTaskReductionInitFn(omp::DeclareReductionOp decl, StringRef baseName,
4200 LLVM::ModuleTranslation &moduleTranslation) {
4201 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4202 llvm::LLVMContext &ctx = llvmModule->getContext();
4203 llvm::Type *voidTy = llvm::Type::getVoidTy(ctx);
4204 llvm::Type *ptrTy = llvm::PointerType::getUnqual(ctx);
4205 llvm::FunctionType *fty =
4206 llvm::FunctionType::get(voidTy, {ptrTy, ptrTy}, false);
4207 llvm::Function *fn =
4208 llvm::Function::Create(fty, llvm::GlobalValue::InternalLinkage,
4209 baseName + ".red.init", llvmModule);
4210 fn->setDoesNotRecurse();
4211 fn->getArg(0)->setName("priv");
4212 fn->getArg(1)->setName("orig");
4213
4214 llvm::BasicBlock *entry = llvm::BasicBlock::Create(ctx, "entry", fn);
4215 llvm::IRBuilder<> b(entry);
4216
4217 // Map the initializer's mold argument the same way the regular reduction
4218 // path does in `mapInitializationArgs`: only load the original value when a
4219 // non-pointer mold is expected. For a pointer-typed mold the storage pointer
4220 // (%orig) is passed through directly, so a mold-yielding initializer lowers
4221 // to `store ptr %orig, ptr %priv` rather than emitting a spurious load.
4222 Value moldArg = decl.getInitializerMoldArg();
4223 llvm::Value *origVal = fn->getArg(1);
4224 if (!isa<LLVM::LLVMPointerType>(moldArg.getType()))
4225 origVal = b.CreateLoad(moduleTranslation.convertType(moldArg.getType()),
4226 fn->getArg(1), "omp.orig");
4227 moduleTranslation.mapValue(moldArg, origVal);
4229 if (failed(inlineConvertOmpRegions(decl.getInitializerRegion(),
4230 "omp.taskred.init", b, moduleTranslation,
4231 &phis))) {
4232 fn->eraseFromParent();
4233 return nullptr;
4234 }
4235 assert(phis.size() == 1 &&
4236 "expected one value yielded from reduction initializer");
4237 b.CreateStore(phis[0], fn->getArg(0));
4238 b.CreateRetVoid();
4239
4240 moduleTranslation.forgetMapping(decl.getInitializerRegion());
4241 return fn;
4242}
4243
4244/// Build an outlined combiner helper for a task_reduction declare_reduction op.
4245/// Signature: void(ptr %lhs, ptr %rhs). For non-byref reductions, the values
4246/// at *%lhs and *%rhs are loaded, fed into the combiner region, and the
4247/// yielded scalar is stored back into *%lhs.
4248static llvm::Function *
4249emitTaskReductionCombFn(omp::DeclareReductionOp decl, StringRef baseName,
4250 LLVM::ModuleTranslation &moduleTranslation) {
4251 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4252 llvm::LLVMContext &ctx = llvmModule->getContext();
4253 llvm::Type *voidTy = llvm::Type::getVoidTy(ctx);
4254 llvm::Type *ptrTy = llvm::PointerType::getUnqual(ctx);
4255 llvm::FunctionType *fty =
4256 llvm::FunctionType::get(voidTy, {ptrTy, ptrTy}, false);
4257 llvm::Function *fn =
4258 llvm::Function::Create(fty, llvm::GlobalValue::InternalLinkage,
4259 baseName + ".red.comb", llvmModule);
4260 fn->setDoesNotRecurse();
4261 fn->getArg(0)->setName("lhs");
4262 fn->getArg(1)->setName("rhs");
4263
4264 llvm::BasicBlock *entry = llvm::BasicBlock::Create(ctx, "entry", fn);
4265 llvm::IRBuilder<> b(entry);
4266
4267 llvm::Type *elemTy = moduleTranslation.convertType(decl.getType());
4268 Block &combBlock = decl.getReductionRegion().front();
4269 assert(combBlock.getNumArguments() == 2 &&
4270 "expected two arguments in declare_reduction combiner");
4271 llvm::Value *lhsVal = b.CreateLoad(elemTy, fn->getArg(0), "omp.lhs");
4272 llvm::Value *rhsVal = b.CreateLoad(elemTy, fn->getArg(1), "omp.rhs");
4273 moduleTranslation.mapValue(combBlock.getArgument(0), lhsVal);
4274 moduleTranslation.mapValue(combBlock.getArgument(1), rhsVal);
4275
4277 if (failed(inlineConvertOmpRegions(decl.getReductionRegion(),
4278 "omp.taskred.comb", b, moduleTranslation,
4279 &phis))) {
4280 fn->eraseFromParent();
4281 return nullptr;
4282 }
4283 assert(phis.size() == 1 &&
4284 "expected one value yielded from reduction combiner");
4285 b.CreateStore(phis[0], fn->getArg(0));
4286 b.CreateRetVoid();
4287
4288 moduleTranslation.forgetMapping(decl.getReductionRegion());
4289 return fn;
4290}
4291
4292/// Emit the per-taskgroup task_reduction descriptor array and the
4293/// `__kmpc_taskred_init` runtime call. \p origPtrs holds the LLVM values for
4294/// the original (shared) variables, one per declaration in \p redDecls.
4295/// `builder` must be set to the point at which the descriptor stores and the
4296/// init call should be emitted; the descriptor array itself is allocated at
4297/// \p allocaIP. \p helperNamePrefix is used to disambiguate the generated
4298/// init/combiner helper symbol names between taskgroup and taskloop callers.
4299///
4300/// When \p isModifier is false, emits `__kmpc_taskred_init` and returns the
4301/// `ptr` value it produces (the taskgroup reduction handle). When \p isModifier
4302/// is true, emits `__kmpc_taskred_modifier_init` instead to open a
4303/// task-reduction scope for a parallel or worksharing construct, passing
4304/// \p isWorksharing as the runtime `is_ws` argument. Returns null on failure.
4305///
4306/// Only the non-byref form is handled here. Byref reductions have already
4307/// been rejected by `checkImplementationStatus`.
4308static llvm::Value *emitTaskReductionInitCall(
4310 ArrayRef<llvm::Value *> origPtrs, StringRef helperNamePrefix,
4311 llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder::InsertPointTy allocaIP,
4312 LLVM::ModuleTranslation &moduleTranslation, bool isModifier,
4313 bool isWorksharing) {
4314 assert(redDecls.size() == origPtrs.size() &&
4315 "expected one orig pointer per reduction decl");
4316 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4317 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4318 llvm::LLVMContext &ctx = llvmModule->getContext();
4319 const llvm::DataLayout &dl = llvmModule->getDataLayout();
4320
4321 llvm::Type *ptrTy = llvm::PointerType::getUnqual(ctx);
4322 llvm::Type *i32Ty = llvm::Type::getInt32Ty(ctx);
4323 llvm::Type *sizeTy =
4324 llvm::Type::getIntNTy(ctx, dl.getPointerSizeInBits(/*AddrSpace=*/0));
4325
4326 // Identified `kmp_taskred_input_t` struct, matching the layout used by
4327 // Clang's CGOpenMPRuntime::emitTaskReductionInit.
4328 llvm::StructType *redInputTy =
4329 llvm::StructType::getTypeByName(ctx, "kmp_taskred_input_t");
4330 if (!redInputTy)
4331 redInputTy = llvm::StructType::create(
4332 ctx, {ptrTy, ptrTy, sizeTy, ptrTy, ptrTy, ptrTy, i32Ty},
4333 "kmp_taskred_input_t");
4334
4335 unsigned n = redDecls.size();
4336 llvm::ArrayType *arrTy = llvm::ArrayType::get(redInputTy, n);
4337
4338 // Allocate the descriptor array in the enclosing function's alloca block.
4339 llvm::AllocaInst *arrAlloca;
4340 {
4341 llvm::IRBuilderBase::InsertPointGuard guard(builder);
4342 builder.restoreIP(allocaIP);
4343 arrAlloca =
4344 builder.CreateAlloca(arrTy, /*ArraySize=*/nullptr, ".taskred.input");
4345 }
4346
4347 // Fill each descriptor entry at the current builder insertion point.
4348 llvm::Value *zero = builder.getInt32(0);
4349 for (unsigned i = 0; i < n; ++i) {
4350 omp::DeclareReductionOp decl = redDecls[i];
4351 llvm::Value *orig = origPtrs[i];
4352 if (auto *origPtrTy = llvm::dyn_cast<llvm::PointerType>(orig->getType());
4353 origPtrTy && origPtrTy->getAddressSpace() != 0)
4354 orig = builder.CreateAddrSpaceCast(orig, ptrTy);
4355 llvm::Type *elemTy = moduleTranslation.convertType(decl.getType());
4356 uint64_t size = dl.getTypeAllocSize(elemTy).getFixedValue();
4357
4358 std::string baseName =
4359 (llvm::Twine(helperNamePrefix) + decl.getSymName()).str();
4360 llvm::Function *initFn =
4361 emitTaskReductionInitFn(decl, baseName, moduleTranslation);
4362 llvm::Function *combFn =
4363 emitTaskReductionCombFn(decl, baseName, moduleTranslation);
4364 if (!initFn || !combFn)
4365 return nullptr;
4366 llvm::Value *elemPtr = builder.CreateInBoundsGEP(
4367 arrTy, arrAlloca, {zero, builder.getInt32(i)}, ".taskred.elem");
4368 auto storeField = [&](unsigned fieldIdx, llvm::Value *val) {
4369 llvm::Value *fieldPtr =
4370 builder.CreateStructGEP(redInputTy, elemPtr, fieldIdx);
4371 builder.CreateStore(val, fieldPtr);
4372 };
4373 storeField(0, orig); // reduce_shar
4374 storeField(1, orig); // reduce_orig
4375 storeField(2, llvm::ConstantInt::get(sizeTy, size)); // reduce_size
4376 storeField(3, initFn); // reduce_init
4377 storeField(4, llvm::ConstantPointerNull::get(ptrTy)); // reduce_fini
4378 storeField(5, combFn); // reduce_comb
4379 storeField(6, llvm::ConstantInt::get(i32Ty, 0)); // flags
4380 }
4381
4382 // Emit the runtime call that registers the task reduction data.
4383 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4384 uint32_t srcLocSize;
4385 llvm::Constant *srcLocStr =
4386 ompBuilder->getOrCreateSrcLocStr(ompLoc, srcLocSize);
4387 llvm::Value *ident = ompBuilder->getOrCreateIdent(srcLocStr, srcLocSize);
4388 ompBuilder->updateToLocation(ompLoc);
4389 llvm::Value *gtid = ompBuilder->getOrCreateThreadID(ident);
4390 if (isModifier) {
4391 // __kmpc_taskred_modifier_init(loc, gtid, is_ws, num, &arr) opens a
4392 // task-reduction scope for the enclosing parallel/worksharing region.
4393 llvm::FunctionCallee modInit = ompBuilder->getOrCreateRuntimeFunction(
4394 *llvmModule, llvm::omp::OMPRTL___kmpc_taskred_modifier_init);
4395 return builder.CreateCall(modInit,
4396 {ident, gtid,
4397 builder.getInt32(isWorksharing ? 1 : 0),
4398 builder.getInt32(n), arrAlloca},
4399 ".taskred.desc");
4400 }
4401 // __kmpc_taskred_init(gtid, num, &arr).
4402 llvm::FunctionCallee taskredInit = ompBuilder->getOrCreateRuntimeFunction(
4403 *llvmModule, llvm::omp::OMPRTL___kmpc_taskred_init);
4404 return builder.CreateCall(taskredInit, {gtid, builder.getInt32(n), arrAlloca},
4405 ".taskred.desc");
4406}
4407
4408/// Emits `__kmpc_task_reduction_modifier_fini(loc, gtid, is_ws)` at the current
4409/// builder insertion point, closing the task-reduction scope opened by the
4410/// `task` reduction modifier on a parallel or worksharing construct.
4411static void
4412emitTaskReductionModifierFini(bool isWorksharing, llvm::IRBuilderBase &builder,
4413 LLVM::ModuleTranslation &moduleTranslation) {
4414 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4415 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4416 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4417 uint32_t srcLocSize;
4418 llvm::Constant *srcLocStr =
4419 ompBuilder->getOrCreateSrcLocStr(ompLoc, srcLocSize);
4420 llvm::Value *ident = ompBuilder->getOrCreateIdent(srcLocStr, srcLocSize);
4421 ompBuilder->updateToLocation(ompLoc);
4422 llvm::Value *gtid = ompBuilder->getOrCreateThreadID(ident);
4423 llvm::FunctionCallee fini = ompBuilder->getOrCreateRuntimeFunction(
4424 *llvmModule, llvm::omp::OMPRTL___kmpc_task_reduction_modifier_fini);
4425 builder.CreateCall(fini,
4426 {ident, gtid, builder.getInt32(isWorksharing ? 1 : 0)});
4427}
4428
4429/// Converts an OpenMP taskgroup construct into LLVM IR using OpenMPIRBuilder.
4430static LogicalResult
4431convertOmpTaskgroupOp(omp::TaskgroupOp tgOp, llvm::IRBuilderBase &builder,
4432 LLVM::ModuleTranslation &moduleTranslation) {
4433 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
4434 if (failed(checkImplementationStatus(*tgOp)))
4435 return failure();
4436
4437 // Resolve and validate task_reduction declarations up front. We only handle
4438 // declare_reduction ops shaped like a non-byref scalar reduction in this
4439 // first cut; richer shapes (two-argument initializer, cleanup region,
4440 // missing combiner) require additional infrastructure.
4442 if (auto syms = tgOp.getTaskReductionSyms()) {
4443 redDecls.reserve(syms->size());
4444 for (auto sym : syms->getAsRange<SymbolRefAttr>()) {
4446 tgOp, sym);
4447 if (!decl)
4448 return tgOp.emitError()
4449 << "failed to resolve task_reduction declare_reduction symbol "
4450 << sym.getRootReference() << " in omp.taskgroup";
4451 if (decl.getInitializerRegion().front().getNumArguments() != 1)
4452 return tgOp.emitError("not yet implemented: task_reduction with "
4453 "two-argument initializer in omp.taskgroup");
4454 if (!decl.getCleanupRegion().empty())
4455 return tgOp.emitError("not yet implemented: task_reduction with "
4456 "cleanup region in omp.taskgroup");
4457 if (decl.getReductionRegion().empty())
4458 return tgOp.emitError("task_reduction declare_reduction is missing a "
4459 "combiner region");
4460 redDecls.push_back(decl);
4461 }
4462 }
4463
4464 auto bodyCB =
4465 [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
4466 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
4467 builder.restoreIP(codegenIP);
4468
4469 if (!redDecls.empty()) {
4471 origPtrs.reserve(redDecls.size());
4472 for (Value v : tgOp.getTaskReductionVars())
4473 origPtrs.push_back(moduleTranslation.lookupValue(v));
4474 if (!emitTaskReductionInitCall(redDecls, origPtrs, "__omp_taskred_",
4475 builder, allocaIP, moduleTranslation))
4476 return llvm::createStringError(
4477 llvm::inconvertibleErrorCode(),
4478 "failed to emit task_reduction initialization for omp.taskgroup");
4479 }
4480
4481 // Inside the taskgroup body, each task_reduction block argument refers to
4482 // the same shared/original storage that the runtime now knows about via
4483 // the descriptor array. Inner tasks that declare in_reduction look up
4484 // per-task private copies through the runtime; the taskgroup body itself
4485 // uses the original variable.
4486 for (auto [i, blockArg] :
4487 llvm::enumerate(tgOp.getRegion().getArguments())) {
4488 llvm::Value *orig =
4489 moduleTranslation.lookupValue(tgOp.getTaskReductionVars()[i]);
4490 moduleTranslation.mapValue(blockArg, orig);
4491 }
4492
4493 return convertOmpOpRegions(tgOp.getRegion(), "omp.taskgroup.region",
4494 builder, moduleTranslation)
4495 .takeError();
4496 };
4497
4499 InsertPointTy allocaIP =
4500 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
4501 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4502 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
4503 moduleTranslation.getOpenMPBuilder()->createTaskgroup(
4504 ompLoc, allocaIP, deallocBlocks, bodyCB);
4505
4506 if (failed(handleError(afterIP, *tgOp)))
4507 return failure();
4508
4509 builder.restoreIP(*afterIP);
4510 return success();
4511}
4512
4513static LogicalResult
4514convertOmpInteropInitOp(omp::InteropInitOp initOp, llvm::IRBuilderBase &builder,
4515 LLVM::ModuleTranslation &moduleTranslation) {
4516 if (!initOp.getDependVars().empty() || initOp.getDependKinds() ||
4517 !initOp.getDependIterated().empty() || initOp.getDependIteratedKinds())
4518 return initOp.emitError()
4519 << "not yet implemented: Unhandled clause depend in "
4520 << omp::InteropInitOp::getOperationName() << " operation";
4521
4522 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4523 llvm::Value *interopVar =
4524 moduleTranslation.lookupValue(initOp.getInteropVar());
4525 llvm::Value *device = initOp.getDevice()
4526 ? moduleTranslation.lookupValue(initOp.getDevice())
4527 : nullptr;
4528
4529 // TODO: Handle depend clauses when supported.
4530 llvm::Value *numDeps = llvm::ConstantInt::get(builder.getInt32Ty(), 0);
4531 llvm::Value *depArray = llvm::ConstantPointerNull::get(builder.getPtrTy());
4532 bool hasNowait = initOp.getNowait();
4533
4534 // A single `init` clause may list both `target` and `targetsync`, but the
4535 // runtime init call takes a single interop-type. Collapse the set to one
4536 // value, matching Clang: if `target` is present use Target, otherwise
4537 // TargetSync. The offload runtime object model supports only one type per
4538 // object; representing both would require a runtime change.
4539 bool hasTarget = false, hasTargetSync = false;
4540 for (mlir::Attribute typeAttr : initOp.getInteropTypes()) {
4541 switch (cast<omp::InteropTypeAttr>(typeAttr).getValue()) {
4542 case omp::InteropType::target:
4543 hasTarget = true;
4544 break;
4545 case omp::InteropType::targetsync:
4546 hasTargetSync = true;
4547 break;
4548 }
4549 }
4550 llvm::omp::OMPInteropType interopType =
4551 (!hasTarget && hasTargetSync) ? llvm::omp::OMPInteropType::TargetSync
4552 : llvm::omp::OMPInteropType::Target;
4553 ompBuilder->createOMPInteropInit(builder, interopVar, interopType, device,
4554 numDeps, depArray, hasNowait);
4555 return success();
4556}
4557
4558static LogicalResult
4559convertOmpInteropDestroyOp(omp::InteropDestroyOp destroyOp,
4560 llvm::IRBuilderBase &builder,
4561 LLVM::ModuleTranslation &moduleTranslation) {
4562 if (!destroyOp.getDependVars().empty() || destroyOp.getDependKinds() ||
4563 !destroyOp.getDependIterated().empty() ||
4564 destroyOp.getDependIteratedKinds())
4565 return destroyOp.emitError()
4566 << "not yet implemented: Unhandled clause depend in "
4567 << omp::InteropDestroyOp::getOperationName() << " operation";
4568
4569 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4570 llvm::Value *interopVar =
4571 moduleTranslation.lookupValue(destroyOp.getInteropVar());
4572 llvm::Value *device =
4573 destroyOp.getDevice()
4574 ? moduleTranslation.lookupValue(destroyOp.getDevice())
4575 : nullptr;
4576
4577 llvm::Value *numDeps = llvm::ConstantInt::get(builder.getInt32Ty(), 0);
4578 llvm::Value *depArray = llvm::ConstantPointerNull::get(builder.getPtrTy());
4579 bool hasNowait = destroyOp.getNowait();
4580
4581 ompBuilder->createOMPInteropDestroy(builder, interopVar, device, numDeps,
4582 depArray, hasNowait);
4583 return success();
4584}
4585
4586static LogicalResult
4587convertOmpInteropUseOp(omp::InteropUseOp useOp, llvm::IRBuilderBase &builder,
4588 LLVM::ModuleTranslation &moduleTranslation) {
4589 if (!useOp.getDependVars().empty() || useOp.getDependKinds() ||
4590 !useOp.getDependIterated().empty() || useOp.getDependIteratedKinds())
4591 return useOp.emitError()
4592 << "not yet implemented: Unhandled clause depend in "
4593 << omp::InteropUseOp::getOperationName() << " operation";
4594
4595 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4596 llvm::Value *interopVar =
4597 moduleTranslation.lookupValue(useOp.getInteropVar());
4598 llvm::Value *device = useOp.getDevice()
4599 ? moduleTranslation.lookupValue(useOp.getDevice())
4600 : nullptr;
4601
4602 llvm::Value *numDeps = llvm::ConstantInt::get(builder.getInt32Ty(), 0);
4603 llvm::Value *depArray = llvm::ConstantPointerNull::get(builder.getPtrTy());
4604 bool hasNowait = useOp.getNowait();
4605
4606 ompBuilder->createOMPInteropUse(builder, interopVar, device, numDeps,
4607 depArray, hasNowait);
4608 return success();
4609}
4610
4611static LogicalResult
4612convertOmpTaskwaitOp(omp::TaskwaitOp twOp, llvm::IRBuilderBase &builder,
4613 LLVM::ModuleTranslation &moduleTranslation) {
4614 if (failed(checkImplementationStatus(*twOp)))
4615 return failure();
4616
4617 llvm::OpenMPIRBuilder::DependenciesInfo dds;
4618 if (failed(buildDependData(
4619 twOp.getDependVars(), twOp.getDependKinds(), twOp.getDependIterated(),
4620 twOp.getDependIteratedKinds(), builder, moduleTranslation, dds))) {
4621 return failure();
4622 }
4623
4624 moduleTranslation.getOpenMPBuilder()->createTaskwait(builder, dds);
4625 if (dds.DepArray) {
4626 builder.CreateFree(dds.DepArray);
4627 }
4628
4629 return success();
4630}
4631
4632/// Converts an OpenMP workshare loop into LLVM IR using OpenMPIRBuilder.
4633static LogicalResult
4634convertOmpWsloop(Operation &opInst, llvm::IRBuilderBase &builder,
4635 LLVM::ModuleTranslation &moduleTranslation) {
4636 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4637 auto wsloopOp = cast<omp::WsloopOp>(opInst);
4638 if (failed(checkImplementationStatus(opInst)))
4639 return failure();
4640
4641 auto loopOp = cast<omp::LoopNestOp>(wsloopOp.getWrappedLoop());
4642 llvm::ArrayRef<bool> isByRef = getIsByRef(wsloopOp.getReductionByref());
4643 assert(isByRef.size() == wsloopOp.getNumReductionVars());
4644
4645 // Static is the default.
4646 auto schedule =
4647 wsloopOp.getScheduleKind().value_or(omp::ClauseScheduleKind::Static);
4648
4649 // Find the loop configuration.
4650 llvm::Value *step = moduleTranslation.lookupValue(loopOp.getLoopSteps()[0]);
4651 llvm::Type *ivType = step->getType();
4652 llvm::Value *chunk = nullptr;
4653 if (wsloopOp.getScheduleChunk()) {
4654 llvm::Value *chunkVar =
4655 moduleTranslation.lookupValue(wsloopOp.getScheduleChunk());
4656 chunk = builder.CreateSExtOrTrunc(chunkVar, ivType);
4657 }
4658
4659 omp::DistributeOp distributeOp = nullptr;
4660 llvm::Value *distScheduleChunk = nullptr;
4661 bool hasDistSchedule = false;
4662 if (llvm::isa_and_present<omp::DistributeOp>(opInst.getParentOp())) {
4663 distributeOp = cast<omp::DistributeOp>(opInst.getParentOp());
4664 hasDistSchedule = distributeOp.getDistScheduleStatic();
4665 if (distributeOp.getDistScheduleChunkSize()) {
4666 llvm::Value *chunkVar = moduleTranslation.lookupValue(
4667 distributeOp.getDistScheduleChunkSize());
4668 distScheduleChunk = builder.CreateSExtOrTrunc(chunkVar, ivType);
4669 }
4670 }
4671
4672 PrivateVarsInfo privateVarsInfo(wsloopOp);
4673
4675 collectReductionDecls(wsloopOp, reductionDecls);
4676
4677 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
4678 findAllocInsertPoints(builder, moduleTranslation);
4679
4680 SmallVector<llvm::Value *> privateReductionVariables(
4681 wsloopOp.getNumReductionVars());
4682
4684 wsloopOp, builder, moduleTranslation, privateVarsInfo, allocaIP);
4685 if (handleError(afterAllocas, opInst).failed())
4686 return failure();
4687
4688 DenseMap<Value, llvm::Value *> reductionVariableMap;
4689
4690 MutableArrayRef<BlockArgument> reductionArgs =
4691 cast<omp::BlockArgOpenMPOpInterface>(opInst).getReductionBlockArgs();
4692
4693 SmallVector<DeferredStore> deferredStores;
4694
4695 if (failed(allocReductionVars(wsloopOp, reductionArgs, builder,
4696 moduleTranslation, allocaIP, reductionDecls,
4697 privateReductionVariables, reductionVariableMap,
4698 deferredStores, isByRef)))
4699 return failure();
4700
4701 if (handleError(initPrivateVars(builder, moduleTranslation, privateVarsInfo),
4702 opInst)
4703 .failed())
4704 return failure();
4705
4706 if (failed(copyFirstPrivateVars(
4707 wsloopOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
4708 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
4709 wsloopOp.getPrivateNeedsBarrier())))
4710 return failure();
4711
4712 assert(afterAllocas.get()->getSinglePredecessor());
4713 if (failed(initReductionVars(wsloopOp, reductionArgs, builder,
4714 moduleTranslation,
4715 afterAllocas.get()->getSinglePredecessor(),
4716 reductionDecls, privateReductionVariables,
4717 reductionVariableMap, isByRef, deferredStores)))
4718 return failure();
4719
4720 // For `reduction(task, ...)` open a task-reduction scope for the worksharing
4721 // loop. Participating explicit tasks accumulate into the per-thread private
4722 // copies, which the worksharing reduction then combines across threads.
4723 bool isTaskReductionMod =
4724 wsloopOp.getReductionMod() == omp::ReductionModifier::task &&
4725 wsloopOp.getNumReductionVars() > 0;
4726 if (isTaskReductionMod &&
4727 !emitTaskReductionInitCall(reductionDecls, privateReductionVariables,
4728 "__omp_taskred_mod_", builder, allocaIP,
4729 moduleTranslation, /*isModifier=*/true,
4730 /*isWorksharing=*/true))
4731 return wsloopOp.emitError(
4732 "failed to emit task reduction modifier initialization");
4733
4734 // TODO: Handle doacross loops when the ordered clause has a parameter.
4735 bool isOrdered = wsloopOp.getOrdered().has_value();
4736 std::optional<omp::ScheduleModifier> scheduleMod = wsloopOp.getScheduleMod();
4737 bool isSimd = wsloopOp.getScheduleSimd();
4738 bool loopNeedsBarrier = !wsloopOp.getNowait();
4739
4740 // The only legal way for the direct parent to be omp.distribute is that this
4741 // represents 'distribute parallel do'. Otherwise, this is a regular
4742 // worksharing loop.
4743 llvm::omp::WorksharingLoopType workshareLoopType =
4744 llvm::isa_and_present<omp::DistributeOp>(opInst.getParentOp())
4745 ? llvm::omp::WorksharingLoopType::DistributeForStaticLoop
4746 : llvm::omp::WorksharingLoopType::ForStaticLoop;
4747
4748 SmallVector<llvm::UncondBrInst *> cancelTerminators;
4749 pushCancelFinalizationCB(cancelTerminators, builder, *ompBuilder, wsloopOp,
4750 llvm::omp::Directive::OMPD_for);
4751
4752 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4753
4754 // Initialize linear variables and linear step
4755 LinearClauseProcessor linearClauseProcessor;
4756
4757 if (!wsloopOp.getLinearVars().empty()) {
4758 auto linearVarTypes = wsloopOp.getLinearVarTypes().value();
4759 for (mlir::Attribute linearVarType : linearVarTypes)
4760 linearClauseProcessor.registerType(moduleTranslation, linearVarType);
4761
4762 for (auto [idx, linearVar] : llvm::enumerate(wsloopOp.getLinearVars()))
4763 linearClauseProcessor.createLinearVar(
4764 builder, moduleTranslation, moduleTranslation.lookupValue(linearVar),
4765 idx);
4766 for (mlir::Value linearStep : wsloopOp.getLinearStepVars())
4767 linearClauseProcessor.initLinearStep(moduleTranslation, linearStep);
4768 }
4769
4771 wsloopOp.getRegion(), "omp.wsloop.region", builder, moduleTranslation);
4772
4773 if (failed(handleError(regionBlock, opInst)))
4774 return failure();
4775
4776 llvm::CanonicalLoopInfo *loopInfo = findCurrentLoopInfo(moduleTranslation);
4777
4778 // Emit Initialization and Update IR for linear variables
4779 if (!wsloopOp.getLinearVars().empty()) {
4780 linearClauseProcessor.initLinearVar(builder, moduleTranslation,
4781 loopInfo->getPreheader());
4782 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterBarrierIP =
4783 moduleTranslation.getOpenMPBuilder()->createBarrier(
4784 builder, llvm::omp::OMPD_barrier);
4785 if (failed(handleError(afterBarrierIP, *loopOp)))
4786 return failure();
4787 builder.restoreIP(*afterBarrierIP);
4788 linearClauseProcessor.updateLinearVar(builder, loopInfo->getBody(),
4789 loopInfo->getIndVar());
4790 linearClauseProcessor.splitLinearFiniBB(builder, loopInfo->getExit());
4791 }
4792
4793 builder.SetInsertPoint(*regionBlock, (*regionBlock)->begin());
4794
4795 // Check if we can generate no-loop kernel
4796 bool noLoopMode = false;
4797 omp::TargetOp targetOp = wsloopOp->getParentOfType<mlir::omp::TargetOp>();
4798 if (targetOp &&
4799 targetOp.getKernelType() == omp::TargetExecMode::spmd_no_loop) {
4800 Operation *targetCapturedOp =
4801 cast<omp::ComposableOpInterface>(*targetOp).findCapturedOp();
4802 // We need this check because, without it, noLoopMode would be set to true
4803 // for every omp.wsloop nested inside a no-loop SPMD target region, even if
4804 // that loop is not the top-level SPMD one.
4805 if (loopOp == targetCapturedOp)
4806 noLoopMode = true;
4807 }
4808
4809 for (size_t index = 0; index < wsloopOp.getLinearVars().size(); index++)
4810 linearClauseProcessor.rewriteInPlace(builder, loopInfo->getBody(),
4811 loopInfo->getLatch(), index);
4812
4813 llvm::OpenMPIRBuilder::InsertPointOrErrorTy wsloopIP =
4814 ompBuilder->applyWorkshareLoop(
4815 ompLoc.DL, loopInfo, allocaIP, loopNeedsBarrier,
4816 convertToScheduleKind(schedule), chunk, isSimd,
4817 scheduleMod == omp::ScheduleModifier::monotonic,
4818 scheduleMod == omp::ScheduleModifier::nonmonotonic, isOrdered,
4819 workshareLoopType, noLoopMode, hasDistSchedule, distScheduleChunk);
4820
4821 if (failed(handleError(wsloopIP, opInst)))
4822 return failure();
4823
4824 // Emit finalization and in-place rewrites for linear vars.
4825 if (!wsloopOp.getLinearVars().empty()) {
4826 llvm::OpenMPIRBuilder::InsertPointTy oldIP = builder.saveIP();
4827 assert(loopInfo->getLastIter() &&
4828 "`lastiter` in CanonicalLoopInfo is nullptr");
4829 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterBarrierIP =
4830 linearClauseProcessor.finalizeLinearVar(builder, moduleTranslation,
4831 loopInfo->getLastIter());
4832 if (failed(handleError(afterBarrierIP, *loopOp)))
4833 return failure();
4834
4835 builder.restoreIP(oldIP);
4836 }
4837
4838 // Set the correct branch target for task cancellation
4839 popCancelFinalizationCB(cancelTerminators, *ompBuilder, wsloopIP.get());
4840
4841 // Close the task-reduction scope before the worksharing reduction combine.
4842 if (isTaskReductionMod)
4843 emitTaskReductionModifierFini(/*isWorksharing=*/true, builder,
4844 moduleTranslation);
4845
4846 // Process the reductions if required.
4847 if (failed(createReductionsAndCleanup(
4848 wsloopOp, builder, moduleTranslation, allocaIP, reductionDecls,
4849 privateReductionVariables, isByRef, wsloopOp.getNowait(),
4850 /*isTeamsReduction=*/false)))
4851 return failure();
4852
4853 return cleanupPrivateVars(wsloopOp, builder, moduleTranslation,
4854 wsloopOp.getLoc(), privateVarsInfo);
4855}
4856
4857/// Converts the OpenMP parallel operation to LLVM IR.
4858static LogicalResult
4859convertOmpParallel(omp::ParallelOp opInst, llvm::IRBuilderBase &builder,
4860 LLVM::ModuleTranslation &moduleTranslation) {
4861 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
4862 ArrayRef<bool> isByRef = getIsByRef(opInst.getReductionByref());
4863 assert(isByRef.size() == opInst.getNumReductionVars());
4864 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4865 bool isCancellable = constructIsCancellable(opInst);
4866
4867 if (failed(checkImplementationStatus(*opInst)))
4868 return failure();
4869
4870 PrivateVarsInfo privateVarsInfo(opInst);
4871 for (Value allocatorVar : opInst.getAllocatorVars()) {
4872 if (privateVarsInfo.convertedAllocators.contains(allocatorVar))
4873 continue;
4874
4875 llvm::Value *allocator = moduleTranslation.lookupValue(allocatorVar);
4876 if (!allocator)
4877 return opInst.emitError("failed to translate OpenMP allocator operand");
4878 if (allocator->getType()->isIntegerTy())
4879 allocator = builder.CreateIntToPtr(allocator, builder.getPtrTy());
4880 else if (allocator->getType()->isPointerTy())
4881 allocator = builder.CreatePointerBitCastOrAddrSpaceCast(
4882 allocator, builder.getPtrTy());
4883 else
4884 return opInst.emitError(
4885 "OpenMP allocator operand must have integer or pointer type");
4886
4887 privateVarsInfo.convertedAllocators.try_emplace(allocatorVar, allocator);
4888 }
4889
4890 // Collect reduction declarations
4892 collectReductionDecls(opInst, reductionDecls);
4893 SmallVector<llvm::Value *> privateReductionVariables(
4894 opInst.getNumReductionVars());
4895 SmallVector<DeferredStore> deferredStores;
4896 // Only open a task-reduction scope when the `task` modifier is present and
4897 // there are reduction variables to combine; otherwise the matching fini in
4898 // the reduction-combine path (guarded by getNumReductionVars() > 0) would be
4899 // skipped, leaving the modifier init unbalanced.
4900 bool isTaskReductionMod =
4901 opInst.getReductionMod() == omp::ReductionModifier::task &&
4902 opInst.getNumReductionVars() > 0;
4903
4904 auto bodyGenCB =
4905 [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
4906 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
4908 opInst, builder, moduleTranslation, privateVarsInfo, allocaIP);
4909 if (handleError(afterAllocas, *opInst).failed())
4910 return llvm::make_error<PreviouslyReportedError>();
4911
4912 // Allocate reduction vars
4913 DenseMap<Value, llvm::Value *> reductionVariableMap;
4914
4915 MutableArrayRef<BlockArgument> reductionArgs =
4916 cast<omp::BlockArgOpenMPOpInterface>(*opInst).getReductionBlockArgs();
4917
4918 allocaIP =
4919 InsertPointTy(allocaIP.getBlock(),
4920 allocaIP.getBlock()->getTerminator()->getIterator());
4921
4922 if (failed(allocReductionVars(
4923 opInst, reductionArgs, builder, moduleTranslation, allocaIP,
4924 reductionDecls, privateReductionVariables, reductionVariableMap,
4925 deferredStores, isByRef)))
4926 return llvm::make_error<PreviouslyReportedError>();
4927
4928 assert(afterAllocas.get()->getSinglePredecessor());
4929 builder.restoreIP(codeGenIP);
4930
4931 if (handleError(
4932 initPrivateVars(builder, moduleTranslation, privateVarsInfo),
4933 *opInst)
4934 .failed())
4935 return llvm::make_error<PreviouslyReportedError>();
4936
4937 if (failed(copyFirstPrivateVars(
4938 opInst, builder, moduleTranslation, privateVarsInfo.mlirVars,
4939 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
4940 opInst.getPrivateNeedsBarrier())))
4941 return llvm::make_error<PreviouslyReportedError>();
4942
4943 if (failed(
4944 initReductionVars(opInst, reductionArgs, builder, moduleTranslation,
4945 afterAllocas.get()->getSinglePredecessor(),
4946 reductionDecls, privateReductionVariables,
4947 reductionVariableMap, isByRef, deferredStores)))
4948 return llvm::make_error<PreviouslyReportedError>();
4949
4950 // For `reduction(task, ...)` open a task-reduction scope so participating
4951 // explicit tasks accumulate into the per-thread private copies; the
4952 // parallel reduction then combines those copies across the team.
4953 if (isTaskReductionMod &&
4954 !emitTaskReductionInitCall(reductionDecls, privateReductionVariables,
4955 "__omp_taskred_mod_", builder, allocaIP,
4956 moduleTranslation, /*isModifier=*/true,
4957 /*isWorksharing=*/false))
4958 return llvm::createStringError(
4959 "failed to emit task reduction modifier initialization");
4960
4961 // Save the alloca insertion point on ModuleTranslation stack for use in
4962 // nested regions.
4964 moduleTranslation, allocaIP, deallocBlocks);
4965
4966 // ParallelOp has only one region associated with it.
4968 opInst.getRegion(), "omp.par.region", builder, moduleTranslation);
4969 if (!regionBlock)
4970 return regionBlock.takeError();
4971
4972 // Process the reductions if required.
4973 if (opInst.getNumReductionVars() > 0) {
4974 // Collect reduction info
4976 SmallVector<OwningAtomicReductionGen> owningAtomicReductionGens;
4978 owningReductionGenRefDataPtrGens;
4980 collectReductionInfo(opInst, builder, moduleTranslation, reductionDecls,
4981 owningReductionGens, owningAtomicReductionGens,
4982 owningReductionGenRefDataPtrGens,
4983 privateReductionVariables, reductionInfos, isByRef);
4984
4985 // Move to region cont block
4986 builder.SetInsertPoint((*regionBlock)->getTerminator());
4987
4988 // Close the task-reduction scope before the per-thread reduction
4989 // contributions are combined across the team.
4990 if (isTaskReductionMod)
4991 emitTaskReductionModifierFini(/*isWorksharing=*/false, builder,
4992 moduleTranslation);
4993
4994 // Generate reductions from info
4995 llvm::UnreachableInst *tempTerminator = builder.CreateUnreachable();
4996 builder.SetInsertPoint(tempTerminator);
4997
4998 llvm::OpenMPIRBuilder::InsertPointOrErrorTy contInsertPoint =
4999 ompBuilder->createReductions(builder, allocaIP, reductionInfos,
5000 isByRef,
5001 /*IsNoWait=*/false,
5002 /*IsTeamsReduction=*/false);
5003 if (!contInsertPoint)
5004 return contInsertPoint.takeError();
5005
5006 if (!contInsertPoint->getBlock())
5007 return llvm::make_error<PreviouslyReportedError>();
5008
5009 tempTerminator->eraseFromParent();
5010 builder.restoreIP(*contInsertPoint);
5011 }
5012
5013 return llvm::Error::success();
5014 };
5015
5016 auto privCB = [](InsertPointTy allocaIP, InsertPointTy codeGenIP,
5017 llvm::Value &, llvm::Value &val, llvm::Value *&replVal) {
5018 // tell OpenMPIRBuilder not to do anything. We handled Privatisation in
5019 // bodyGenCB.
5020 replVal = &val;
5021 return codeGenIP;
5022 };
5023
5024 // TODO: Perform finalization actions for variables. This has to be
5025 // called for variables which have destructors/finalizers.
5026 auto finiCB = [&](InsertPointTy codeGenIP) -> llvm::Error {
5027 InsertPointTy oldIP = builder.saveIP();
5028 builder.restoreIP(codeGenIP);
5029
5030 // if the reduction has a cleanup region, inline it here to finalize the
5031 // reduction variables
5032 SmallVector<Region *> reductionCleanupRegions;
5033 llvm::transform(reductionDecls, std::back_inserter(reductionCleanupRegions),
5034 [](omp::DeclareReductionOp reductionDecl) {
5035 return &reductionDecl.getCleanupRegion();
5036 });
5037 if (failed(inlineOmpRegionCleanup(
5038 reductionCleanupRegions, privateReductionVariables,
5039 moduleTranslation, builder, "omp.reduction.cleanup")))
5040 return llvm::createStringError(
5041 "failed to inline `cleanup` region of `omp.declare_reduction`");
5042
5043 if (failed(cleanupPrivateVars(opInst, builder, moduleTranslation,
5044 opInst.getLoc(), privateVarsInfo)))
5045 return llvm::make_error<PreviouslyReportedError>();
5046
5047 // If we could be performing cancellation, add the cancellation barrier on
5048 // the way out of the outlined region.
5049 if (isCancellable) {
5050 auto IPOrErr = ompBuilder->createBarrier(
5051 llvm::OpenMPIRBuilder::LocationDescription(builder),
5052 llvm::omp::Directive::OMPD_unknown,
5053 /* ForceSimpleCall */ false,
5054 /* CheckCancelFlag */ false);
5055 if (!IPOrErr)
5056 return IPOrErr.takeError();
5057 }
5058
5059 builder.restoreIP(oldIP);
5060 return llvm::Error::success();
5061 };
5062
5063 llvm::Value *ifCond = nullptr;
5064 if (auto ifVar = opInst.getIfExpr())
5065 ifCond = moduleTranslation.lookupValue(ifVar);
5066 llvm::Value *numThreads = nullptr;
5067 if (!opInst.getNumThreadsVars().empty())
5068 numThreads = moduleTranslation.lookupValue(opInst.getNumThreads(0));
5069 auto pbKind = llvm::omp::OMP_PROC_BIND_default;
5070 if (auto bind = opInst.getProcBindKind())
5071 pbKind = getProcBindKind(*bind);
5072
5074 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
5075 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
5076 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5077
5078 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
5079 ompBuilder->createParallel(ompLoc, allocaIP, deallocBlocks, bodyGenCB,
5080 privCB, finiCB, ifCond, numThreads, pbKind,
5081 isCancellable);
5082
5083 if (failed(handleError(afterIP, *opInst)))
5084 return failure();
5085
5086 builder.restoreIP(*afterIP);
5087 return success();
5088}
5089
5090/// Convert Order attribute to llvm::omp::OrderKind.
5091static llvm::omp::OrderKind
5092convertOrderKind(std::optional<omp::ClauseOrderKind> o) {
5093 if (!o)
5094 return llvm::omp::OrderKind::OMP_ORDER_unknown;
5095 switch (*o) {
5096 case omp::ClauseOrderKind::Concurrent:
5097 return llvm::omp::OrderKind::OMP_ORDER_concurrent;
5098 }
5099 llvm_unreachable("Unknown ClauseOrderKind kind");
5100}
5101
5102/// Converts an OpenMP simd loop into LLVM IR using OpenMPIRBuilder.
5103static LogicalResult
5104convertOmpSimd(Operation &opInst, llvm::IRBuilderBase &builder,
5105 LLVM::ModuleTranslation &moduleTranslation) {
5106 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5107 auto simdOp = cast<omp::SimdOp>(opInst);
5108
5109 if (failed(checkImplementationStatus(opInst)))
5110 return failure();
5111
5112 PrivateVarsInfo privateVarsInfo(simdOp);
5113
5114 MutableArrayRef<BlockArgument> reductionArgs =
5115 cast<omp::BlockArgOpenMPOpInterface>(opInst).getReductionBlockArgs();
5116 DenseMap<Value, llvm::Value *> reductionVariableMap;
5117 SmallVector<llvm::Value *> privateReductionVariables(
5118 simdOp.getNumReductionVars());
5119 SmallVector<DeferredStore> deferredStores;
5121 collectReductionDecls(simdOp, reductionDecls);
5122 llvm::ArrayRef<bool> isByRef = getIsByRef(simdOp.getReductionByref());
5123 assert(isByRef.size() == simdOp.getNumReductionVars());
5124
5125 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
5126 findAllocInsertPoints(builder, moduleTranslation);
5127
5129 simdOp, builder, moduleTranslation, privateVarsInfo, allocaIP);
5130 if (handleError(afterAllocas, opInst).failed())
5131 return failure();
5132
5133 // Initialize linear variables and linear step
5134 LinearClauseProcessor linearClauseProcessor;
5135 if (linearClauseProcessor.initLinearIV(simdOp).failed())
5136 return failure();
5137
5138 if (!simdOp.getLinearVars().empty()) {
5139 auto linearVarTypes = simdOp.getLinearVarTypes().value();
5140 for (mlir::Attribute linearVarType : linearVarTypes)
5141 linearClauseProcessor.registerType(moduleTranslation, linearVarType);
5142 for (auto [idx, linearVar] : llvm::enumerate(simdOp.getLinearVars())) {
5143 bool isImplicit = false;
5144 for (auto [mlirPrivVar, llvmPrivateVar] : llvm::zip_equal(
5145 privateVarsInfo.mlirVars, privateVarsInfo.llvmVars)) {
5146 // If the linear variable is implicit, reuse the already
5147 // existing llvm::Value
5148 if (linearVar == mlirPrivVar) {
5149 isImplicit = true;
5150 linearClauseProcessor.createLinearVar(builder, moduleTranslation,
5151 llvmPrivateVar, idx);
5152 break;
5153 }
5154 }
5155
5156 if (!isImplicit)
5157 linearClauseProcessor.createLinearVar(
5158 builder, moduleTranslation,
5159 moduleTranslation.lookupValue(linearVar), idx);
5160 }
5161 for (mlir::Value linearStep : simdOp.getLinearStepVars())
5162 linearClauseProcessor.initLinearStep(moduleTranslation, linearStep);
5163 }
5164
5165 if (failed(allocReductionVars(simdOp, reductionArgs, builder,
5166 moduleTranslation, allocaIP, reductionDecls,
5167 privateReductionVariables, reductionVariableMap,
5168 deferredStores, isByRef)))
5169 return failure();
5170
5171 if (handleError(initPrivateVars(builder, moduleTranslation, privateVarsInfo),
5172 opInst)
5173 .failed())
5174 return failure();
5175
5176 // No call to copyFirstPrivateVars because FIRSTPRIVATE is not allowed for
5177 // SIMD.
5178
5179 assert(afterAllocas.get()->getSinglePredecessor());
5180 if (failed(initReductionVars(simdOp, reductionArgs, builder,
5181 moduleTranslation,
5182 afterAllocas.get()->getSinglePredecessor(),
5183 reductionDecls, privateReductionVariables,
5184 reductionVariableMap, isByRef, deferredStores)))
5185 return failure();
5186
5187 llvm::ConstantInt *simdlen = nullptr;
5188 if (std::optional<uint64_t> simdlenVar = simdOp.getSimdlen())
5189 simdlen = builder.getInt64(simdlenVar.value());
5190
5191 llvm::ConstantInt *safelen = nullptr;
5192 if (std::optional<uint64_t> safelenVar = simdOp.getSafelen())
5193 safelen = builder.getInt64(safelenVar.value());
5194
5195 llvm::MapVector<llvm::Value *, llvm::Value *> alignedVars;
5196 llvm::omp::OrderKind order = convertOrderKind(simdOp.getOrder());
5197
5198 llvm::BasicBlock *sourceBlock = builder.GetInsertBlock();
5199 std::optional<ArrayAttr> alignmentValues = simdOp.getAlignments();
5200 mlir::OperandRange operands = simdOp.getAlignedVars();
5201 for (size_t i = 0; i < operands.size(); ++i) {
5202 llvm::Value *alignment = nullptr;
5203 llvm::Value *llvmVal = moduleTranslation.lookupValue(operands[i]);
5204 llvm::Type *ty = llvmVal->getType();
5205
5206 auto intAttr = cast<IntegerAttr>((*alignmentValues)[i]);
5207 alignment = builder.getInt64(intAttr.getInt());
5208 assert(ty->isPointerTy() && "Invalid type for aligned variable");
5209 assert(alignment && "Invalid alignment value");
5210
5211 // Check if the alignment value is not a power of 2. If so, skip emitting
5212 // alignment.
5213 if (!intAttr.getValue().isPowerOf2())
5214 continue;
5215
5216 auto curInsert = builder.saveIP();
5217 builder.SetInsertPoint(sourceBlock);
5218 llvmVal = builder.CreateLoad(ty, llvmVal);
5219 builder.restoreIP(curInsert);
5220 alignedVars[llvmVal] = alignment;
5221 }
5222
5224 simdOp.getRegion(), "omp.simd.region", builder, moduleTranslation);
5225
5226 if (failed(handleError(regionBlock, opInst)))
5227 return failure();
5228
5229 llvm::CanonicalLoopInfo *loopInfo = findCurrentLoopInfo(moduleTranslation);
5230 // Emit Initialization for linear variables
5231 if (simdOp.getLinearVars().size()) {
5232 linearClauseProcessor.initLinearVar(builder, moduleTranslation,
5233 loopInfo->getPreheader());
5234
5235 linearClauseProcessor.updateLinearVar(builder, loopInfo->getBody(),
5236 loopInfo->getIndVar());
5237 }
5238 builder.SetInsertPoint(*regionBlock, (*regionBlock)->begin());
5239
5240 for (size_t index = 0; index < simdOp.getLinearVars().size(); index++)
5241 linearClauseProcessor.rewriteInPlace(builder, loopInfo->getBody(),
5242 loopInfo->getLatch(), index);
5243
5244 ompBuilder->applySimd(loopInfo, alignedVars,
5245 simdOp.getIfExpr()
5246 ? moduleTranslation.lookupValue(simdOp.getIfExpr())
5247 : nullptr,
5248 order, simdlen, safelen);
5249
5250 linearClauseProcessor.updateLinearIV(builder, moduleTranslation);
5251 linearClauseProcessor.emitStoresForLinearVar(builder);
5252
5253 // We now need to reduce the per-simd-lane reduction variable into the
5254 // original variable. This works a bit differently to other reductions (e.g.
5255 // wsloop) because we don't need to call into the OpenMP runtime to handle
5256 // threads: everything happened in this one thread.
5257 for (auto [i, tuple] : llvm::enumerate(
5258 llvm::zip(reductionDecls, isByRef, simdOp.getReductionVars(),
5259 privateReductionVariables))) {
5260 auto [decl, byRef, reductionVar, privateReductionVar] = tuple;
5261
5262 OwningReductionGen gen = makeReductionGen(decl, builder, moduleTranslation);
5263 llvm::Value *originalVariable = moduleTranslation.lookupValue(reductionVar);
5264 llvm::Type *reductionType = moduleTranslation.convertType(decl.getType());
5265
5266 // We have one less load for by-ref case because that load is now inside of
5267 // the reduction region.
5268 llvm::Value *redValue = originalVariable;
5269 if (!byRef)
5270 redValue =
5271 builder.CreateLoad(reductionType, redValue, "red.value." + Twine(i));
5272 llvm::Value *privateRedValue = builder.CreateLoad(
5273 reductionType, privateReductionVar, "red.private.value." + Twine(i));
5274 llvm::Value *reduced;
5275
5276 auto res = gen(builder.saveIP(), redValue, privateRedValue, reduced);
5277 if (failed(handleError(res, opInst)))
5278 return failure();
5279 builder.restoreIP(res.get());
5280
5281 // For by-ref case, the store is inside of the reduction region.
5282 if (!byRef)
5283 builder.CreateStore(reduced, originalVariable);
5284 }
5285
5286 // After the construct, deallocate private reduction variables.
5287 SmallVector<Region *> reductionRegions;
5288 llvm::transform(reductionDecls, std::back_inserter(reductionRegions),
5289 [](omp::DeclareReductionOp reductionDecl) {
5290 return &reductionDecl.getCleanupRegion();
5291 });
5292 if (failed(inlineOmpRegionCleanup(reductionRegions, privateReductionVariables,
5293 moduleTranslation, builder,
5294 "omp.reduction.cleanup")))
5295 return failure();
5296
5297 return cleanupPrivateVars(simdOp, builder, moduleTranslation, simdOp.getLoc(),
5298 privateVarsInfo);
5299}
5300
5301/// Converts an OpenMP loop nest into LLVM IR using OpenMPIRBuilder.
5302static LogicalResult
5303convertOmpLoopNest(Operation &opInst, llvm::IRBuilderBase &builder,
5304 LLVM::ModuleTranslation &moduleTranslation) {
5305 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5306 auto loopOp = cast<omp::LoopNestOp>(opInst);
5307
5308 if (failed(checkImplementationStatus(opInst)))
5309 return failure();
5310
5311 // Set up the source location value for OpenMP runtime.
5312 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5313
5314 // Generator of the canonical loop body.
5317 auto bodyGen = [&](llvm::OpenMPIRBuilder::InsertPointTy ip,
5318 llvm::Value *iv) -> llvm::Error {
5319 // Make sure further conversions know about the induction variable.
5320 moduleTranslation.mapValue(
5321 loopOp.getRegion().front().getArgument(loopInfos.size()), iv);
5322
5323 // Capture the body insertion point for use in nested loops. BodyIP of the
5324 // CanonicalLoopInfo always points to the beginning of the entry block of
5325 // the body.
5326 bodyInsertPoints.push_back(ip);
5327
5328 if (loopInfos.size() != loopOp.getNumLoops() - 1)
5329 return llvm::Error::success();
5330
5331 // Convert the body of the loop.
5332 builder.restoreIP(ip);
5334 loopOp.getRegion(), "omp.loop_nest.region", builder, moduleTranslation);
5335 if (!regionBlock)
5336 return regionBlock.takeError();
5337
5338 builder.SetInsertPoint(*regionBlock, (*regionBlock)->begin());
5339 return llvm::Error::success();
5340 };
5341
5342 // Delegate actual loop construction to the OpenMP IRBuilder.
5343 // TODO: this currently assumes omp.loop_nest is semantically similar to SCF
5344 // loop, i.e. it has a positive step, uses signed integer semantics.
5345 // Reconsider this code when the nested loop operation clearly supports more
5346 // cases.
5347 for (unsigned i = 0, e = loopOp.getNumLoops(); i < e; ++i) {
5348 llvm::Value *lowerBound =
5349 moduleTranslation.lookupValue(loopOp.getLoopLowerBounds()[i]);
5350 llvm::Value *upperBound =
5351 moduleTranslation.lookupValue(loopOp.getLoopUpperBounds()[i]);
5352 llvm::Value *step = moduleTranslation.lookupValue(loopOp.getLoopSteps()[i]);
5353
5354 // Make sure loop trip count are emitted in the preheader of the outermost
5355 // loop at the latest so that they are all available for the new collapsed
5356 // loop will be created below.
5357 llvm::OpenMPIRBuilder::LocationDescription loc = ompLoc;
5358 llvm::OpenMPIRBuilder::InsertPointTy computeIP = ompLoc.IP;
5359 if (i != 0) {
5360 loc = llvm::OpenMPIRBuilder::LocationDescription(bodyInsertPoints.back(),
5361 ompLoc.DL);
5362 computeIP = loopInfos.front()->getPreheaderIP();
5363 }
5364
5366 ompBuilder->createCanonicalLoop(
5367 loc, bodyGen, lowerBound, upperBound, step,
5368 /*IsSigned=*/true, loopOp.getLoopInclusive(), computeIP);
5369
5370 if (failed(handleError(loopResult, *loopOp)))
5371 return failure();
5372
5373 loopInfos.push_back(*loopResult);
5374 }
5375
5376 llvm::OpenMPIRBuilder::InsertPointTy afterIP =
5377 loopInfos.front()->getAfterIP();
5378
5379 // Do tiling.
5380 if (const auto &tiles = loopOp.getTileSizes()) {
5381 llvm::Type *ivType = loopInfos.front()->getIndVarType();
5383
5384 for (auto tile : tiles.value()) {
5385 llvm::Value *tileVal = llvm::ConstantInt::get(ivType, tile);
5386 tileSizes.push_back(tileVal);
5387 }
5388
5389 std::vector<llvm::CanonicalLoopInfo *> newLoops =
5390 ompBuilder->tileLoops(ompLoc.DL, loopInfos, tileSizes);
5391
5392 // Update afterIP to get the correct insertion point after
5393 // tiling.
5394 llvm::BasicBlock *afterBB = newLoops.front()->getAfter();
5395 llvm::BasicBlock *afterAfterBB = afterBB->getSingleSuccessor();
5396 afterIP = {afterAfterBB, afterAfterBB->begin()};
5397
5398 // Update the loop infos.
5399 loopInfos.clear();
5400 for (const auto &newLoop : newLoops)
5401 loopInfos.push_back(newLoop);
5402 } // Tiling done.
5403
5404 // Do collapse.
5405 const auto &numCollapse = loopOp.getCollapseNumLoops();
5407 loopInfos.begin(), loopInfos.begin() + (numCollapse));
5408
5409 auto newTopLoopInfo =
5410 ompBuilder->collapseLoops(ompLoc.DL, collapseLoopInfos, {});
5411
5412 assert(newTopLoopInfo && "New top loop information is missing");
5413 moduleTranslation.stackWalk<OpenMPLoopInfoStackFrame>(
5414 [&](OpenMPLoopInfoStackFrame &frame) {
5415 frame.loopInfo = newTopLoopInfo;
5416 return WalkResult::interrupt();
5417 });
5418
5419 // Continue building IR after the loop. Note that the LoopInfo returned by
5420 // `collapseLoops` points inside the outermost loop and is intended for
5421 // potential further loop transformations. Use the insertion point stored
5422 // before collapsing loops instead.
5423 builder.restoreIP(afterIP);
5424 return success();
5425}
5426
5427/// Convert an omp.canonical_loop to LLVM-IR
5428static LogicalResult
5429convertOmpCanonicalLoopOp(omp::CanonicalLoopOp op, llvm::IRBuilderBase &builder,
5430 LLVM::ModuleTranslation &moduleTranslation) {
5431 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5432
5433 llvm::OpenMPIRBuilder::LocationDescription loopLoc(builder);
5434 Value loopIV = op.getInductionVar();
5435 Value loopTC = op.getTripCount();
5436
5437 llvm::Value *llvmTC = moduleTranslation.lookupValue(loopTC);
5438
5440 ompBuilder->createCanonicalLoop(
5441 loopLoc,
5442 [&](llvm::OpenMPIRBuilder::InsertPointTy ip, llvm::Value *llvmIV) {
5443 // Register the mapping of MLIR induction variable to LLVM-IR
5444 // induction variable
5445 moduleTranslation.mapValue(loopIV, llvmIV);
5446
5447 builder.restoreIP(ip);
5449 convertOmpOpRegions(op.getRegion(), "omp.loop.region", builder,
5450 moduleTranslation);
5451
5452 return bodyGenStatus.takeError();
5453 },
5454 llvmTC, "omp.loop");
5455 if (!llvmOrError)
5456 return op.emitError(llvm::toString(llvmOrError.takeError()));
5457
5458 llvm::CanonicalLoopInfo *llvmCLI = *llvmOrError;
5459 llvm::IRBuilderBase::InsertPoint afterIP = llvmCLI->getAfterIP();
5460 builder.restoreIP(afterIP);
5461
5462 // Register the mapping of MLIR loop to LLVM-IR OpenMPIRBuilder loop
5463 if (Value cli = op.getCli())
5464 moduleTranslation.mapOmpLoop(cli, llvmCLI);
5465
5466 return success();
5467}
5468
5469/// Apply a `#pragma omp unroll` / "!$omp unroll" transformation using the
5470/// OpenMPIRBuilder.
5471static LogicalResult
5472applyUnrollHeuristic(omp::UnrollHeuristicOp op, llvm::IRBuilderBase &builder,
5473 LLVM::ModuleTranslation &moduleTranslation) {
5474 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5475
5476 Value applyee = op.getApplyee();
5477 assert(applyee && "Loop to apply unrolling on required");
5478
5479 llvm::CanonicalLoopInfo *consBuilderCLI =
5480 moduleTranslation.lookupOMPLoop(applyee);
5481 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5482 ompBuilder->unrollLoopHeuristic(loc.DL, consBuilderCLI);
5483
5484 moduleTranslation.invalidateOmpLoop(applyee);
5485 return success();
5486}
5487
5488/// Apply a `#pragma omp unroll full` / `!$omp unroll full` transformation
5489/// using the OpenMPIRBuilder.
5490static LogicalResult
5491applyUnrollFull(omp::UnrollFullOp op, llvm::IRBuilderBase &builder,
5492 LLVM::ModuleTranslation &moduleTranslation) {
5493 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5494
5495 Value applyee = op.getApplyee();
5496 assert(applyee && "Loop to apply unrolling on required");
5497
5498 llvm::CanonicalLoopInfo *consBuilderCLI =
5499 moduleTranslation.lookupOMPLoop(applyee);
5500 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5501 ompBuilder->unrollLoopFull(loc.DL, consBuilderCLI);
5502
5503 moduleTranslation.invalidateOmpLoop(applyee);
5504 return success();
5505}
5506
5507/// Apply a `#pragma omp unroll partial` / `!$omp unroll partial`
5508/// transformation using the OpenMPIRBuilder.
5509static LogicalResult
5510applyUnrollPartial(omp::UnrollPartialOp op, llvm::IRBuilderBase &builder,
5511 LLVM::ModuleTranslation &moduleTranslation) {
5512 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5513
5514 Value applyee = op.getApplyee();
5515 assert(applyee && "Loop to apply unrolling on required");
5516
5517 llvm::CanonicalLoopInfo *consBuilderCLI =
5518 moduleTranslation.lookupOMPLoop(applyee);
5519 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5520
5521 // No generatee is supported yet, so the unrolled loop's CanonicalLoopInfo is
5522 // not requested and unrolling is deferred to LLVM's LoopUnroll pass.
5523 int32_t factor = static_cast<int32_t>(op.getUnrollFactor());
5524 ompBuilder->unrollLoopPartial(loc.DL, consBuilderCLI, factor,
5525 /*UnrolledCLI=*/nullptr);
5526
5527 moduleTranslation.invalidateOmpLoop(applyee);
5528 return success();
5529}
5530
5531/// Apply a `#pragma omp tile` / `!$omp tile` transformation using the
5532/// OpenMPIRBuilder.
5533static LogicalResult applyTile(omp::TileOp op, llvm::IRBuilderBase &builder,
5534 LLVM::ModuleTranslation &moduleTranslation) {
5535 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5536 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5537
5539 SmallVector<llvm::Value *> translatedSizes;
5540
5541 for (Value size : op.getSizes()) {
5542 llvm::Value *translatedSize = moduleTranslation.lookupValue(size);
5543 assert(translatedSize &&
5544 "sizes clause arguments must already be translated");
5545 translatedSizes.push_back(translatedSize);
5546 }
5547
5548 for (Value applyee : op.getApplyees()) {
5549 llvm::CanonicalLoopInfo *consBuilderCLI =
5550 moduleTranslation.lookupOMPLoop(applyee);
5551 assert(applyee && "Canonical loop must already been translated");
5552 translatedLoops.push_back(consBuilderCLI);
5553 }
5554
5555 auto generatedLoops =
5556 ompBuilder->tileLoops(loc.DL, translatedLoops, translatedSizes);
5557 if (!op.getGeneratees().empty()) {
5558 for (auto [mlirLoop, genLoop] :
5559 zip_equal(op.getGeneratees(), generatedLoops))
5560 moduleTranslation.mapOmpLoop(mlirLoop, genLoop);
5561 }
5562
5563 // CLIs can only be consumed once
5564 for (Value applyee : op.getApplyees())
5565 moduleTranslation.invalidateOmpLoop(applyee);
5566
5567 return success();
5568}
5569
5570/// Apply a `#pragma omp fuse` / `!$omp fuse` transformation using the
5571/// OpenMPIRBuilder.
5572static LogicalResult applyFuse(omp::FuseOp op, llvm::IRBuilderBase &builder,
5573 LLVM::ModuleTranslation &moduleTranslation) {
5574 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5575 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5576
5577 // Select what CLIs are going to be fused
5578 SmallVector<llvm::CanonicalLoopInfo *> beforeFuse, toFuse, afterFuse;
5579 for (size_t i = 0; i < op.getApplyees().size(); i++) {
5580 Value applyee = op.getApplyees()[i];
5581 llvm::CanonicalLoopInfo *consBuilderCLI =
5582 moduleTranslation.lookupOMPLoop(applyee);
5583 assert(applyee && "Canonical loop must already been translated");
5584 if (op.getFirst().has_value() && i < op.getFirst().value() - 1)
5585 beforeFuse.push_back(consBuilderCLI);
5586 else if (op.getCount().has_value() &&
5587 i >= op.getFirst().value() + op.getCount().value() - 1)
5588 afterFuse.push_back(consBuilderCLI);
5589 else
5590 toFuse.push_back(consBuilderCLI);
5591 }
5592 assert(
5593 (op.getGeneratees().empty() ||
5594 beforeFuse.size() + afterFuse.size() + 1 == op.getGeneratees().size()) &&
5595 "Wrong number of generatees");
5596
5597 // do the fuse
5598 auto generatedLoop = ompBuilder->fuseLoops(loc.DL, toFuse);
5599 if (!op.getGeneratees().empty()) {
5600 size_t i = 0;
5601 for (; i < beforeFuse.size(); i++)
5602 moduleTranslation.mapOmpLoop(op.getGeneratees()[i], beforeFuse[i]);
5603 moduleTranslation.mapOmpLoop(op.getGeneratees()[i++], generatedLoop);
5604 for (; i < afterFuse.size(); i++)
5605 moduleTranslation.mapOmpLoop(op.getGeneratees()[i], afterFuse[i]);
5606 }
5607
5608 // CLIs can only be consumed once
5609 for (Value applyee : op.getApplyees())
5610 moduleTranslation.invalidateOmpLoop(applyee);
5611
5612 return success();
5613}
5614
5615/// Convert an Atomic Ordering attribute to llvm::AtomicOrdering.
5616static llvm::AtomicOrdering
5617convertAtomicOrdering(std::optional<omp::ClauseMemoryOrderKind> ao) {
5618 if (!ao)
5619 return llvm::AtomicOrdering::Monotonic; // Default Memory Ordering
5620
5621 switch (*ao) {
5622 case omp::ClauseMemoryOrderKind::Seq_cst:
5623 return llvm::AtomicOrdering::SequentiallyConsistent;
5624 case omp::ClauseMemoryOrderKind::Acq_rel:
5625 return llvm::AtomicOrdering::AcquireRelease;
5626 case omp::ClauseMemoryOrderKind::Acquire:
5627 return llvm::AtomicOrdering::Acquire;
5628 case omp::ClauseMemoryOrderKind::Release:
5629 return llvm::AtomicOrdering::Release;
5630 case omp::ClauseMemoryOrderKind::Relaxed:
5631 return llvm::AtomicOrdering::Monotonic;
5632 }
5633 llvm_unreachable("Unknown ClauseMemoryOrderKind kind");
5634}
5635
5636/// Compute the cmpxchg failure ordering for an atomic compare op: use the
5637/// `fail` clause ordering when present (the verifier guarantees it is a valid
5638/// cmpxchg failure ordering), otherwise the strongest failure ordering derived
5639/// from the success ordering (which matches the OpenMPIRBuilder default).
5640static llvm::AtomicOrdering
5641getAtomicCompareFailureOrdering(omp::AtomicCompareOp atomicCompareOp,
5642 llvm::AtomicOrdering atomicOrdering) {
5643 if (atomicCompareOp.getFailMemoryOrder())
5644 return convertAtomicOrdering(atomicCompareOp.getFailMemoryOrder());
5645 return llvm::AtomicCmpXchgInst::getStrongestFailureOrdering(atomicOrdering);
5646}
5647
5648/// Convert omp.atomic.read operation to LLVM IR.
5649static LogicalResult
5650convertOmpAtomicRead(Operation &opInst, llvm::IRBuilderBase &builder,
5651 LLVM::ModuleTranslation &moduleTranslation) {
5652 auto readOp = cast<omp::AtomicReadOp>(opInst);
5653 if (failed(checkImplementationStatus(opInst)))
5654 return failure();
5655
5656 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5657 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
5658 findAllocInsertPoints(builder, moduleTranslation);
5659
5660 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5661
5662 llvm::AtomicOrdering AO = convertAtomicOrdering(readOp.getMemoryOrder());
5663 llvm::Value *x = moduleTranslation.lookupValue(readOp.getX());
5664 llvm::Value *v = moduleTranslation.lookupValue(readOp.getV());
5665
5666 llvm::Type *elementType =
5667 moduleTranslation.convertType(readOp.getElementType());
5668
5669 llvm::OpenMPIRBuilder::AtomicOpValue V = {v, elementType, false, false};
5670 llvm::OpenMPIRBuilder::AtomicOpValue X = {x, elementType, false, false};
5671 builder.restoreIP(ompBuilder->createAtomicRead(ompLoc, X, V, AO, allocaIP));
5672 return success();
5673}
5674
5675/// Converts an omp.atomic.write operation to LLVM IR.
5676static LogicalResult
5677convertOmpAtomicWrite(Operation &opInst, llvm::IRBuilderBase &builder,
5678 LLVM::ModuleTranslation &moduleTranslation) {
5679 auto writeOp = cast<omp::AtomicWriteOp>(opInst);
5680 if (failed(checkImplementationStatus(opInst)))
5681 return failure();
5682
5683 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5684 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
5685 findAllocInsertPoints(builder, moduleTranslation);
5686
5687 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5688 llvm::AtomicOrdering ao = convertAtomicOrdering(writeOp.getMemoryOrder());
5689 llvm::Value *expr = moduleTranslation.lookupValue(writeOp.getExpr());
5690 llvm::Value *dest = moduleTranslation.lookupValue(writeOp.getX());
5691 llvm::Type *ty = moduleTranslation.convertType(writeOp.getExpr().getType());
5692 llvm::OpenMPIRBuilder::AtomicOpValue x = {dest, ty, /*isSigned=*/false,
5693 /*isVolatile=*/false};
5694 builder.restoreIP(
5695 ompBuilder->createAtomicWrite(ompLoc, x, expr, ao, allocaIP));
5696 return success();
5697}
5698
5699/// Converts an LLVM dialect binary operation to the corresponding enum value
5700/// for `atomicrmw` supported binary operation.
5701static llvm::AtomicRMWInst::BinOp convertBinOpToAtomic(Operation &op) {
5703 .Case([&](LLVM::AddOp) { return llvm::AtomicRMWInst::BinOp::Add; })
5704 .Case([&](LLVM::SubOp) { return llvm::AtomicRMWInst::BinOp::Sub; })
5705 .Case([&](LLVM::AndOp) { return llvm::AtomicRMWInst::BinOp::And; })
5706 .Case([&](LLVM::OrOp) { return llvm::AtomicRMWInst::BinOp::Or; })
5707 .Case([&](LLVM::XOrOp) { return llvm::AtomicRMWInst::BinOp::Xor; })
5708 .Case([&](LLVM::UMaxOp) { return llvm::AtomicRMWInst::BinOp::UMax; })
5709 .Case([&](LLVM::UMinOp) { return llvm::AtomicRMWInst::BinOp::UMin; })
5710 .Case([&](LLVM::FAddOp) { return llvm::AtomicRMWInst::BinOp::FAdd; })
5711 .Case([&](LLVM::FSubOp) { return llvm::AtomicRMWInst::BinOp::FSub; })
5712 .Default(llvm::AtomicRMWInst::BinOp::BAD_BINOP);
5713}
5714
5715static void extractAtomicControlFlags(omp::AtomicUpdateOp atomicUpdateOp,
5716 bool &isIgnoreDenormalMode,
5717 bool &isFineGrainedMemory,
5718 bool &isRemoteMemory) {
5719 isIgnoreDenormalMode = false;
5720 isFineGrainedMemory = false;
5721 isRemoteMemory = false;
5722 if (atomicUpdateOp && atomicUpdateOp.getAtomicControlAttr()) {
5723 mlir::omp::AtomicControlAttr atomicControlAttr =
5724 atomicUpdateOp.getAtomicControlAttr();
5725 isIgnoreDenormalMode = atomicControlAttr.getIgnoreDenormalMode();
5726 isFineGrainedMemory = atomicControlAttr.getFineGrainedMemory();
5727 isRemoteMemory = atomicControlAttr.getRemoteMemory();
5728 }
5729}
5730
5731/// Converts an OpenMP atomic update operation using OpenMPIRBuilder.
5732static LogicalResult
5733convertOmpAtomicUpdate(omp::AtomicUpdateOp &opInst,
5734 llvm::IRBuilderBase &builder,
5735 LLVM::ModuleTranslation &moduleTranslation) {
5736 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5737 if (failed(checkImplementationStatus(*opInst)))
5738 return failure();
5739
5740 // Convert values and types.
5741 auto &innerOpList = opInst.getRegion().front().getOperations();
5742 bool isXBinopExpr{false};
5743 llvm::AtomicRMWInst::BinOp binop;
5744 mlir::Value mlirExpr;
5745 llvm::Value *llvmExpr = nullptr;
5746 llvm::Value *llvmX = nullptr;
5747 llvm::Type *llvmXElementType = nullptr;
5748 if (innerOpList.size() == 2) {
5749 // The two operations here are the update and the terminator.
5750 // Since we can identify the update operation, there is a possibility
5751 // that we can generate the atomicrmw instruction.
5752 mlir::Operation &innerOp = *opInst.getRegion().front().begin();
5753 if (!llvm::is_contained(innerOp.getOperands(),
5754 opInst.getRegion().getArgument(0))) {
5755 return opInst.emitError("no atomic update operation with region argument"
5756 " as operand found inside atomic.update region");
5757 }
5758 binop = convertBinOpToAtomic(innerOp);
5759 isXBinopExpr = innerOp.getOperand(0) == opInst.getRegion().getArgument(0);
5760 mlirExpr = (isXBinopExpr ? innerOp.getOperand(1) : innerOp.getOperand(0));
5761 llvmExpr = moduleTranslation.lookupValue(mlirExpr);
5762 } else {
5763 // Since the update region includes more than one operation
5764 // we will resort to generating a cmpxchg loop.
5765 binop = llvm::AtomicRMWInst::BinOp::BAD_BINOP;
5766 }
5767 llvmX = moduleTranslation.lookupValue(opInst.getX());
5768 llvmXElementType = moduleTranslation.convertType(
5769 opInst.getRegion().getArgument(0).getType());
5770 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {llvmX, llvmXElementType,
5771 /*isSigned=*/false,
5772 /*isVolatile=*/false};
5773
5774 llvm::AtomicOrdering atomicOrdering =
5775 convertAtomicOrdering(opInst.getMemoryOrder());
5776
5777 // Generate update code.
5778 auto updateFn =
5779 [&opInst, &moduleTranslation](
5780 llvm::Value *atomicx,
5781 llvm::IRBuilder<> &builder) -> llvm::Expected<llvm::Value *> {
5782 Block &bb = *opInst.getRegion().begin();
5783 moduleTranslation.mapValue(*opInst.getRegion().args_begin(), atomicx);
5784 moduleTranslation.mapBlock(&bb, builder.GetInsertBlock());
5785 if (failed(moduleTranslation.convertBlock(bb, true, builder)))
5786 return llvm::make_error<PreviouslyReportedError>();
5787
5788 omp::YieldOp yieldop = dyn_cast<omp::YieldOp>(bb.getTerminator());
5789 assert(yieldop && yieldop.getResults().size() == 1 &&
5790 "terminator must be omp.yield op and it must have exactly one "
5791 "argument");
5792 return moduleTranslation.lookupValue(yieldop.getResults()[0]);
5793 };
5794
5795 bool isIgnoreDenormalMode;
5796 bool isFineGrainedMemory;
5797 bool isRemoteMemory;
5798 extractAtomicControlFlags(opInst, isIgnoreDenormalMode, isFineGrainedMemory,
5799 isRemoteMemory);
5800 // Handle ambiguous alloca, if any.
5801 auto allocaIP = findAllocInsertPoints(builder, moduleTranslation);
5802 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5803 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
5804 ompBuilder->createAtomicUpdate(ompLoc, allocaIP, llvmAtomicX, llvmExpr,
5805 atomicOrdering, binop, updateFn,
5806 isXBinopExpr, isIgnoreDenormalMode,
5807 isFineGrainedMemory, isRemoteMemory);
5808
5809 if (failed(handleError(afterIP, *opInst)))
5810 return failure();
5811
5812 builder.restoreIP(*afterIP);
5813 return success();
5814}
5815
5816/// Helper to extract the OMPAtomicCompareOp from an integer comparison
5817/// predicate. Returns std::nullopt for unsupported predicates.
5818static std::optional<llvm::omp::OMPAtomicCompareOp>
5819convertICmpPredicateToAtomicCompareOp(LLVM::ICmpPredicate predicate) {
5820 switch (predicate) {
5821 case LLVM::ICmpPredicate::eq:
5822 return llvm::omp::OMPAtomicCompareOp::EQ;
5823 case LLVM::ICmpPredicate::slt:
5824 case LLVM::ICmpPredicate::ult:
5825 return llvm::omp::OMPAtomicCompareOp::MIN;
5826 case LLVM::ICmpPredicate::sgt:
5827 case LLVM::ICmpPredicate::ugt:
5828 return llvm::omp::OMPAtomicCompareOp::MAX;
5829 default:
5830 return std::nullopt;
5831 }
5832}
5833
5834/// Helper to extract the OMPAtomicCompareOp from a floating-point comparison
5835/// predicate. Returns std::nullopt for unsupported predicates.
5836static std::optional<llvm::omp::OMPAtomicCompareOp>
5837convertFCmpPredicateToAtomicCompareOp(LLVM::FCmpPredicate predicate) {
5838 switch (predicate) {
5839 case LLVM::FCmpPredicate::oeq:
5840 case LLVM::FCmpPredicate::ueq:
5841 return llvm::omp::OMPAtomicCompareOp::EQ;
5842 case LLVM::FCmpPredicate::olt:
5843 case LLVM::FCmpPredicate::ult:
5844 return llvm::omp::OMPAtomicCompareOp::MIN;
5845 case LLVM::FCmpPredicate::ogt:
5846 case LLVM::FCmpPredicate::ugt:
5847 return llvm::omp::OMPAtomicCompareOp::MAX;
5848 default:
5849 return std::nullopt;
5850 }
5851}
5852
5853/// Result of matching the decomposed complex equality pattern inside an atomic
5854/// compare region.
5856 bool isComplex = false;
5857 bool isNE = false; // `or` of the field compares => NE (unsupported).
5858 mlir::Value eAggregate; // The complex expected value (`e`).
5859 bool isXBinopExpr = false; // True if x is the first fcmp operand.
5860};
5861
5862/// Detect a decomposed complex equality comparison in an atomic compare region:
5863/// %re_x = llvm.extractvalue %xval[0]
5864/// %re_e = llvm.extractvalue %eStruct[0]
5865/// %cmp_re = llvm.fcmp "oeq" %re_x, %re_e
5866/// %im_x = llvm.extractvalue %xval[1]
5867/// %im_e = llvm.extractvalue %eStruct[1]
5868/// %cmp_im = llvm.fcmp "oeq" %im_x, %im_e
5869/// %cmp = llvm.and %cmp_re, %cmp_im (llvm.or would be NE)
5870/// It is recognised by an and/or whose operands are both fcmps operating on
5871/// extractvalues, one chain rooted at the block argument (x) and the other at
5872/// the expected complex value (e).
5875 auto traceToAggregate = [](mlir::Value v) -> mlir::Value {
5876 if (auto extractOp = v.getDefiningOp<LLVM::ExtractValueOp>())
5877 return extractOp.getContainer();
5878 return nullptr;
5879 };
5880 for (Operation &op : block.getOperations()) {
5881 if (!isa<LLVM::AndOp, LLVM::OrOp>(op))
5882 continue;
5883 auto lhsFcmp = op.getOperand(0).getDefiningOp<LLVM::FCmpOp>();
5884 auto rhsFcmp = op.getOperand(1).getDefiningOp<LLVM::FCmpOp>();
5885 if (!lhsFcmp || !rhsFcmp)
5886 continue;
5887 mlir::Value lhsAgg0 = traceToAggregate(lhsFcmp.getOperand(0));
5888 mlir::Value lhsAgg1 = traceToAggregate(lhsFcmp.getOperand(1));
5889 bool lhsXIsOp0 = (lhsAgg0 == block.getArgument(0));
5890 bool lhsXIsOp1 = (lhsAgg1 == block.getArgument(0));
5891 if (!lhsXIsOp0 && !lhsXIsOp1)
5892 continue;
5893 mlir::Value eAggregate = lhsXIsOp0 ? lhsAgg1 : lhsAgg0;
5894 if (!eAggregate)
5895 continue;
5896 result.isComplex = true;
5897 result.isNE = isa<LLVM::OrOp>(op);
5898 result.eAggregate = eAggregate;
5899 result.isXBinopExpr = lhsXIsOp0;
5900 break;
5901 }
5902 return result;
5903}
5904
5905/// Emit an IEEE-754-correct `cmpxchg` for a complex (struct-typed) atomic
5906/// compare with `fcmp oeq`. The old value of X is returned (as the complex
5907/// struct type) in \p oldComplex and the success flag (i1) in \p cmpOk.
5908/// \p failOrdering is the memory ordering used when the compare-exchange does
5909/// not store; it must be a valid cmpxchg failure ordering.
5910static void emitComplexAtomicCmpXchg(llvm::IRBuilderBase &builder,
5911 llvm::Value *llvmX, llvm::Type *complexTy,
5912 llvm::Value *eVal, llvm::Value *dVal,
5913 llvm::AtomicOrdering atomicOrdering,
5914 llvm::AtomicOrdering failOrdering,
5915 bool isWeak, llvm::Value *&oldComplex,
5916 llvm::Value *&cmpOk) {
5917 const llvm::DataLayout &DL =
5918 builder.GetInsertBlock()->getModule()->getDataLayout();
5919 unsigned totalBits = DL.getTypeStoreSizeInBits(complexTy).getFixedValue();
5920 llvm::IntegerType *intTy =
5921 llvm::IntegerType::get(builder.getContext(), totalBits);
5922 llvm::Align complexAlign = DL.getABITypeAlign(complexTy);
5923 llvm::Align intAlign = DL.getABITypeAlign(intTy);
5924 llvm::Align maxAlign = std::max(complexAlign, intAlign);
5925
5926 // Spill D to obtain its integer bit pattern for the swap value.
5927 llvm::AllocaInst *dAlloca =
5928 builder.CreateAlloca(complexTy, nullptr, "cmplx.d");
5929 dAlloca->setAlignment(maxAlign);
5930 builder.CreateAlignedStore(dVal, dAlloca, maxAlign);
5931 llvm::Value *dInt =
5932 builder.CreateAlignedLoad(intTy, dAlloca, maxAlign, "cmplx.d.int");
5933
5934 // Load X atomically and reinterpret as complex. Use the failure ordering: on
5935 // a failed component comparison we branch around the cmpxchg, so this load is
5936 // the only memory op on that path.
5937 llvm::LoadInst *xCurr =
5938 builder.CreateAlignedLoad(intTy, llvmX, maxAlign, "cmplx.x.load");
5939 xCurr->setAtomic(failOrdering);
5940 llvm::AllocaInst *xAlloca =
5941 builder.CreateAlloca(complexTy, nullptr, "cmplx.x");
5942 xAlloca->setAlignment(maxAlign);
5943 builder.CreateAlignedStore(xCurr, xAlloca, maxAlign);
5944 llvm::Value *xStruct =
5945 builder.CreateAlignedLoad(complexTy, xAlloca, maxAlign, "cmplx.x.val");
5946
5947 // Component-wise IEEE-754 equality: `fcmp oeq` yields false for NaN (so a
5948 // NaN component correctly makes the compare fail) and true for +0.0 vs -0.0
5949 // (so a zero-sign difference does not spuriously fail the compare).
5950 llvm::Value *reX = builder.CreateExtractValue(xStruct, 0);
5951 llvm::Value *imX = builder.CreateExtractValue(xStruct, 1);
5952 llvm::Value *reE = builder.CreateExtractValue(eVal, 0);
5953 llvm::Value *imE = builder.CreateExtractValue(eVal, 1);
5954 llvm::Value *reEq = builder.CreateFCmpOEQ(reX, reE, "cmplx.re.eq");
5955 llvm::Value *imEq = builder.CreateFCmpOEQ(imX, imE, "cmplx.im.eq");
5956 llvm::Value *fpEqual = builder.CreateAnd(reEq, imEq, "cmplx.eq");
5957
5958 // When the components compare equal, attempt the swap using X's own loaded
5959 // bit pattern as the comparand; otherwise the compare fails and X is left
5960 // unchanged (the captured old value is the value just loaded).
5961 llvm::BasicBlock *curBB = builder.GetInsertBlock();
5962 llvm::Function *fn = curBB->getParent();
5963 llvm::BasicBlock *swapBB =
5964 llvm::BasicBlock::Create(builder.getContext(), "cmplx.atomic.swap", fn);
5965 llvm::BasicBlock *exitBB =
5966 llvm::BasicBlock::Create(builder.getContext(), "cmplx.atomic.exit", fn);
5967 builder.CreateCondBr(fpEqual, swapBB, exitBB);
5968
5969 builder.SetInsertPoint(swapBB);
5970 llvm::AtomicCmpXchgInst *cmpXchg = builder.CreateAtomicCmpXchg(
5971 llvmX, xCurr, dInt, maxAlign, atomicOrdering, failOrdering);
5972 cmpXchg->setWeak(isWeak);
5973 llvm::Value *oldSwap = builder.CreateExtractValue(cmpXchg, 0);
5974 llvm::Value *okSwap = builder.CreateExtractValue(cmpXchg, 1);
5975 builder.CreateBr(exitBB);
5976
5977 // Merge the swap and no-swap paths.
5978 builder.SetInsertPoint(exitBB);
5979 llvm::PHINode *oldIntPHI = builder.CreatePHI(intTy, 2, "cmplx.old.int");
5980 oldIntPHI->addIncoming(oldSwap, swapBB);
5981 oldIntPHI->addIncoming(xCurr, curBB);
5982 llvm::PHINode *okPHI = builder.CreatePHI(builder.getInt1Ty(), 2, "cmplx.ok");
5983 okPHI->addIncoming(okSwap, swapBB);
5984 okPHI->addIncoming(builder.getFalse(), curBB);
5985
5986 // Reinterpret the old integer value as the complex struct via memory.
5987 llvm::AllocaInst *oldAlloca =
5988 builder.CreateAlloca(complexTy, nullptr, "cmplx.old");
5989 oldAlloca->setAlignment(maxAlign);
5990 builder.CreateAlignedStore(oldIntPHI, oldAlloca, maxAlign);
5991 oldComplex = builder.CreateAlignedLoad(complexTy, oldAlloca, maxAlign,
5992 "cmplx.old.val");
5993 cmpOk = okPHI;
5994}
5995
5996/// Holds the extracted comparison pattern information from an atomic compare
5997/// region.
5999 llvm::omp::OMPAtomicCompareOp compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
6000 llvm::Value *eVal = nullptr;
6001 llvm::Value *dVal = nullptr;
6002 bool isXBinopExpr = false;
6003 bool isSigned = false;
6004};
6005/// Extract comparison predicate, expected value (e), desired value (d), and
6006/// related flags from an atomic compare region block by scanning for
6007/// icmp/fcmp/select/min/max operations.
6008static LogicalResult extractAtomicComparePattern(
6009 Block &block,
6010 llvm::function_ref<llvm::Value *(mlir::Value)> materializeValue,
6011 omp::AtomicCompareOp atomicCompareOp, AtomicComparePatternInfo &info) {
6012 // Complex equality is a decomposed per-field pattern (extractvalue + fcmp +
6013 // and) rather than a single scalar compare. Detect it first so the scalar
6014 // icmp/fcmp handling below does not mistake a real/imaginary field for the
6015 // whole expected value.
6017 cplx.isComplex) {
6018 if (cplx.isNE)
6019 return atomicCompareOp.emitError(
6020 "unsupported comparison predicate (NE) for complex atomic compare");
6021 info.compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
6022 info.isXBinopExpr = cplx.isXBinopExpr;
6023 info.eVal = materializeValue(cplx.eAggregate);
6024 for (Operation &op : block.getOperations()) {
6025 if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
6026 info.dVal = materializeValue(selectOp.getTrueValue());
6027 break;
6028 }
6029 }
6030 return success();
6031 }
6032
6033 for (Operation &op : block.getOperations()) {
6034 // Pre-filter: skip icmps that don't involve the block argument
6035 // (e.g., truthiness extractions from logical-to-integer conversion).
6036 if (auto icmpOp = dyn_cast<LLVM::ICmpOp>(op);
6037 icmpOp && icmpOp.getOperand(0) != block.getArgument(0) &&
6038 icmpOp.getOperand(1) != block.getArgument(0))
6039 continue;
6040
6041 LogicalResult result =
6043 .Case<LLVM::ICmpOp>([&](LLVM::ICmpOp icmpOp) -> LogicalResult {
6044 auto maybeOp =
6045 convertICmpPredicateToAtomicCompareOp(icmpOp.getPredicate());
6046 if (!maybeOp)
6047 return atomicCompareOp.emitError(
6048 "unsupported comparison predicate in atomic compare");
6049 info.compareOp = *maybeOp;
6050 LLVM::ICmpPredicate pred = icmpOp.getPredicate();
6051 info.isSigned = (pred == LLVM::ICmpPredicate::slt ||
6052 pred == LLVM::ICmpPredicate::sgt ||
6053 pred == LLVM::ICmpPredicate::sle ||
6054 pred == LLVM::ICmpPredicate::sge);
6055 info.isXBinopExpr =
6056 (icmpOp.getOperand(0) == block.getArgument(0));
6057 mlir::Value eOperand = info.isXBinopExpr ? icmpOp.getOperand(1)
6058 : icmpOp.getOperand(0);
6059 info.eVal = materializeValue(eOperand);
6060 return success();
6061 })
6062 .Case<LLVM::FCmpOp>([&](LLVM::FCmpOp fcmpOp) -> LogicalResult {
6063 auto maybeOp =
6064 convertFCmpPredicateToAtomicCompareOp(fcmpOp.getPredicate());
6065 if (!maybeOp)
6066 return atomicCompareOp.emitError(
6067 "unsupported comparison predicate in atomic compare");
6068 info.compareOp = *maybeOp;
6069 info.isXBinopExpr =
6070 (fcmpOp.getOperand(0) == block.getArgument(0));
6071 mlir::Value eOperand = info.isXBinopExpr ? fcmpOp.getOperand(1)
6072 : fcmpOp.getOperand(0);
6073 info.eVal = materializeValue(eOperand);
6074 return success();
6075 })
6076 .Case<LLVM::SelectOp>([&](LLVM::SelectOp selectOp) {
6077 if (!info.dVal)
6078 info.dVal = materializeValue(selectOp.getTrueValue());
6079 return success();
6080 })
6081 .Case<mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
6082 mlir::arith::MaxUIOp, mlir::arith::MinUIOp,
6083 mlir::arith::MaximumFOp, mlir::arith::MinimumFOp,
6084 LLVM::SMaxOp, LLVM::SMinOp, LLVM::UMaxOp, LLVM::UMinOp,
6085 LLVM::MaxNumOp, LLVM::MinNumOp>([&](Operation *) {
6086 // Canonicalized min/max ops (arith or LLVM intrinsic form).
6087 // max(x,e) came from slt/ult/olt -> OMPAtomicCompareOp::MIN
6088 // min(x,e) came from sgt/ugt/ogt -> OMPAtomicCompareOp::MAX
6089 // (OMPIRBuilder inverts: MIN->atomicrmw max, MAX->atomicrmw min)
6090 bool isMax = isa<mlir::arith::MaxSIOp, mlir::arith::MaxUIOp,
6091 mlir::arith::MaximumFOp, LLVM::SMaxOp,
6092 LLVM::UMaxOp, LLVM::MaxNumOp>(op);
6093 info.compareOp = isMax ? llvm::omp::OMPAtomicCompareOp::MIN
6094 : llvm::omp::OMPAtomicCompareOp::MAX;
6095 info.isSigned = isa<mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
6096 LLVM::SMaxOp, LLVM::SMinOp>(op);
6097 info.isXBinopExpr = (op.getOperand(0) == block.getArgument(0));
6098 mlir::Value eOperand =
6099 info.isXBinopExpr ? op.getOperand(1) : op.getOperand(0);
6100 info.eVal = materializeValue(eOperand);
6101 info.dVal = info.eVal;
6102 return success();
6103 })
6104 .Default([](Operation *) { return success(); });
6105
6106 if (failed(result))
6107 return result;
6108 }
6109 return success();
6110}
6111
6112static LogicalResult
6113convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
6114 llvm::IRBuilderBase &builder,
6115 LLVM::ModuleTranslation &moduleTranslation) {
6116 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
6117 if (failed(checkImplementationStatus(*atomicCaptureOp)))
6118 return failure();
6119
6120 omp::AtomicUpdateOp atomicUpdateOp = atomicCaptureOp.getAtomicUpdateOp();
6121 omp::AtomicWriteOp atomicWriteOp = atomicCaptureOp.getAtomicWriteOp();
6122 omp::AtomicCompareOp atomicCompareOp = atomicCaptureOp.getAtomicCompareOp();
6123
6124 // If the capture contains an atomic.compare, delegate to
6125 // createAtomicCompare with the capture variable (V) set.
6126 if (atomicCompareOp) {
6127 omp::AtomicReadOp atomicReadOp = atomicCaptureOp.getAtomicReadOp();
6128 assert(atomicReadOp && "expected atomic.read in capture+compare");
6129
6130 Region &region = atomicCompareOp.getRegion();
6131 Block &block = region.front();
6132
6133 llvm::Type *llvmXElementType =
6134 moduleTranslation.convertType(block.getArgument(0).getType());
6135 llvm::Value *llvmX = moduleTranslation.lookupValue(atomicCompareOp.getX());
6136 llvm::Value *llvmV = moduleTranslation.lookupValue(atomicReadOp.getV());
6137
6138 bool isSigned = false;
6139 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {
6140 llvmX, llvmXElementType, isSigned, /*IsVolatile=*/false};
6141 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicV = {
6142 llvmV, llvmXElementType, /*isSigned=*/false, /*IsVolatile=*/false};
6143 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicR = {nullptr, nullptr, false,
6144 false};
6145
6146 llvm::AtomicOrdering atomicOrdering =
6147 convertAtomicOrdering(atomicCaptureOp.getMemoryOrder());
6148
6149 // Pre-translate non-pattern operations inside the compare region.
6150 auto isAtomicComparePatternOp = [](Operation &op) {
6151 return llvm::isa<LLVM::ICmpOp, LLVM::FCmpOp, LLVM::SelectOp, LLVM::AndOp,
6152 LLVM::OrOp, LLVM::SMaxOp, LLVM::SMinOp, LLVM::UMaxOp,
6153 LLVM::UMinOp, LLVM::MaxNumOp, LLVM::MinNumOp,
6154 mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
6155 mlir::arith::MaxUIOp, mlir::arith::MinUIOp,
6156 mlir::arith::MaximumFOp, mlir::arith::MinimumFOp>(op);
6157 };
6158 for (Operation &op : block.without_terminator()) {
6159 if (isAtomicComparePatternOp(op))
6160 continue;
6161 bool allOperandsMapped =
6162 llvm::all_of(op.getOperands(), [&](mlir::Value v) {
6163 return moduleTranslation.lookupValue(v) != nullptr;
6164 });
6165 if (!allOperandsMapped)
6166 continue;
6167 if (failed(moduleTranslation.convertOperation(op, builder)))
6168 return atomicCompareOp.emitError(
6169 "failed to translate operation inside atomic compare region");
6170 }
6171
6172 auto materializeValue = [&](mlir::Value val) -> llvm::Value * {
6173 if (llvm::Value *existing = moduleTranslation.lookupValue(val))
6174 return existing;
6175 if (auto loadOp = val.getDefiningOp<LLVM::LoadOp>()) {
6176 if (loadOp->getParentRegion() == &region) {
6177 llvm::Value *loadAddr =
6178 moduleTranslation.lookupValue(loadOp.getAddr());
6179 if (!loadAddr)
6180 return nullptr;
6181 llvm::Type *loadType =
6182 moduleTranslation.convertType(loadOp.getResult().getType());
6183 return builder.CreateLoad(loadType, loadAddr);
6184 }
6185 }
6186 return nullptr;
6187 };
6188
6189 // Extract comparison predicate, eVal, and dVal from the region.
6190 AtomicComparePatternInfo patternInfo;
6191 if (failed(extractAtomicComparePattern(block, materializeValue,
6192 atomicCompareOp, patternInfo)))
6193 return failure();
6194
6195 llvm::omp::OMPAtomicCompareOp compareOp = patternInfo.compareOp;
6196 llvm::Value *eVal = patternInfo.eVal;
6197 llvm::Value *dVal = patternInfo.dVal;
6198 bool isXBinopExpr = patternInfo.isXBinopExpr;
6199 isSigned = patternInfo.isSigned;
6200
6201 if (!eVal)
6202 return atomicCompareOp.emitError(
6203 "failed to extract expected value (e) from atomic compare region");
6204 if (!dVal) {
6205 auto yieldOp = cast<omp::YieldOp>(block.getTerminator());
6206 if (yieldOp.getResults().empty())
6207 return atomicCompareOp.emitError(
6208 "failed to extract desired value (d) from atomic compare region");
6209 dVal = materializeValue(yieldOp.getResults()[0]);
6210 }
6211
6212 llvmAtomicX.IsSigned = isSigned;
6213
6214 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6215 bool isReadFirst = isa<omp::AtomicReadOp>(atomicCaptureOp.getFirstOp());
6216 bool isPostfixCapture = !isReadFirst;
6217 bool isFailOnly = atomicCaptureOp.getFailOnly();
6218
6219 // Complex equality capture: x is struct-typed, which the OMPIRBuilder
6220 // cannot handle, so emit an IEEE-754-correct cmpxchg (as in the non-capture
6221 // complex path) and reconstruct the captured value from its result. The
6222 // helper compares components with `fcmp oeq` so `-0.0 == +0.0` and `NaN`
6223 // are handled as in the scalar float path. Complex only supports the ==
6224 // comparison.
6225 if (llvmXElementType->isStructTy()) {
6226 llvm::Value *oldComplex = nullptr;
6227 llvm::Value *cmpOk = nullptr;
6228 llvm::AtomicOrdering failOrdering =
6229 getAtomicCompareFailureOrdering(atomicCompareOp, atomicOrdering);
6230 emitComplexAtomicCmpXchg(builder, llvmX, llvmXElementType, eVal, dVal,
6231 atomicOrdering, failOrdering,
6232 atomicCompareOp.getWeak(), oldComplex, cmpOk);
6233
6234 if (isFailOnly) {
6235 // v is written only when the compare fails (cmpOk == false).
6236 llvm::Value *cmpFailed = builder.CreateNot(cmpOk);
6237 llvm::BasicBlock *curBB = builder.GetInsertBlock();
6238 llvm::Function *fn = curBB->getParent();
6239 llvm::BasicBlock *contBB = llvm::BasicBlock::Create(
6240 builder.getContext(), "omp.atomic.cont", fn);
6241 llvm::BasicBlock *exitBB = llvm::BasicBlock::Create(
6242 builder.getContext(), "omp.atomic.exit", fn);
6243 builder.CreateCondBr(cmpFailed, contBB, exitBB);
6244 builder.SetInsertPoint(contBB);
6245 builder.CreateStore(oldComplex, llvmAtomicV.Var,
6246 llvmAtomicV.IsVolatile);
6247 builder.CreateBr(exitBB);
6248 builder.SetInsertPoint(exitBB);
6249 } else if (isPostfixCapture) {
6250 // v gets the new value of x: d on success, old x otherwise.
6251 llvm::Value *newComplex = builder.CreateSelect(cmpOk, dVal, oldComplex);
6252 builder.CreateStore(newComplex, llvmAtomicV.Var,
6253 llvmAtomicV.IsVolatile);
6254 } else {
6255 // Prefix: v gets the old value of x.
6256 builder.CreateStore(oldComplex, llvmAtomicV.Var,
6257 llvmAtomicV.IsVolatile);
6258 }
6259
6260 // Emit flush after atomic compare if needed (release/acq_rel/seq_cst).
6261 if (atomicOrdering == llvm::AtomicOrdering::Release ||
6262 atomicOrdering == llvm::AtomicOrdering::AcquireRelease ||
6263 atomicOrdering == llvm::AtomicOrdering::SequentiallyConsistent) {
6264 llvm::OpenMPIRBuilder::LocationDescription flushLoc(builder);
6265 ompBuilder->createFlush(flushLoc);
6266 }
6267 return success();
6268 }
6269
6270 // Min/max (<, >) comparisons lower to an atomicrmw. The OMPIRBuilder has no
6271 // notion of a failed compare for an atomicrmw, so the fail-only capture
6272 // form (v written only when the compare fails) has no valid mapping and is
6273 // Min/max (<, >) comparisons lower to an atomicrmw. The OMPIRBuilder has no
6274 // notion of a failed compare for an atomicrmw (it asserts on IsFailOnly),
6275 // so for min/max the fail-only capture is reconstructed manually below.
6276 bool isMinMax = compareOp != llvm::omp::OMPAtomicCompareOp::EQ;
6277
6278 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicVForCall = llvmAtomicV;
6279 // Capture into V is reconstructed manually below for:
6280 // * postfix capture (v gets the new value of x): equality from the
6281 // cmpxchg result, min/max from the atomicrmw result;
6282 // * min/max fail-only capture (the OMPIRBuilder cannot express it).
6283 // Bypass V in the OMPIRBuilder for those cases so it does not also emit its
6284 // own (for min/max, incorrect or unsupported) capture store.
6285 bool minMaxManualCapture = isMinMax && (isPostfixCapture || isFailOnly);
6286 bool eqPostfixManualCapture = !isMinMax && isPostfixCapture && !isFailOnly;
6287 if (minMaxManualCapture || eqPostfixManualCapture)
6288 llvmAtomicVForCall = {nullptr, nullptr, false, false};
6289
6290 // The OMPIRBuilder only understands IsFailOnly for the equality (cmpxchg)
6291 // path; for min/max it would assert. Min/max fail-only is handled here.
6292 bool builderFailOnly = isFailOnly && !isMinMax;
6293
6294 // IsPostfixUpdate selects which value the OMPIRBuilder captures into V:
6295 // * min/max prefix and equality prefix: a direct store of the old value
6296 // (IsPostfixUpdate=true).
6297 // * equality fail-only: a conditional store (IsPostfixUpdate=false).
6298 // Manually-reconstructed captures bypass V above.
6299 bool isPostfixUpdate = !builderFailOnly;
6300
6301 bool isWeak = atomicCompareOp.getWeak();
6302 bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
6303 llvm::AtomicOrdering failureOrdering =
6304 getAtomicCompareFailureOrdering(atomicCompareOp, atomicOrdering);
6305 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
6306 ompBuilder->createAtomicCompare(
6307 ompLoc, llvmAtomicX, llvmAtomicVForCall, llvmAtomicR, eVal, dVal,
6308 atomicOrdering, compareOp, isXBinopExpr, isPostfixUpdate,
6309 builderFailOnly, failureOrdering, isWeak);
6310 ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
6311
6312 if (failed(handleError(afterIP, *atomicCaptureOp)))
6313 return failure();
6314
6315 builder.restoreIP(*afterIP);
6316
6317 // Min/max capture is reconstructed from the atomicrmw the OMPIRBuilder
6318 // emits (its result is the old value of x). V was bypassed above.
6319 // * postfix: v gets the new value min/max(old, e);
6320 // * fail-only: v gets the old value, but only when the compare failed
6321 // (i.e. the atomicrmw did not change x).
6322 // (Prefix min/max captures the old value directly through V, so nothing
6323 // extra is needed there.)
6324 if (isMinMax && (isPostfixCapture || isFailOnly)) {
6325 llvm::BasicBlock *curBB = builder.GetInsertBlock();
6326 llvm::AtomicRMWInst *rmw = nullptr;
6327 for (auto &inst : llvm::reverse(*curBB)) {
6328 if (auto *r = dyn_cast<llvm::AtomicRMWInst>(&inst)) {
6329 rmw = r;
6330 break;
6331 }
6332 }
6333 assert(rmw && "expected atomicrmw for min/max compare capture");
6334 llvm::Value *oldVal = rmw;
6335 llvm::Value *rhs = rmw->getValOperand();
6336
6337 if (isFailOnly) {
6338 // The compare "failed" (the else branch runs) exactly when the
6339 // atomicrmw did not change x. Recompute the original update condition
6340 // on the old value and negate it. v is stored only in that case.
6341 llvm::CmpInst::Predicate updatePred;
6342 switch (rmw->getOperation()) {
6343 case llvm::AtomicRMWInst::Min:
6344 updatePred = llvm::CmpInst::ICMP_SGT;
6345 break;
6346 case llvm::AtomicRMWInst::Max:
6347 updatePred = llvm::CmpInst::ICMP_SLT;
6348 break;
6349 case llvm::AtomicRMWInst::UMin:
6350 updatePred = llvm::CmpInst::ICMP_UGT;
6351 break;
6352 case llvm::AtomicRMWInst::UMax:
6353 updatePred = llvm::CmpInst::ICMP_ULT;
6354 break;
6355 case llvm::AtomicRMWInst::FMin:
6356 updatePred = llvm::CmpInst::FCMP_OGT;
6357 break;
6358 case llvm::AtomicRMWInst::FMax:
6359 updatePred = llvm::CmpInst::FCMP_OLT;
6360 break;
6361 default:
6362 llvm_unreachable(
6363 "unexpected atomicrmw op for min/max compare capture");
6364 }
6365 llvm::Value *updated = builder.CreateCmp(updatePred, oldVal, rhs);
6366 llvm::Value *failed = builder.CreateNot(updated);
6367 llvm::Function *fn = curBB->getParent();
6368 llvm::BasicBlock *contBB = llvm::BasicBlock::Create(
6369 builder.getContext(), "omp.atomic.cont", fn);
6370 llvm::BasicBlock *exitBB = llvm::BasicBlock::Create(
6371 builder.getContext(), "omp.atomic.exit", fn);
6372 builder.CreateCondBr(failed, contBB, exitBB);
6373 builder.SetInsertPoint(contBB);
6374 builder.CreateStore(oldVal, llvmAtomicV.Var, llvmAtomicV.IsVolatile);
6375 builder.CreateBr(exitBB);
6376 builder.SetInsertPoint(exitBB);
6377 } else {
6378 llvm::Intrinsic::ID id;
6379 switch (rmw->getOperation()) {
6380 case llvm::AtomicRMWInst::Min:
6381 id = llvm::Intrinsic::smin;
6382 break;
6383 case llvm::AtomicRMWInst::Max:
6384 id = llvm::Intrinsic::smax;
6385 break;
6386 case llvm::AtomicRMWInst::UMin:
6387 id = llvm::Intrinsic::umin;
6388 break;
6389 case llvm::AtomicRMWInst::UMax:
6390 id = llvm::Intrinsic::umax;
6391 break;
6392 case llvm::AtomicRMWInst::FMin:
6393 id = llvm::Intrinsic::minnum;
6394 break;
6395 case llvm::AtomicRMWInst::FMax:
6396 id = llvm::Intrinsic::maxnum;
6397 break;
6398 default:
6399 llvm_unreachable(
6400 "unexpected atomicrmw op for min/max compare capture");
6401 }
6402 llvm::Value *newVal = builder.CreateBinaryIntrinsic(id, oldVal, rhs);
6403 builder.CreateStore(newVal, llvmAtomicV.Var, llvmAtomicV.IsVolatile);
6404 }
6405 }
6406
6407 // Equality postfix: v = select(success, D, old) — reconstructs the new
6408 // value of x from the cmpxchg result.
6409 if (!isMinMax && isPostfixCapture && !isFailOnly) {
6410 llvm::BasicBlock *curBB = builder.GetInsertBlock();
6411 llvm::Value *oldVal = nullptr;
6412 llvm::Value *successVal = nullptr;
6413
6414 // Integer path (and non-HandleFPNegZero FP path): a single cmpxchg
6415 // lives in the current block.
6416 for (auto &inst : llvm::reverse(*curBB)) {
6417 if (isa<llvm::AtomicCmpXchgInst>(&inst)) {
6418 oldVal = builder.CreateExtractValue(&inst, /*Idxs=*/0);
6419 successVal = builder.CreateExtractValue(&inst, /*Idxs=*/1);
6420 break;
6421 }
6422 }
6423
6424 // FP HandleFPNegZero path: the OMPIRBuilder emits a multi-block
6425 // structure (NaN / ±0.0 handling) with cmpxchg in predecessor
6426 // blocks. Results are merged via PHI nodes in the current (exit)
6427 // block: an i1 PHI for success and a bitcast of an integer PHI
6428 // for the old FP value.
6429 if (!oldVal) {
6430 for (auto &inst : *curBB) {
6431 auto *phi = dyn_cast<llvm::PHINode>(&inst);
6432 if (!phi)
6433 break;
6434 if (phi->getType()->isIntegerTy(1))
6435 successVal = phi;
6436 }
6437 for (auto &inst : *curBB) {
6438 if (auto *bc = dyn_cast<llvm::BitCastInst>(&inst)) {
6439 oldVal = bc;
6440 break;
6441 }
6442 }
6443 }
6444
6445 assert(oldVal && "expected cmpxchg or PHI+bitcast for compare capture");
6446 assert(successVal && "expected success flag for compare capture");
6447 llvm::Value *newVal = builder.CreateSelect(successVal, dVal, oldVal);
6448 builder.CreateStore(newVal, llvmAtomicV.Var, llvmAtomicV.IsVolatile);
6449 }
6450
6451 return success();
6452 }
6453
6454 mlir::Value mlirExpr;
6455 bool isXBinopExpr = false, isPostfixUpdate = false;
6456 llvm::AtomicRMWInst::BinOp binop = llvm::AtomicRMWInst::BinOp::BAD_BINOP;
6457
6458 assert((atomicUpdateOp || atomicWriteOp) &&
6459 "internal op must be an atomic.update or atomic.write op");
6460
6461 if (atomicWriteOp) {
6462 isPostfixUpdate = true;
6463 mlirExpr = atomicWriteOp.getExpr();
6464 } else {
6465 isPostfixUpdate = atomicCaptureOp.getSecondOp() ==
6466 atomicCaptureOp.getAtomicUpdateOp().getOperation();
6467 auto &innerOpList = atomicUpdateOp.getRegion().front().getOperations();
6468 // Find the binary update operation that uses the region argument
6469 // and get the expression to update
6470 if (innerOpList.size() == 2) {
6471 mlir::Operation &innerOp = *atomicUpdateOp.getRegion().front().begin();
6472 if (!llvm::is_contained(innerOp.getOperands(),
6473 atomicUpdateOp.getRegion().getArgument(0))) {
6474 return atomicUpdateOp.emitError(
6475 "no atomic update operation with region argument"
6476 " as operand found inside atomic.update region");
6477 }
6478 binop = convertBinOpToAtomic(innerOp);
6479 isXBinopExpr =
6480 innerOp.getOperand(0) == atomicUpdateOp.getRegion().getArgument(0);
6481 mlirExpr = (isXBinopExpr ? innerOp.getOperand(1) : innerOp.getOperand(0));
6482 } else {
6483 binop = llvm::AtomicRMWInst::BinOp::BAD_BINOP;
6484 }
6485 }
6486
6487 llvm::Value *llvmExpr = moduleTranslation.lookupValue(mlirExpr);
6488 llvm::Value *llvmX =
6489 moduleTranslation.lookupValue(atomicCaptureOp.getAtomicReadOp().getX());
6490 llvm::Value *llvmV =
6491 moduleTranslation.lookupValue(atomicCaptureOp.getAtomicReadOp().getV());
6492 llvm::Type *llvmXElementType = moduleTranslation.convertType(
6493 atomicCaptureOp.getAtomicReadOp().getElementType());
6494 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {llvmX, llvmXElementType,
6495 /*isSigned=*/false,
6496 /*isVolatile=*/false};
6497 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicV = {llvmV, llvmXElementType,
6498 /*isSigned=*/false,
6499 /*isVolatile=*/false};
6500
6501 llvm::AtomicOrdering atomicOrdering =
6502 convertAtomicOrdering(atomicCaptureOp.getMemoryOrder());
6503
6504 auto updateFn =
6505 [&](llvm::Value *atomicx,
6506 llvm::IRBuilder<> &builder) -> llvm::Expected<llvm::Value *> {
6507 if (atomicWriteOp)
6508 return moduleTranslation.lookupValue(atomicWriteOp.getExpr());
6509 Block &bb = *atomicUpdateOp.getRegion().begin();
6510 moduleTranslation.mapValue(*atomicUpdateOp.getRegion().args_begin(),
6511 atomicx);
6512 moduleTranslation.mapBlock(&bb, builder.GetInsertBlock());
6513 if (failed(moduleTranslation.convertBlock(bb, true, builder)))
6514 return llvm::make_error<PreviouslyReportedError>();
6515
6516 omp::YieldOp yieldop = dyn_cast<omp::YieldOp>(bb.getTerminator());
6517 assert(yieldop && yieldop.getResults().size() == 1 &&
6518 "terminator must be omp.yield op and it must have exactly one "
6519 "argument");
6520 return moduleTranslation.lookupValue(yieldop.getResults()[0]);
6521 };
6522
6523 bool isIgnoreDenormalMode;
6524 bool isFineGrainedMemory;
6525 bool isRemoteMemory;
6526 extractAtomicControlFlags(atomicUpdateOp, isIgnoreDenormalMode,
6527 isFineGrainedMemory, isRemoteMemory);
6528 // Handle ambiguous alloca, if any.
6529 auto allocaIP = findAllocInsertPoints(builder, moduleTranslation);
6530 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6531 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
6532 ompBuilder->createAtomicCapture(
6533 ompLoc, allocaIP, llvmAtomicX, llvmAtomicV, llvmExpr, atomicOrdering,
6534 binop, updateFn, atomicUpdateOp, isPostfixUpdate, isXBinopExpr,
6535 isIgnoreDenormalMode, isFineGrainedMemory, isRemoteMemory);
6536
6537 if (failed(handleError(afterIP, *atomicCaptureOp)))
6538 return failure();
6539
6540 builder.restoreIP(*afterIP);
6541 return success();
6542}
6543
6544/// Converts an omp.atomic.compare operation to LLVM IR.
6545///
6546/// if (x == e) x = d
6547/// The region contains a comparison + select pattern:
6548/// ^bb0(%xval: T):
6549/// %cmp = llvm.icmp/fcmp <pred> %xval, %e : T
6550/// %sel = llvm.select %cmp, %d, %xval : i1, T
6551/// omp.yield(%sel : T)
6552///
6553/// From MLIR extract:
6554/// 1) comparison operator
6555/// 2) expected value (e)
6556/// 3) desired value (d)
6557/// These are passed to OpenMPIRBuilder::createAtomicCompare which generates
6558/// the actual cmpxchg / atomicrmw instruction.
6559///
6560static LogicalResult
6561convertOmpAtomicCompare(omp::AtomicCompareOp atomicCompareOp,
6562 llvm::IRBuilderBase &builder,
6563 LLVM::ModuleTranslation &moduleTranslation) {
6564 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
6565 if (failed(checkImplementationStatus(*atomicCompareOp)))
6566 return failure();
6567
6568 Region &region = atomicCompareOp.getRegion();
6569 Block &block = region.front();
6570
6571 // Determine element type from the region block argument
6572 llvm::Type *llvmXElementType =
6573 moduleTranslation.convertType(block.getArgument(0).getType());
6574 if (!llvmXElementType)
6575 return atomicCompareOp.emitError(
6576 "unable to determine element type for atomic compare");
6577
6578 llvm::Value *llvmX = moduleTranslation.lookupValue(atomicCompareOp.getX());
6579
6580 // IsSigned is determined from the comparison predicate in the region.
6581 // Signed ICmp predicates (slt/sgt) set this to true; unsigned (ult/ugt)
6582 // leave it false. For EQ and float comparisons, signedness is irrelevant.
6583 bool isSigned = false;
6584 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {llvmX, llvmXElementType,
6585 isSigned,
6586 /*IsVolatile=*/false};
6587
6588 llvm::AtomicOrdering atomicOrdering =
6589 convertAtomicOrdering(atomicCompareOp.getMemoryOrder());
6590
6591 auto isAtomicComparePatternOp = [](Operation &op) {
6592 return llvm::isa<LLVM::ICmpOp, LLVM::FCmpOp, LLVM::SelectOp, LLVM::AndOp,
6593 LLVM::OrOp, LLVM::SMaxOp, LLVM::SMinOp, LLVM::UMaxOp,
6594 LLVM::UMinOp, LLVM::MaxNumOp, LLVM::MinNumOp,
6595 mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
6596 mlir::arith::MaxUIOp, mlir::arith::MinUIOp,
6597 mlir::arith::MaximumFOp, mlir::arith::MinimumFOp>(op);
6598 };
6599
6600 // Pre-translate operations inside the region that compute e and d (e.g.,
6601 // GEP, loads for dereferencing Fortran pointers) but are not part of the
6602 // atomic compare-and-swap pattern (icmp/fcmp, select, and/or).
6603 //
6604 // 1) Validity: The OpenMP spec requires e and d to be evaluated before the
6605 // atomic operation, so emitting their computation here is correct.
6606 // 2) Memory effects: These ops only depend on values defined outside the
6607 // region. They cannot observe the block argument (%xval), which is the
6608 // value loaded atomically by cmpxchg and does not exist yet.
6609 // 3) Invariant enforcement: The `allOperandsMapped` check below skips any
6610 // op whose operands include the unmapped block argument, guaranteeing
6611 // only region-external-dependent ops are pre-translated.
6612 for (Operation &op : block.without_terminator()) {
6613 // Skip operations that form the atomic compare pattern — these are
6614 // not emitted as individual instructions but are analyzed below to
6615 // extract the comparison predicate, expected value (e), and desired
6616 // value (d) for generating a single cmpxchg/atomicrmw.
6617 if (isAtomicComparePatternOp(op))
6618 continue;
6619
6620 // Avoid translating ops that depend on the unmapped block argument.
6621 bool allOperandsMapped = llvm::all_of(op.getOperands(), [&](mlir::Value v) {
6622 return moduleTranslation.lookupValue(v) != nullptr;
6623 });
6624 if (!allOperandsMapped)
6625 continue;
6626
6627 if (failed(moduleTranslation.convertOperation(op, builder)))
6628 return atomicCompareOp.emitError(
6629 "failed to translate operation inside atomic compare region");
6630 }
6631
6632 // Look up a value that may have been pre-translated or defined outside the
6633 // region.
6634 auto materializeValue = [&](mlir::Value val) -> llvm::Value * {
6635 // Check if the value is already mapped (pre-translated or defined outside).
6636 if (llvm::Value *existing = moduleTranslation.lookupValue(val))
6637 return existing;
6638 // Fallback for a single LoadOp whose address is mapped but whose result
6639 // was not pre-translated.
6640 if (auto loadOp = val.getDefiningOp<LLVM::LoadOp>()) {
6641 if (loadOp->getParentRegion() == &region) {
6642 llvm::Value *loadAddr = moduleTranslation.lookupValue(loadOp.getAddr());
6643 if (!loadAddr)
6644 return nullptr;
6645 llvm::Type *loadType =
6646 moduleTranslation.convertType(loadOp.getResult().getType());
6647 return builder.CreateLoad(loadType, loadAddr);
6648 }
6649 }
6650 return nullptr;
6651 };
6652
6653 // Walk the region to extract comparison predicate, eVal, and dVal.
6654 // if (x == eVal) x = dVal
6655 llvm::omp::OMPAtomicCompareOp compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
6656 llvm::Value *eVal = nullptr;
6657 llvm::Value *dVal = nullptr;
6658 bool isXBinopExpr = false;
6659
6660 // Check for a decomposed complex comparison pattern (extractvalue + fcmp +
6661 // and/or of the real/imaginary fields).
6663 bool isComplexPattern = cplx.isComplex;
6664 if (isComplexPattern) {
6665 if (cplx.isNE)
6666 // OrOp corresponds to NE, which is not a valid atomic compare op.
6667 return atomicCompareOp.emitError(
6668 "unsupported comparison predicate (NE) for complex atomic compare");
6669 compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
6670 isXBinopExpr = cplx.isXBinopExpr;
6671 eVal = materializeValue(cplx.eAggregate);
6672 }
6673
6674 if (isComplexPattern) {
6675 // dVal from SelectOp or YieldOp.
6676 for (Operation &op : block.getOperations()) {
6677 if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
6678 dVal = materializeValue(selectOp.getTrueValue());
6679 break;
6680 }
6681 }
6682 if (!dVal) {
6683 auto yieldOp = cast<omp::YieldOp>(block.getTerminator());
6684 if (yieldOp.getResults().empty())
6685 return atomicCompareOp.emitError(
6686 "failed to extract desired value (d) from atomic compare region");
6687 dVal = materializeValue(yieldOp.getResults()[0]);
6688 }
6689
6690 llvm::Value *oldComplex = nullptr;
6691 llvm::Value *cmpOk = nullptr;
6692 llvm::AtomicOrdering failOrdering =
6693 getAtomicCompareFailureOrdering(atomicCompareOp, atomicOrdering);
6694 emitComplexAtomicCmpXchg(builder, llvmX, llvmXElementType, eVal, dVal,
6695 atomicOrdering, failOrdering,
6696 atomicCompareOp.getWeak(), oldComplex, cmpOk);
6697 (void)oldComplex;
6698 (void)cmpOk;
6699
6700 // Emit flush after atomic compare if needed (for release, acq_rel,
6701 // seq_cst orderings).
6702 if (atomicOrdering == llvm::AtomicOrdering::Release ||
6703 atomicOrdering == llvm::AtomicOrdering::AcquireRelease ||
6704 atomicOrdering == llvm::AtomicOrdering::SequentiallyConsistent) {
6705 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6706 ompBuilder->createFlush(ompLoc);
6707 }
6708 return success();
6709 } else {
6710 AtomicComparePatternInfo patternInfo;
6711 if (failed(extractAtomicComparePattern(block, materializeValue,
6712 atomicCompareOp, patternInfo)))
6713 return failure();
6714 compareOp = patternInfo.compareOp;
6715 eVal = patternInfo.eVal;
6716 dVal = patternInfo.dVal;
6717 isXBinopExpr = patternInfo.isXBinopExpr;
6718 isSigned = patternInfo.isSigned;
6719 }
6720
6721 if (!eVal)
6722 return atomicCompareOp.emitError(
6723 "failed to extract expected value (e) from atomic compare region");
6724 if (!dVal) {
6725 // Fall back to the yield operand.
6726 auto yieldOp = cast<omp::YieldOp>(block.getTerminator());
6727 if (yieldOp.getResults().empty())
6728 return atomicCompareOp.emitError(
6729 "failed to extract desired value (d) from atomic compare region");
6730 dVal = materializeValue(yieldOp.getResults()[0]);
6731 }
6732
6733 llvmAtomicX.IsSigned = isSigned;
6734
6735 llvm::OpenMPIRBuilder::AtomicOpValue vOpVal = {nullptr, nullptr, false,
6736 false};
6737 llvm::OpenMPIRBuilder::AtomicOpValue rOpVal = {nullptr, nullptr, false,
6738 false};
6739 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6740
6741 bool isWeak = atomicCompareOp.getWeak();
6742
6743 bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
6744 llvm::AtomicOrdering failureOrdering =
6745 getAtomicCompareFailureOrdering(atomicCompareOp, atomicOrdering);
6746 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
6747 ompBuilder->createAtomicCompare(
6748 ompLoc, llvmAtomicX, vOpVal, rOpVal, eVal, dVal, atomicOrdering,
6749 compareOp, isXBinopExpr, /*IsPostfixUpdate=*/false,
6750 /*IsFailOnly=*/false, failureOrdering, isWeak);
6751 ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
6752
6753 if (failed(handleError(afterIP, *atomicCompareOp)))
6754 return failure();
6755
6756 builder.restoreIP(*afterIP);
6757 return success();
6758}
6759
6760static llvm::omp::Directive convertCancellationConstructType(
6761 omp::ClauseCancellationConstructType directive) {
6762 switch (directive) {
6763 case omp::ClauseCancellationConstructType::Loop:
6764 return llvm::omp::Directive::OMPD_for;
6765 case omp::ClauseCancellationConstructType::Parallel:
6766 return llvm::omp::Directive::OMPD_parallel;
6767 case omp::ClauseCancellationConstructType::Sections:
6768 return llvm::omp::Directive::OMPD_sections;
6769 case omp::ClauseCancellationConstructType::Taskgroup:
6770 return llvm::omp::Directive::OMPD_taskgroup;
6771 }
6772 llvm_unreachable("Unhandled cancellation construct type");
6773}
6774
6775static LogicalResult
6776convertOmpCancel(omp::CancelOp op, llvm::IRBuilderBase &builder,
6777 LLVM::ModuleTranslation &moduleTranslation) {
6778 if (failed(checkImplementationStatus(*op.getOperation())))
6779 return failure();
6780
6781 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6782 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
6783
6784 llvm::Value *ifCond = nullptr;
6785 if (Value ifVar = op.getIfExpr())
6786 ifCond = moduleTranslation.lookupValue(ifVar);
6787
6788 llvm::omp::Directive cancelledDirective =
6789 convertCancellationConstructType(op.getCancelDirective());
6790
6791 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
6792 ompBuilder->createCancel(ompLoc, ifCond, cancelledDirective);
6793
6794 if (failed(handleError(afterIP, *op.getOperation())))
6795 return failure();
6796
6797 builder.restoreIP(afterIP.get());
6798
6799 return success();
6800}
6801
6802static LogicalResult
6803convertOmpCancellationPoint(omp::CancellationPointOp op,
6804 llvm::IRBuilderBase &builder,
6805 LLVM::ModuleTranslation &moduleTranslation) {
6806 if (failed(checkImplementationStatus(*op.getOperation())))
6807 return failure();
6808
6809 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6810 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
6811
6812 llvm::omp::Directive cancelledDirective =
6813 convertCancellationConstructType(op.getCancelDirective());
6814
6815 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
6816 ompBuilder->createCancellationPoint(ompLoc, cancelledDirective);
6817
6818 if (failed(handleError(afterIP, *op.getOperation())))
6819 return failure();
6820
6821 builder.restoreIP(afterIP.get());
6822
6823 return success();
6824}
6825
6826/// Converts an OpenMP Threadprivate operation into LLVM IR using
6827/// OpenMPIRBuilder.
6828static LogicalResult
6829convertOmpThreadprivate(Operation &opInst, llvm::IRBuilderBase &builder,
6830 LLVM::ModuleTranslation &moduleTranslation) {
6831 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6832 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
6833 auto threadprivateOp = cast<omp::ThreadprivateOp>(opInst);
6834
6835 if (failed(checkImplementationStatus(opInst)))
6836 return failure();
6837
6838 Value symAddr = threadprivateOp.getSymAddr();
6839 auto *symOp = symAddr.getDefiningOp();
6840
6841 if (auto asCast = dyn_cast<LLVM::AddrSpaceCastOp>(symOp))
6842 symOp = asCast.getOperand().getDefiningOp();
6843
6844 if (!isa<LLVM::AddressOfOp>(symOp))
6845 return opInst.emitError("Addressing symbol not found");
6846 LLVM::AddressOfOp addressOfOp = dyn_cast<LLVM::AddressOfOp>(symOp);
6847
6848 LLVM::GlobalOp global =
6849 addressOfOp.getGlobal(moduleTranslation.symbolTable());
6850 llvm::GlobalValue *globalValue = moduleTranslation.lookupGlobal(global);
6851 llvm::Type *type = globalValue->getValueType();
6852 llvm::TypeSize typeSize =
6853 builder.GetInsertBlock()->getModule()->getDataLayout().getTypeStoreSize(
6854 type);
6855 llvm::ConstantInt *size = builder.getInt64(typeSize.getFixedValue());
6856 llvm::Value *callInst = ompBuilder->createCachedThreadPrivate(
6857 ompLoc, globalValue, size, global.getSymName() + ".cache");
6858 moduleTranslation.mapValue(opInst.getResult(0), callInst);
6859
6860 return success();
6861}
6862
6863static llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseKind
6864convertToDeviceClauseKind(mlir::omp::DeclareTargetDeviceType deviceClause) {
6865 switch (deviceClause) {
6866 case mlir::omp::DeclareTargetDeviceType::host:
6867 return llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseHost;
6868 break;
6869 case mlir::omp::DeclareTargetDeviceType::nohost:
6870 return llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseNoHost;
6871 break;
6872 case mlir::omp::DeclareTargetDeviceType::any:
6873 return llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseAny;
6874 break;
6875 }
6876 llvm_unreachable("unhandled device clause");
6877}
6878
6879static llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryKind
6881 mlir::omp::DeclareTargetCaptureClause captureClause) {
6882 switch (captureClause) {
6883 case mlir::omp::DeclareTargetCaptureClause::to:
6884 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryTo;
6885 case mlir::omp::DeclareTargetCaptureClause::link:
6886 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryLink;
6887 case mlir::omp::DeclareTargetCaptureClause::enter:
6888 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryEnter;
6889 case mlir::omp::DeclareTargetCaptureClause::none:
6890 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryNone;
6891 }
6892 llvm_unreachable("unhandled capture clause");
6893}
6894
6896 Operation *op = value.getDefiningOp();
6897 if (auto addrCast = dyn_cast_if_present<LLVM::AddrSpaceCastOp>(op))
6898 op = addrCast->getOperand(0).getDefiningOp();
6899 if (auto addressOfOp = dyn_cast_if_present<LLVM::AddressOfOp>(op)) {
6900 auto modOp = addressOfOp->getParentOfType<mlir::ModuleOp>();
6901 return modOp.lookupSymbol(addressOfOp.getGlobalName());
6902 }
6903 return nullptr;
6904}
6905
6907 while (Operation *op = value.getDefiningOp()) {
6908 if (auto addrCast = dyn_cast_if_present<LLVM::AddrSpaceCastOp>(op))
6909 value = addrCast.getOperand();
6910 // Traces through hlfir.declare, fir.declare to reach the base address and
6911 // use for type lookup.
6912 else if (op->getName().getIdentifier() &&
6913 (op->getName().getIdentifier().str() == "hlfir.declare" ||
6914 op->getName().getIdentifier().str() == "fir.declare")) {
6915 if (op->getNumOperands() > 0)
6916 value = op->getOperand(0);
6917 else
6918 break;
6919 } else {
6920 break;
6921 }
6922 }
6923 return value;
6924}
6925
6926// Determine the LLVM type whose storage size should be allocated for an
6927// OpenMP allocate directive list item. Opaque pointers lose element type, so
6928// trace through declare wrappers to the underlying global or stack allocation.
6929static llvm::Type *
6931 LLVM::ModuleTranslation &moduleTranslation) {
6932 llvm::Type *llvmVarTy = moduleTranslation.convertType(var.getType());
6933 if (!llvmVarTy->isPointerTy())
6934 return llvmVarTy;
6935
6936 if (Operation *globalOp = getGlobalOpFromValue(baseVar))
6937 if (auto gop = dyn_cast<LLVM::GlobalOp>(globalOp))
6938 return moduleTranslation.convertType(gop.getGlobalType());
6939
6940 if (auto allocaOp =
6941 dyn_cast_if_present<LLVM::AllocaOp>(baseVar.getDefiningOp()))
6942 return moduleTranslation.convertType(allocaOp.getElemType());
6943
6944 if (llvm::Value *baseLlvm = moduleTranslation.lookupValue(baseVar))
6945 if (auto *allocaInst = dyn_cast<llvm::AllocaInst>(baseLlvm))
6946 return allocaInst->getAllocatedType();
6947
6948 return llvmVarTy;
6949}
6950
6951// For dynamically-sized stack allocations, compute the allocation size from
6952// the alloca's element count at runtime.
6953static std::optional<llvm::Value *> getDynamicAllocatedSize(
6954 Value var, Value baseVar, LLVM::ModuleTranslation &moduleTranslation,
6955 llvm::IRBuilderBase &builder, const llvm::DataLayout &dataLayout) {
6956 if (auto allocaOp =
6957 dyn_cast_if_present<LLVM::AllocaOp>(baseVar.getDefiningOp())) {
6958 if (Value arraySize = allocaOp.getArraySize()) {
6959 llvm::Type *elemTy =
6960 moduleTranslation.convertType(allocaOp.getElemType());
6961 llvm::Value *numElems = moduleTranslation.lookupValue(arraySize);
6962 if (!numElems->getType()->isIntegerTy(64))
6963 numElems = builder.CreateZExt(numElems, builder.getInt64Ty());
6964 uint64_t elemSize = dataLayout.getTypeAllocSize(elemTy).getFixedValue();
6965 return builder.CreateMul(numElems, builder.getInt64(elemSize));
6966 }
6967 }
6968 if (llvm::Value *baseLlvm = moduleTranslation.lookupValue(baseVar)) {
6969 if (auto *allocaInst = dyn_cast<llvm::AllocaInst>(baseLlvm)) {
6970 if (allocaInst->isArrayAllocation() &&
6971 !llvm::isa<llvm::ArrayType>(allocaInst->getAllocatedType())) {
6972 uint64_t elemSize =
6973 dataLayout.getTypeAllocSize(allocaInst->getAllocatedType())
6974 .getFixedValue();
6975 return builder.CreateMul(allocaInst->getArraySize(),
6976 builder.getInt64(elemSize));
6977 }
6978 }
6979 }
6980 return std::nullopt;
6981}
6982
6983static llvm::SmallString<64>
6984getDeclareTargetRefPtrSuffix(LLVM::GlobalOp globalOp,
6985 llvm::OpenMPIRBuilder &ompBuilder,
6986 llvm::vfs::FileSystem &vfs) {
6987 llvm::SmallString<64> suffix;
6988 llvm::raw_svector_ostream os(suffix);
6989 if (globalOp.getVisibility() == mlir::SymbolTable::Visibility::Private) {
6990 auto loc = globalOp->getLoc()->findInstanceOf<FileLineColLoc>();
6991 auto fileInfoCallBack = [&loc]() {
6992 return std::pair<std::string, uint64_t>(
6993 llvm::StringRef(loc.getFilename()), loc.getLine());
6994 };
6995
6996 os << llvm::format(
6997 "_%x",
6998 ompBuilder.getTargetEntryUniqueInfo(fileInfoCallBack, vfs).FileID);
6999 }
7000 os << "_decl_tgt_ref_ptr";
7001
7002 return suffix;
7003}
7004
7005static bool isDeclareTargetLink(Value value) {
7006 if (auto declareTargetGlobal =
7007 dyn_cast_if_present<omp::DeclareTargetInterface>(
7008 getGlobalOpFromValue(value)))
7009 if (declareTargetGlobal.getDeclareTargetCaptureClause() ==
7010 omp::DeclareTargetCaptureClause::link)
7011 return true;
7012 return false;
7013}
7014
7015static bool isDeclareTargetTo(Value value) {
7016 if (auto declareTargetGlobal =
7017 dyn_cast_if_present<omp::DeclareTargetInterface>(
7018 getGlobalOpFromValue(value)))
7019 if (declareTargetGlobal.getDeclareTargetCaptureClause() ==
7020 omp::DeclareTargetCaptureClause::to ||
7021 declareTargetGlobal.getDeclareTargetCaptureClause() ==
7022 omp::DeclareTargetCaptureClause::enter)
7023 return true;
7024 return false;
7025}
7026
7027// Returns the reference pointer generated by the lowering of the declare
7028// target operation in cases where the link clause is used or the to clause is
7029// used in USM mode.
7030static llvm::Value *
7032 LLVM::ModuleTranslation &moduleTranslation) {
7033 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
7034 if (auto gOp =
7035 dyn_cast_or_null<LLVM::GlobalOp>(getGlobalOpFromValue(value))) {
7036 // In this case, we must utilise the reference pointer generated by
7037 // the declare target operation, similar to Clang
7038 if (isDeclareTargetLink(value) ||
7039 (isDeclareTargetTo(value) &&
7040 ompBuilder->Config.hasRequiresUnifiedSharedMemory())) {
7042 gOp, *ompBuilder, moduleTranslation.getFileSystem());
7043
7044 if (gOp.getSymName().contains(suffix))
7045 return moduleTranslation.getLLVMModule()->getNamedValue(
7046 gOp.getSymName());
7047
7048 return moduleTranslation.getLLVMModule()->getNamedValue(
7049 (gOp.getSymName().str() + suffix.str()).str());
7050 }
7051 }
7052 return nullptr;
7053}
7054
7055namespace {
7056// Append customMappers information to existing MapInfosTy
7057struct MapInfosTy : llvm::OpenMPIRBuilder::MapInfosTy {
7058 SmallVector<Operation *, 4> Mappers;
7059
7060 /// Append arrays in \a CurInfo.
7061 void append(MapInfosTy &curInfo) {
7062 Mappers.append(curInfo.Mappers.begin(), curInfo.Mappers.end());
7063 llvm::OpenMPIRBuilder::MapInfosTy::append(curInfo);
7064 }
7065};
7066// A small helper structure to contain data gathered
7067// for map lowering and coalese it into one area and
7068// avoiding extra computations such as searches in the
7069// llvm module for lowered mapped variables or checking
7070// if something is declare target (and retrieving the
7071// value) more than neccessary.
7072struct MapInfoData : MapInfosTy {
7073 llvm::SmallVector<bool, 4> IsDeclareTarget;
7074 llvm::SmallVector<bool, 4> IsAMember;
7075 // Identify if mapping was added by mapClause or use_device clauses.
7076 llvm::SmallVector<bool, 4> IsAMapping;
7077 llvm::SmallVector<mlir::Operation *, 4> MapClause;
7078 llvm::SmallVector<llvm::Value *, 4> OriginalValue;
7079 // Stripped off array/pointer to get the underlying
7080 // element type
7081 llvm::SmallVector<llvm::Type *, 4> BaseType;
7082
7083 /// Append arrays in \a CurInfo.
7084 void append(MapInfoData &CurInfo) {
7085 IsDeclareTarget.append(CurInfo.IsDeclareTarget.begin(),
7086 CurInfo.IsDeclareTarget.end());
7087 MapClause.append(CurInfo.MapClause.begin(), CurInfo.MapClause.end());
7088 OriginalValue.append(CurInfo.OriginalValue.begin(),
7089 CurInfo.OriginalValue.end());
7090 BaseType.append(CurInfo.BaseType.begin(), CurInfo.BaseType.end());
7091 MapInfosTy::append(CurInfo);
7092 }
7093};
7094
7095enum class TargetDirectiveEnumTy : uint32_t {
7096 None = 0,
7097 Target = 1,
7098 TargetData = 2,
7099 TargetEnterData = 3,
7100 TargetExitData = 4,
7101 TargetUpdate = 5
7102};
7103
7104static TargetDirectiveEnumTy getTargetDirectiveEnumTyFromOp(Operation *op) {
7105 return llvm::TypeSwitch<Operation *, TargetDirectiveEnumTy>(op)
7106 .Case([](omp::TargetDataOp) { return TargetDirectiveEnumTy::TargetData; })
7107 .Case([](omp::TargetEnterDataOp) {
7108 return TargetDirectiveEnumTy::TargetEnterData;
7109 })
7110 .Case([&](omp::TargetExitDataOp) {
7111 return TargetDirectiveEnumTy::TargetExitData;
7112 })
7113 .Case([&](omp::TargetUpdateOp) {
7114 return TargetDirectiveEnumTy::TargetUpdate;
7115 })
7116 .Case([&](omp::TargetOp) { return TargetDirectiveEnumTy::Target; })
7117 .Default([&](Operation *op) { return TargetDirectiveEnumTy::None; });
7118}
7119
7120} // namespace
7121
7122static uint64_t getArrayElementSizeInBits(LLVM::LLVMArrayType arrTy,
7123 DataLayout &dl) {
7124 if (auto nestedArrTy = llvm::dyn_cast_if_present<LLVM::LLVMArrayType>(
7125 arrTy.getElementType()))
7126 return getArrayElementSizeInBits(nestedArrTy, dl);
7127 return dl.getTypeSizeInBits(arrTy.getElementType());
7128}
7129
7130// The intent is to verify if the mapped data being passed is a
7131// pointer -> pointee that requires special handling in certain cases,
7132// e.g. applying the OMP_MAP_PTR_AND_OBJ map type.
7133//
7134// There may be a better way to verify this, but unfortunately with
7135// opaque pointers we lose the ability to easily check if something is
7136// a pointer whilst maintaining access to the underlying type.
7137static bool checkIfPointerMap(omp::MapInfoOp mapOp) {
7138 // If we have a varPtrPtr field assigned then the underlying type is a pointer
7139 if (mapOp.getVarPtrPtr())
7140 return true;
7141
7142 // If the map data is declare target with a link clause, then it's represented
7143 // as a pointer when we lower it to LLVM-IR even if at the MLIR level it has
7144 // no relation to pointers.
7145 if (isDeclareTargetLink(mapOp.getVarPtr()))
7146 return true;
7147
7148 return false;
7149}
7150
7151// A privatizeable attach map is a pointer/descriptor that is privatized and
7152// passed directly as a kernel argument (target_param) rather than undergoing
7153// the standard attach/parent mapping. These are handled specially in a couple
7154// of places in map lowering.
7155static bool isPrivatizeableAttachMap(omp::ClauseMapFlags mapType) {
7156 return bitEnumContainsAll(mapType, omp::ClauseMapFlags::priv |
7157 omp::ClauseMapFlags::target_param |
7158 omp::ClauseMapFlags::attach);
7159}
7160
7161// This function calculates the size to be offloaded for a specified type, given
7162// its associated map clause (which can contain bounds information which affects
7163// the total size), this size is calculated based on the underlying element type
7164// e.g. given a 1-D array of ints, we will calculate the size from the integer
7165// type * number of elements in the array. This size can be used in other
7166// calculations but is ultimately used as an argument to the OpenMP runtimes
7167// kernel argument structure which is generated through the combinedInfo data
7168// structures.
7169// This function is somewhat equivalent to Clang's getExprTypeSize inside of
7170// CGOpenMPRuntime.cpp.
7171static llvm::Value *getSizeInBytes(DataLayout &dl, const mlir::Type &type,
7172 Operation *clauseOp,
7173 llvm::Value *basePointer,
7174 llvm::Type *baseType,
7175 llvm::IRBuilderBase &builder,
7176 LLVM::ModuleTranslation &moduleTranslation) {
7177 if (auto memberClause =
7178 mlir::dyn_cast_if_present<mlir::omp::MapInfoOp>(clauseOp)) {
7179 // This calculates the size to transfer based on bounds and the underlying
7180 // element type, provided bounds have been specified (Fortran
7181 // pointers/allocatables/target and arrays that have sections specified fall
7182 // into this as well)
7183 if (!memberClause.getBounds().empty()) {
7184 llvm::Value *elementCount = builder.getInt64(1);
7185 for (auto bounds : memberClause.getBounds()) {
7186 if (auto boundOp = mlir::dyn_cast_if_present<mlir::omp::MapBoundsOp>(
7187 bounds.getDefiningOp())) {
7188 // The below calculation for the size to be mapped calculated from the
7189 // map.info's bounds is: (elemCount * [UB - LB] + 1), later we
7190 // multiply by the underlying element types byte size to get the full
7191 // size to be offloaded based on the bounds
7192 elementCount = builder.CreateMul(
7193 elementCount,
7194 builder.CreateAdd(
7195 builder.CreateSub(
7196 moduleTranslation.lookupValue(boundOp.getUpperBound()),
7197 moduleTranslation.lookupValue(boundOp.getLowerBound())),
7198 builder.getInt64(1)));
7199 }
7200 }
7201
7202 // utilising getTypeSizeInBits instead of getTypeSize as getTypeSize gives
7203 // the size in inconsistent byte or bit format.
7204 uint64_t underlyingTypeSzInBits = dl.getTypeSizeInBits(type);
7205 if (auto arrTy = llvm::dyn_cast_if_present<LLVM::LLVMArrayType>(type))
7206 underlyingTypeSzInBits = getArrayElementSizeInBits(arrTy, dl);
7207
7208 // The size in bytes x number of elements, the sizeInBytes stored is
7209 // the underyling types size, e.g. if ptr<i32>, it'll be the i32's
7210 // size, so we do some on the fly runtime math to get the size in
7211 // bytes from the extent (ub - lb) * sizeInBytes. NOTE: This may need
7212 // some adjustment for members with more complex types.
7213 llvm::Value *sizeCalc = builder.CreateMul(
7214 elementCount, builder.getInt64(underlyingTypeSzInBits / 8),
7215 "element_count");
7216
7217 // This is a part of a "complicated" bit of size calculation logic that is
7218 // in place to handle a couple of scenarios, one specific to Fortran and
7219 // the other a more general OpenMP issue. The other piece of the
7220 // calculation can be found as the final size calculation within the
7221 // processIndividualMap function. Ideally we would move it here, but due
7222 // to the complexity of calculating the final base address of some
7223 // constructs (required for a nullary check), it's left as the final step.
7224 // So, in the below 2 cases, the nullary check is in processIndividualMap
7225 // and the size equality check is here. The cases this modifications help
7226 // cover are:
7227 //
7228 // 1) If an argument has a null base pointer, then the size must be set to
7229 // 0 to avoid the runtime exploding/complaining about an illegal
7230 // pointer map. The size returning non-zero is feasible in certain
7231 // cases if for example someone has specified there own bounds/range.
7232 // 2) We wish to support a very specific OpenMP Fortran edge-case where a
7233 // size zero array can be legally presence checked and found to be on
7234 // device when it has been mapped. In these rare occasions the
7235 // allocatable/pointer will have a size of 1 allocated for the
7236 // underlying data, but this wall not be represented within the size of
7237 // the descriptor, so we get a non-nullary pointer and a size of 0,
7238 // allowing us to specify a size of 1 in these cases registering it on
7239 // the device mapping table as present.
7240 //
7241 // The default fall through case is just returning the size calculation
7242 // above, if we are not nullary and the size we calculate is non-zero,
7243 // which is basically any pointer type that is allocated in someway
7244 // (providing you are not running on a rare system that allows malloc's of
7245 // size 0 with whatever caveats that may come with).
7246 //
7247 // Later in the nullary check in processIndividualMap it just devolves to
7248 // selecting a size of 0 if we are nullary, if we are not, we will return
7249 // either 1 or the calculated size, depending on the outcome of this
7250 // select.
7251 if (checkIfPointerMap(memberClause)) {
7252 return builder.CreateSelect(
7253 builder.CreateICmpEQ(sizeCalc, builder.getInt64(0)),
7254 builder.getInt64(1), sizeCalc);
7255 }
7256
7257 return sizeCalc;
7258 }
7259 }
7260
7261 return builder.getInt64(dl.getTypeSizeInBits(type) / 8);
7262}
7263
7264// Convert the MLIR map flag set to the runtime map flag set for embedding
7265// in LLVM-IR. This is important as the two bit-flag lists do not correspond
7266// 1-to-1 as there's flags the runtime doesn't care about and vice versa.
7267// Certain flags are discarded here such as RefPtee and co.
7268static llvm::omp::OpenMPOffloadMappingFlags
7269convertClauseMapFlags(omp::ClauseMapFlags mlirFlags) {
7270 const bool hasExplicitMap =
7271 (mlirFlags & ~omp::ClauseMapFlags::is_device_ptr) !=
7272 omp::ClauseMapFlags::none;
7273
7274 llvm::omp::OpenMPOffloadMappingFlags mapType =
7275 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE;
7276
7277 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::to))
7278 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TO;
7279
7280 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::from))
7281 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_FROM;
7282
7283 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::always))
7284 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ALWAYS;
7285
7286 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::del))
7287 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_DELETE;
7288
7289 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::return_param))
7290 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
7291
7292 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::priv))
7293 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_PRIVATE;
7294
7295 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::literal))
7296 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL;
7297
7298 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::implicit))
7299 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_IMPLICIT;
7300
7301 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::close))
7302 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_CLOSE;
7303
7304 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::present))
7305 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_PRESENT;
7306
7307 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::ompx_hold))
7308 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_OMPX_HOLD;
7309
7310 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::attach))
7311 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH;
7312
7313 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::target_param))
7314 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TARGET_PARAM;
7315
7316 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::is_device_ptr)) {
7317 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TARGET_PARAM;
7318 if (!hasExplicitMap)
7319 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL;
7320 }
7321
7322 return mapType;
7323}
7324
7326 MapInfoData &mapData, SmallVectorImpl<Value> &mapVars,
7327 LLVM::ModuleTranslation &moduleTranslation, DataLayout &dl,
7328 llvm::IRBuilderBase &builder, ArrayRef<Value> useDevPtrOperands = {},
7329 ArrayRef<Value> useDevAddrOperands = {},
7330 ArrayRef<Value> hasDevAddrOperands = {}) {
7331
7332 auto checkRefPtrOrPteeMapWithAttach = [](omp::ClauseMapFlags mapType) {
7333 bool hasRefType =
7334 bitEnumContainsAll(mapType, omp::ClauseMapFlags::ref_ptr) ||
7335 bitEnumContainsAll(mapType, omp::ClauseMapFlags::ref_ptee);
7336 return hasRefType &&
7337 bitEnumContainsAll(mapType, omp::ClauseMapFlags::attach);
7338 };
7339
7340 auto checkIsAMember = [](const auto &mapVars, auto mapOp) {
7341 // Check if this is a member mapping and correctly assign that it is, if
7342 // it is a member of a larger object.
7343 // TODO: Need better handling of members, and distinguishing of members
7344 // that are implicitly allocated on device vs explicitly passed in as
7345 // arguments.
7346 // TODO: May require some further additions to support nested record
7347 // types, i.e. member maps that can have member maps.
7348 for (Value mapValue : mapVars) {
7349 auto map = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
7350 for (auto member : map.getMembers())
7351 if (member == mapOp)
7352 return true;
7353 }
7354 return false;
7355 };
7356
7357 // Process MapOperands
7358 for (Value mapValue : mapVars) {
7359 auto mapOp = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
7360 bool isAttachStyleMap =
7361 checkRefPtrOrPteeMapWithAttach(mapOp.getMapType()) ||
7362 isPrivatizeableAttachMap(mapOp.getMapType());
7363 Value offloadPtr = (mapOp.getVarPtrPtr() && !isAttachStyleMap)
7364 ? mapOp.getVarPtrPtr()
7365 : mapOp.getVarPtr();
7366 mapData.OriginalValue.push_back(moduleTranslation.lookupValue(offloadPtr));
7367 mapData.Pointers.push_back(
7368 isAttachStyleMap ? moduleTranslation.lookupValue(mapOp.getVarPtrPtr())
7369 : mapData.OriginalValue.back());
7370
7371 if (llvm::Value *refPtr =
7372 getRefPtrIfDeclareTarget(offloadPtr, moduleTranslation)) {
7373 mapData.IsDeclareTarget.push_back(true);
7374 mapData.BasePointers.push_back(refPtr);
7375 } else if (isDeclareTargetTo(offloadPtr)) {
7376 mapData.IsDeclareTarget.push_back(true);
7377 mapData.BasePointers.push_back(mapData.OriginalValue.back());
7378 } else { // regular mapped variable
7379 mapData.IsDeclareTarget.push_back(false);
7380 mapData.BasePointers.push_back(mapData.OriginalValue.back());
7381 }
7382
7383 // In every situation we currently have if we have a varPtrPtr present
7384 // we wish to utilise it's type for the base type, main cases are
7385 // currently Fortran descriptor base address maps and attach maps.
7386 mapData.BaseType.push_back(moduleTranslation.convertType(
7387 mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtrType().value()
7388 : mapOp.getVarPtrType()));
7389
7390 // For the attach map cases, it's a little odd, as we effectively have to
7391 // utilise the base address (including all bounds offsets) for the pointer
7392 // field, the pointer address for the base address field, and the pointer
7393 // not the data (base addresses) size. So we end up with a mix of base
7394 // types and sizes we wish to insert here.
7395 mlir::Type sizeType = (isAttachStyleMap || !mapOp.getVarPtrPtr())
7396 ? mapOp.getVarPtrType()
7397 : mapOp.getVarPtrPtrType().value();
7398 mapData.Sizes.push_back(getSizeInBytes(
7399 dl, sizeType, isAttachStyleMap ? nullptr : mapOp,
7400 mapData.Pointers.back(), moduleTranslation.convertType(sizeType),
7401 builder, moduleTranslation));
7402 mapData.MapClause.push_back(mapOp.getOperation());
7403 mapData.Types.push_back(convertClauseMapFlags(mapOp.getMapType()));
7404 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7405 mapData.HasAttachPtr.push_back(false);
7406 mapData.Names.push_back(LLVM::createMappingInformation(
7407 mapOp.getLoc(), *moduleTranslation.getOpenMPBuilder()));
7408 mapData.DevicePointers.push_back(llvm::OpenMPIRBuilder::DeviceInfoTy::None);
7409 if (mapOp.getMapperId())
7410 mapData.Mappers.push_back(
7412 mapOp, mapOp.getMapperIdAttr()));
7413 else
7414 mapData.Mappers.push_back(nullptr);
7415 mapData.IsAMapping.push_back(true);
7416 mapData.IsAMember.push_back(checkIsAMember(mapVars, mapOp));
7417 }
7418
7419 auto findMapInfo = [&mapData](llvm::Value *val,
7420 llvm::OpenMPIRBuilder::DeviceInfoTy devInfoTy,
7421 size_t memberCount) {
7422 unsigned index = 0;
7423 bool found = false;
7424 for (llvm::Value *basePtr : mapData.OriginalValue) {
7425 auto mapOp = cast<omp::MapInfoOp>(mapData.MapClause[index]);
7426 // TODO: Currently we define an equivalent mapping as
7427 // the same base pointer and an equivalent member count, but
7428 // that is a loose definition. We may have to extend to check
7429 // for other fields (varPtrPtr/individual members being mapped).
7430 // Note: Attach maps are not the same as a normal data transfer
7431 // they specify to the runtime to perform an attach map and they
7432 // (at least at the moment) are never something we would aim to
7433 // return in a use_dev_* clause, so they are skipped in terms of
7434 // duplicate maps.
7435 bool isAttachMap =
7436 (mapData.Types[index] &
7437 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) ==
7438 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH;
7439 if (!isAttachMap && basePtr == val && mapData.IsAMapping[index] &&
7440 memberCount == mapOp.getMembers().size()) {
7441 found = true;
7442 mapData.Types[index] |=
7443 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
7444 mapData.DevicePointers[index] = devInfoTy;
7445 }
7446 index++;
7447 }
7448 return found;
7449 };
7450
7451 // Process useDevPtr(Addr)Operands
7452 auto addDevInfos = [&](const llvm::ArrayRef<Value> &useDevOperands,
7453 llvm::OpenMPIRBuilder::DeviceInfoTy devInfoTy) {
7454 for (Value mapValue : useDevOperands) {
7455 auto mapOp = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
7456 Value offloadPtr =
7457 mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtr() : mapOp.getVarPtr();
7458 llvm::Value *origValue = moduleTranslation.lookupValue(offloadPtr);
7459
7460 // Check if map info is already present for this entry.
7461 if (!findMapInfo(origValue, devInfoTy, mapOp.getMembers().size())) {
7462 mapData.OriginalValue.push_back(origValue);
7463 mapData.Pointers.push_back(mapData.OriginalValue.back());
7464 mapData.IsDeclareTarget.push_back(false);
7465 mapData.BasePointers.push_back(mapData.OriginalValue.back());
7466 mlir::Type baseTy = mapOp.getVarPtrPtr()
7467 ? mapOp.getVarPtrPtrType().value()
7468 : mapOp.getVarPtrType();
7469 mapData.BaseType.push_back(moduleTranslation.convertType(baseTy));
7470 mapData.Sizes.push_back(builder.getInt64(0));
7471 mapData.MapClause.push_back(mapOp.getOperation());
7472 mapData.Types.push_back(
7473 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM);
7474 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7475 mapData.HasAttachPtr.push_back(false);
7476 mapData.Names.push_back(LLVM::createMappingInformation(
7477 mapOp.getLoc(), *moduleTranslation.getOpenMPBuilder()));
7478 mapData.DevicePointers.push_back(devInfoTy);
7479 mapData.Mappers.push_back(nullptr);
7480 mapData.IsAMapping.push_back(false);
7481 mapData.IsAMember.push_back(checkIsAMember(useDevOperands, mapOp));
7482 }
7483 }
7484 };
7485
7486 addDevInfos(useDevAddrOperands, llvm::OpenMPIRBuilder::DeviceInfoTy::Address);
7487 addDevInfos(useDevPtrOperands, llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer);
7488
7489 for (Value mapValue : hasDevAddrOperands) {
7490 auto mapOp = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
7491 Value offloadPtr =
7492 mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtr() : mapOp.getVarPtr();
7493 llvm::Value *origValue = moduleTranslation.lookupValue(offloadPtr);
7494 auto mapType = convertClauseMapFlags(mapOp.getMapType());
7495 auto mapTypeAlways = llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ALWAYS;
7496 bool isDevicePtr =
7497 (mapOp.getMapType() & omp::ClauseMapFlags::is_device_ptr) !=
7498 omp::ClauseMapFlags::none;
7499
7500 mapData.OriginalValue.push_back(origValue);
7501 mapData.BasePointers.push_back(origValue);
7502 mapData.Pointers.push_back(origValue);
7503 mapData.IsDeclareTarget.push_back(false);
7504
7505 mlir::Type baseTy = mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtrType().value()
7506 : mapOp.getVarPtrType();
7507 mapData.BaseType.push_back(moduleTranslation.convertType(baseTy));
7508 mapData.Sizes.push_back(builder.getInt64(dl.getTypeSize(baseTy)));
7509
7510 mapData.MapClause.push_back(mapOp.getOperation());
7511 if (llvm::to_underlying(mapType & mapTypeAlways)) {
7512 // Descriptors are mapped with the ALWAYS flag, since they can get
7513 // rematerialized, so the address of the decriptor for a given object
7514 // may change from one place to another.
7515 mapData.Types.push_back(mapType);
7516 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7517 mapData.HasAttachPtr.push_back(false);
7518 // Technically it's possible for a non-descriptor mapping to have
7519 // both has-device-addr and ALWAYS, so lookup the mapper in case it
7520 // exists.
7521 if (mapOp.getMapperId()) {
7522 mapData.Mappers.push_back(
7524 mapOp, mapOp.getMapperIdAttr()));
7525 } else {
7526 mapData.Mappers.push_back(nullptr);
7527 }
7528 } else {
7529 // For is_device_ptr we need the map type to propagate so the runtime
7530 // can materialize the device-side copy of the pointer container.
7531 mapData.Types.push_back(
7532 isDevicePtr ? mapType
7533 : llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL);
7534 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7535 mapData.HasAttachPtr.push_back(false);
7536 mapData.Mappers.push_back(nullptr);
7537 }
7538 mapData.Names.push_back(LLVM::createMappingInformation(
7539 mapOp.getLoc(), *moduleTranslation.getOpenMPBuilder()));
7540 mapData.DevicePointers.push_back(
7541 isDevicePtr ? llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer
7542 : llvm::OpenMPIRBuilder::DeviceInfoTy::Address);
7543 mapData.IsAMapping.push_back(false);
7544 mapData.IsAMember.push_back(checkIsAMember(hasDevAddrOperands, mapOp));
7545 }
7546}
7547
7548static int getMapDataMemberIdx(MapInfoData &mapData, omp::MapInfoOp memberOp) {
7549 auto *res = llvm::find(mapData.MapClause, memberOp);
7550 assert(res != mapData.MapClause.end() &&
7551 "MapInfoOp for member not found in MapData, cannot return index");
7552 return std::distance(mapData.MapClause.begin(), res);
7553}
7554
7556 omp::MapInfoOp mapInfo, bool first = true) {
7557 ArrayAttr indexAttr = mapInfo.getMembersIndexAttr();
7558 llvm::SmallVector<size_t> occludedChildren;
7559 llvm::sort(
7560 indices.begin(), indices.end(), [&](const size_t a, const size_t b) {
7561 // Bail early if we are asked to look at the same index. If we do not
7562 // bail early, we can end up mistakenly adding indices to
7563 // occludedChildren. This can occur with some types of libc++ hardening.
7564 if (a == b)
7565 return false;
7566
7567 auto memberIndicesA = cast<ArrayAttr>(indexAttr[a]);
7568 auto memberIndicesB = cast<ArrayAttr>(indexAttr[b]);
7569
7570 for (auto it : llvm::zip(memberIndicesA, memberIndicesB)) {
7571 int64_t aIndex = mlir::cast<IntegerAttr>(std::get<0>(it)).getInt();
7572 int64_t bIndex = mlir::cast<IntegerAttr>(std::get<1>(it)).getInt();
7573
7574 if (aIndex == bIndex)
7575 continue;
7576
7577 if (aIndex < bIndex)
7578 return first;
7579
7580 if (aIndex > bIndex)
7581 return !first;
7582 }
7583
7584 // Iterated up until the end of the smallest member and
7585 // they were found to be equal up to that point, so select
7586 // the member with the lowest index count, so the "parent"
7587 bool memberAParent = memberIndicesA.size() < memberIndicesB.size();
7588 if (memberAParent)
7589 occludedChildren.push_back(b);
7590 else
7591 occludedChildren.push_back(a);
7592 return memberAParent;
7593 });
7594
7595 for (auto v : occludedChildren)
7596 indices.erase(std::remove(indices.begin(), indices.end(), v),
7597 indices.end());
7598}
7599
7600static omp::MapInfoOp getFirstOrLastMappedMemberPtr(omp::MapInfoOp mapInfo,
7601 bool first) {
7602 ArrayAttr indexAttr = mapInfo.getMembersIndexAttr();
7603 // Only 1 member has been mapped, we can return it.
7604 if (indexAttr.size() == 1)
7605 return cast<omp::MapInfoOp>(mapInfo.getMembers()[0].getDefiningOp());
7606 llvm::SmallVector<size_t> indices(indexAttr.size());
7607 std::iota(indices.begin(), indices.end(), 0);
7608 sortMapIndices(indices, mapInfo, first);
7609 return llvm::cast<omp::MapInfoOp>(
7610 mapInfo.getMembers()[indices.front()].getDefiningOp());
7611}
7612
7613/// This function calculates the array/pointer offset for map data provided
7614/// with bounds operations, e.g. when provided something like the following:
7615///
7616/// Fortran
7617/// map(tofrom: array(2:5, 3:2))
7618///
7619/// We must calculate the initial pointer offset to pass across, this function
7620/// performs this using bounds.
7621///
7622/// TODO/WARNING: This only supports Fortran's column major indexing currently
7623/// as is noted in the note below and comments in the function, we must extend
7624/// this function when we add a C++ frontend.
7625/// NOTE: which while specified in row-major order it currently needs to be
7626/// flipped for Fortran's column order array allocation and access (as
7627/// opposed to C++'s row-major, hence the backwards processing where order is
7628/// important). This is likely important to keep in mind for the future when
7629/// we incorporate a C++ frontend, both frontends will need to agree on the
7630/// ordering of generated bounds operations (one may have to flip them) to
7631/// make the below lowering frontend agnostic. The offload size
7632/// calcualtion may also have to be adjusted for C++.
7633static std::vector<llvm::Value *>
7635 llvm::IRBuilderBase &builder, bool isArrayTy,
7636 OperandRange bounds) {
7637 std::vector<llvm::Value *> idx;
7638 // There's no bounds to calculate an offset from, we can safely
7639 // ignore and return no indices.
7640 if (bounds.empty())
7641 return idx;
7642
7643 // If we have an array type, then we have its type so can treat it as a
7644 // normal GEP instruction where the bounds operations are simply indexes
7645 // into the array. We currently do reverse order of the bounds, which
7646 // I believe leans more towards Fortran's column-major in memory.
7647 if (isArrayTy) {
7648 idx.push_back(builder.getInt64(0));
7649 for (int i = bounds.size() - 1; i >= 0; --i) {
7650 if (auto boundOp = dyn_cast_if_present<omp::MapBoundsOp>(
7651 bounds[i].getDefiningOp())) {
7652 idx.push_back(moduleTranslation.lookupValue(boundOp.getLowerBound()));
7653 }
7654 }
7655 } else {
7656 // If we do not have an array type, but we have bounds, then we're dealing
7657 // with a pointer that's being treated like an array and we have the
7658 // underlying type e.g. an i32, or f64 etc, e.g. a fortran descriptor base
7659 // address (pointer pointing to the actual data) so we must caclulate the
7660 // offset using a single index which the following loop attempts to
7661 // compute using the standard column-major algorithm e.g for a 3D array:
7662 //
7663 // ((((c_idx * b_len) + b_idx) * a_len) + a_idx)
7664 //
7665 // It is of note that it's doing column-major rather than row-major at the
7666 // moment, but having a way for the frontend to indicate which major format
7667 // to use or standardizing/canonicalizing the order of the bounds to compute
7668 // the offset may be useful in the future when there's other frontends with
7669 // different formats.
7670 for (int i = bounds.size() - 1; i >= 0; --i) {
7671 if (auto boundOp = dyn_cast_if_present<omp::MapBoundsOp>(
7672 bounds[i].getDefiningOp())) {
7673 if (i == ((int)bounds.size() - 1))
7674 idx.emplace_back(
7675 moduleTranslation.lookupValue(boundOp.getLowerBound()));
7676 else
7677 idx.back() = builder.CreateAdd(
7678 builder.CreateMul(idx.back(), moduleTranslation.lookupValue(
7679 boundOp.getExtent())),
7680 moduleTranslation.lookupValue(boundOp.getLowerBound()));
7681 }
7682 }
7683 }
7684
7685 return idx;
7686}
7687
7689 llvm::transform(values, std::back_inserter(ints), [](Attribute value) {
7690 return cast<IntegerAttr>(value).getInt();
7691 });
7692}
7693
7694// Gathers members that are overlapping in the parent, excluding members that
7695// themselves overlap, keeping the top-most (closest to parents level) map.
7696static void
7698 omp::MapInfoOp parentOp) {
7699 // No members mapped, no overlaps.
7700 if (parentOp.getMembers().empty())
7701 return;
7702
7703 // Single member, we can insert and return early.
7704 if (parentOp.getMembers().size() == 1) {
7705 overlapMapDataIdxs.push_back(0);
7706 return;
7707 }
7708
7709 ArrayAttr indexAttr = parentOp.getMembersIndexAttr();
7710 size_t numMembers = indexAttr.size();
7711
7712 // Pre-convert all member indices to integer arrays for efficient comparison.
7713 llvm::SmallVector<llvm::SmallVector<int64_t>> memberIndices(numMembers);
7714 for (auto [i, indicesAttr] : llvm::enumerate(indexAttr))
7715 getAsIntegers(cast<ArrayAttr>(indicesAttr), memberIndices[i]);
7716
7717 // For each member, check if it's superseded by another (shorter prefix)
7718 // member. If member j's indices are a prefix of member i's indices, then
7719 // i is a child of j and should be skipped. e.g. if member [0] is mapped,
7720 // we skip members [0,1], [0,2], etc.
7721 llvm::SmallDenseSet<size_t> skipIndices;
7722 for (size_t i = 0; i < numMembers; ++i) {
7723 const auto &iIndices = memberIndices[i];
7724 for (size_t j = 0; j < numMembers; ++j) {
7725 if (i == j)
7726 continue;
7727 const auto &jIndices = memberIndices[j];
7728 // If j's indices are a strict prefix of i's indices, skip i
7729 if (jIndices.size() < iIndices.size() &&
7730 std::equal(jIndices.begin(), jIndices.end(), iIndices.begin())) {
7731 skipIndices.insert(i);
7732 break; // No need to check other potential parents
7733 }
7734 }
7735 }
7736
7737 // Collect indices of members that are not superseded by a parent.
7738 for (size_t i = 0; i < numMembers; ++i)
7739 if (!skipIndices.contains(i))
7740 overlapMapDataIdxs.push_back(i);
7741}
7742
7743/// This function handles the insertion of a single item of map data from
7744/// MapInfoData into the OMPIRBuilder's MapInfo list. Utilising this function
7745/// means the map being inserted can be treated as a non-parent map entity,
7746/// if the memberOfFlag is set then the map being inserted is treated as
7747/// a member map of a larger entity. The insertion into the MapInfo list of
7748/// the OMPIRBuilder can vary based on a number of factors, such as if it's
7749/// a ref_ptr or ref_ptee map, if it's a member of a record, what construct
7750/// the map belongs to and the various map type bit flags that are set for
7751/// the map.
7752static void
7753processIndividualMap(llvm::IRBuilderBase &builder,
7754 llvm::OpenMPIRBuilder &ompBuilder, MapInfoData &mapData,
7755 size_t mapDataIdx, MapInfosTy &combinedInfo,
7756 TargetDirectiveEnumTy targetDirective,
7757 llvm::omp::OpenMPOffloadMappingFlags memberOfFlag =
7758 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE,
7759 bool isTargetParam = true, int mapDataParentIdx = -1) {
7760 auto mapFlag = mapData.Types[mapDataIdx];
7761 auto mapInfoOp = llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIdx]);
7762
7763 bool isPtrTy = checkIfPointerMap(mapInfoOp);
7764 bool isAttachMap = ((convertClauseMapFlags(mapInfoOp.getMapType()) &
7765 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) ==
7766 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH);
7767
7768 // Declare target variables are not passed to the kernel, and for the moment
7769 // attach maps are not passed to the kernel. However, it is possible to create
7770 // attach maps that transfer data and thus can be kernel arguments, but our
7771 // existing frontend does not do this.
7772 if (isTargetParam &&
7773 (targetDirective == TargetDirectiveEnumTy::Target &&
7774 !mapData.IsDeclareTarget[mapDataIdx]) &&
7775 !isAttachMap)
7776 mapFlag |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TARGET_PARAM;
7777
7778 if (mapInfoOp.getMapCaptureType() == omp::VariableCaptureKind::ByCopy &&
7779 !isPtrTy)
7780 mapFlag |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL;
7781
7782 // If we have a pointer and it's part of a MEMBER_OF mapping we do not apply
7783 // MEMBER_OF, as the runtime currently has a work-around that utilises
7784 // MEMBER_OF to prevent reference updating in certain scenarios instead of
7785 // target_param. However, this causes a noticeable issue in cases where we
7786 // map some data (Fortran descriptor primarily at the moment), alter it on
7787 // the host, and then expect it to not be updated in a subsequent implicit map
7788 // (such as an implicit map on a target).
7789 if (memberOfFlag != llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE) {
7790 if (!isPtrTy && !isAttachMap)
7791 ompBuilder.setCorrectMemberOfFlag(mapFlag, memberOfFlag);
7792
7793 // The return parameter should be the over-riding parent in cases where we
7794 // have a return parameter that is echoed to all members, the main case of
7795 // this currently is with fortran descriptors. It may need more finessing
7796 // for C/C++ in the future or descriptors that are members of derived
7797 // types.
7798 mapFlag &= ~llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
7799 }
7800
7801 // We apply MAP_PTR_AND_OBJ when within a declare mapper object as it enforces
7802 // MEMBER_OF mappings on maps that are passed the initial nesting depth, which
7803 // includes pointed to data and attach members, both of which are technically
7804 // not part of the main object. This has the side effect of causing early
7805 // map-backs in certain cases where an implicit declare mapper has been
7806 // emitted for a target region. Applying MAP_PTR_AND_OBJ in these situations
7807 // circumvents this.
7808 if (isPtrTy && !isAttachMap && mapData.IsDeclareTarget[mapDataIdx])
7809 mapFlag |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_PTR_AND_OBJ;
7810
7811 // if we're provided a mapDataParentIdx, then the data being mapped is
7812 // part of a larger object (in a parent <-> member mapping) and in this
7813 // case our BasePointer should be the parent. Except in the edge case
7814 // where we are mapping pointee data, where we try staying close to
7815 // what Clang currently does and utilise the regular base pointer of the
7816 // data.
7817 bool isRefPtee =
7818 !bitEnumContainsAll(mapInfoOp.getMapType(),
7819 omp::ClauseMapFlags::ref_ptr) &&
7820 bitEnumContainsAll(mapInfoOp.getMapType(), omp::ClauseMapFlags::ref_ptee);
7821 bool isRefPtrPtee = bitEnumContainsAll(mapInfoOp.getMapType(),
7822 omp::ClauseMapFlags::ref_ptr |
7823 omp::ClauseMapFlags::ref_ptee);
7824
7825 if (!mapInfoOp->getParentOfType<omp::DeclareMapperOp>() &&
7826 mapDataParentIdx >= 0 && !(isRefPtee || (isRefPtrPtee && isPtrTy))) {
7827 combinedInfo.BasePointers.emplace_back(
7828 mapData.BasePointers[mapDataParentIdx]);
7829 } else {
7830 combinedInfo.BasePointers.emplace_back(mapData.BasePointers[mapDataIdx]);
7831 }
7832
7833 combinedInfo.Pointers.emplace_back(mapData.Pointers[mapDataIdx]);
7834 combinedInfo.DevicePointers.emplace_back(
7835 memberOfFlag != llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE
7836 ? llvm::OpenMPIRBuilder::DeviceInfoTy::None
7837 : mapData.DevicePointers[mapDataIdx]);
7838 combinedInfo.Mappers.emplace_back(mapData.Mappers[mapDataIdx]);
7839 combinedInfo.Names.emplace_back(mapData.Names[mapDataIdx]);
7840 combinedInfo.Types.emplace_back(mapFlag);
7841 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7842 combinedInfo.HasAttachPtr.emplace_back(false);
7843 combinedInfo.Sizes.emplace_back(
7844 isPtrTy ? builder.CreateSelect(
7845 builder.CreateIsNull(mapData.Pointers[mapDataIdx]),
7846 builder.getInt64(0), mapData.Sizes[mapDataIdx])
7847 : mapData.Sizes[mapDataIdx]);
7848}
7849
7850// This creates two insertions into the MapInfosTy data structure for the
7851// "parent" of a set of members, (usually a container e.g.
7852// class/structure/derived type) when subsequent members have also been
7853// explicitly mapped on the same map clause. Certain types, such as Fortran
7854// descriptors are mapped like this as well, however, the members are
7855// implicit as far as a user is concerned, but we must explicitly map them
7856// internally.
7857//
7858// This function also returns the memberOfFlag for this particular parent,
7859// which is utilised in subsequent member mappings (by modifying there map type
7860// with it) to indicate that a member is part of this parent and should be
7861// treated by the runtime as such. Important to achieve the correct mapping.
7862//
7863// This function borrows a lot from Clang's emitCombinedEntry function
7864// inside of CGOpenMPRuntime.cpp
7866 LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder,
7867 llvm::OpenMPIRBuilder &ompBuilder, DataLayout &dl, MapInfosTy &combinedInfo,
7868 MapInfoData &mapData, uint64_t mapDataIndex,
7869 llvm::omp::OpenMPOffloadMappingFlags memberOfFlag,
7870 TargetDirectiveEnumTy targetDirective) {
7871 using MapFlags = llvm::omp::OpenMPOffloadMappingFlags;
7872 assert(!ompBuilder.Config.isTargetDevice() &&
7873 "function only supported for host device codegen");
7874 auto parentClause =
7875 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
7876 auto *parentMapper = mapData.Mappers[mapDataIndex];
7877
7878 // Map the first segment of the parent. If a user-defined mapper is attached,
7879 // include the parent's to/from-style bits (and common modifiers) in this
7880 // base entry so the mapper receives correct copy semantics via its 'type'
7881 // parameter. Also keep TARGET_PARAM when required for kernel arguments.
7882 MapFlags baseFlag = (targetDirective == TargetDirectiveEnumTy::Target &&
7883 !mapData.IsDeclareTarget[mapDataIndex])
7884 ? MapFlags::OMP_MAP_TARGET_PARAM
7885 : MapFlags::OMP_MAP_NONE;
7886
7887 if (parentMapper) {
7888 // Preserve relevant map-type bits from the parent clause. These include
7889 // the copy direction (TO/FROM), as well as commonly used modifiers that
7890 // should be visible to the mapper for correct behaviour.
7891 MapFlags parentFlags = mapData.Types[mapDataIndex];
7892 MapFlags preserve = MapFlags::OMP_MAP_TO | MapFlags::OMP_MAP_FROM |
7893 MapFlags::OMP_MAP_ALWAYS | MapFlags::OMP_MAP_CLOSE |
7894 MapFlags::OMP_MAP_PRESENT |
7895 MapFlags::OMP_MAP_OMPX_HOLD |
7896 MapFlags::OMP_MAP_IMPLICIT;
7897 baseFlag |= (parentFlags & preserve);
7898 } else {
7899 MapFlags parentFlags = mapData.Types[mapDataIndex];
7900 MapFlags preserve = MapFlags::OMP_MAP_TO | MapFlags::OMP_MAP_FROM |
7901 MapFlags::OMP_MAP_PRESENT |
7902 MapFlags::OMP_MAP_RETURN_PARAM |
7903 MapFlags::OMP_MAP_IMPLICIT;
7904 baseFlag |= (parentFlags & preserve);
7905 }
7906
7907 combinedInfo.Types.emplace_back(baseFlag);
7908 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7909 combinedInfo.HasAttachPtr.emplace_back(false);
7910 combinedInfo.DevicePointers.emplace_back(
7911 mapData.DevicePointers[mapDataIndex]);
7912 // Only attach the mapper to the base entry when we are mapping the whole
7913 // parent. Combined/segment entries must not carry a mapper; otherwise the
7914 // mapper can be invoked with a partial size, which is undefined behaviour.
7915 combinedInfo.Mappers.emplace_back(
7916 parentMapper && !parentClause.getPartialMap() ? parentMapper : nullptr);
7917 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
7918 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder));
7919 combinedInfo.BasePointers.emplace_back(mapData.BasePointers[mapDataIndex]);
7920
7921 // Calculate size of the parent object being mapped based on the
7922 // addresses at runtime, highAddr - lowAddr = size. This of course
7923 // doesn't factor in allocated data like pointers, hence the further
7924 // processing of members specified by users, or in the case of
7925 // Fortran pointers and allocatables, the mapping of the pointed to
7926 // data by the descriptor (which itself, is a structure containing
7927 // runtime information on the dynamically allocated data).
7928 llvm::Value *lowAddr, *highAddr;
7929 if (!parentClause.getPartialMap()) {
7930 lowAddr = builder.CreatePointerCast(mapData.Pointers[mapDataIndex],
7931 builder.getPtrTy());
7932 highAddr = builder.CreatePointerCast(
7933 builder.CreateConstGEP1_32(mapData.BaseType[mapDataIndex],
7934 mapData.Pointers[mapDataIndex], 1),
7935 builder.getPtrTy());
7936 combinedInfo.Pointers.emplace_back(mapData.Pointers[mapDataIndex]);
7937 } else {
7938 auto mapOp = dyn_cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
7939 int firstMemberIdx = getMapDataMemberIdx(
7940 mapData, getFirstOrLastMappedMemberPtr(mapOp, true));
7941 lowAddr = builder.CreatePointerCast(mapData.BasePointers[firstMemberIdx],
7942 builder.getPtrTy());
7943
7944 int lastMemberIdx = getMapDataMemberIdx(
7945 mapData, getFirstOrLastMappedMemberPtr(mapOp, false));
7946 auto lastMemberMapInfo =
7947 cast<omp::MapInfoOp>(mapData.MapClause[lastMemberIdx]);
7948
7949 // NOTE: Currently, for RefPtee the BaseType is set to the varPtrPtr field,
7950 // which is the pointer datas type and not the member within the structure
7951 // that it's part of, so we have to make sure we use the member type in this
7952 // case when calculating the parents size offsets.
7953 // TODO: May be good to extend MapInfoData to support tracking of both
7954 // VarPtr/VarPtrPtr BaseType's to better distinguish what's being used more
7955 // consistently.
7956 bool isRefPteeMap = bitEnumContainsAll(lastMemberMapInfo.getMapType(),
7957 omp::ClauseMapFlags::ref_ptee) &&
7958 !bitEnumContainsAll(lastMemberMapInfo.getMapType(),
7959 omp::ClauseMapFlags::ref_ptr);
7960 llvm::Type *castType = mapData.BaseType[lastMemberIdx];
7961 if (isRefPteeMap)
7962 castType =
7963 moduleTranslation.convertType(lastMemberMapInfo.getVarPtrType());
7964 highAddr = builder.CreatePointerCast(
7965 builder.CreateGEP(castType, mapData.BasePointers[lastMemberIdx],
7966 builder.getInt64(1)),
7967 builder.getPtrTy());
7968 combinedInfo.Pointers.emplace_back(mapData.BasePointers[firstMemberIdx]);
7969 }
7970
7971 llvm::Value *size = builder.CreateIntCast(
7972 builder.CreatePtrDiff(builder.getInt8Ty(), highAddr, lowAddr),
7973 builder.getInt64Ty(),
7974 /*isSigned=*/false);
7975 combinedInfo.Sizes.push_back(size);
7976
7977 // This creates the initial MEMBER_OF mapping that consists of
7978 // the parent/top level container (same as above effectively, except
7979 // with a fixed initial compile time size and separate maptype which
7980 // indicates the true mape type (tofrom etc.). This parent mapping is
7981 // only relevant if the structure in its totality is being mapped,
7982 // otherwise the above suffices.
7983 if (!parentClause.getPartialMap()) {
7984 // TODO: This will need to be expanded to include the whole host of logic
7985 // for the map flags that Clang currently supports (e.g. it should do some
7986 // further case specific flag modifications). For the moment, it handles
7987 // what we support as expected.
7988 MapFlags mapFlag = mapData.Types[mapDataIndex];
7989 bool hasMapClose = (MapFlags(mapFlag) & MapFlags::OMP_MAP_CLOSE) ==
7990 MapFlags::OMP_MAP_CLOSE;
7991 ompBuilder.setCorrectMemberOfFlag(mapFlag, memberOfFlag);
7992
7993 llvm::SmallVector<size_t> overlapIdxs;
7994 // Find all of the members that "overlap", i.e. occlude other members that
7995 // were mapped alongside the parent, e.g. member [0], occludes [0,1] and
7996 // [0,2], but not [1,0].
7997 getOverlappedMembers(overlapIdxs, parentClause);
7998
7999 // When we only have one overlap we skip the case that tries to segment the
8000 // mapping as best it can without creating holes, as the calculation is more
8001 // likely to have more overhead than anything we gain from mapping a smaller
8002 // chunk of data. This can be seen in cases where we are mapping Fortran
8003 // descriptors which are a special case of record type mapping.
8004 //
8005 // The cases for close and update are unique edge cases where the segmenting
8006 // does not play well with the runtime currently.
8007 if (targetDirective == TargetDirectiveEnumTy::TargetUpdate || hasMapClose ||
8008 overlapIdxs.size() == 1) {
8009 combinedInfo.Types.emplace_back(mapFlag);
8010 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
8011 combinedInfo.HasAttachPtr.emplace_back(false);
8012 combinedInfo.DevicePointers.emplace_back(
8013 mapData.DevicePointers[mapDataIndex]);
8014 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
8015 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder));
8016 combinedInfo.BasePointers.emplace_back(
8017 mapData.BasePointers[mapDataIndex]);
8018 combinedInfo.Pointers.emplace_back(mapData.Pointers[mapDataIndex]);
8019 combinedInfo.Sizes.emplace_back(mapData.Sizes[mapDataIndex]);
8020 combinedInfo.Mappers.emplace_back(nullptr);
8021 } else {
8022 // We need to make sure the overlapped members are sorted in order of
8023 // lowest address to highest address.
8024 sortMapIndices(overlapIdxs, parentClause);
8025
8026 lowAddr = builder.CreatePointerCast(mapData.Pointers[mapDataIndex],
8027 builder.getPtrTy());
8028 highAddr = builder.CreatePointerCast(
8029 builder.CreateConstGEP1_32(mapData.BaseType[mapDataIndex],
8030 mapData.Pointers[mapDataIndex], 1),
8031 builder.getPtrTy());
8032
8033 // Currently, the return parameter should be the over-riding parent in
8034 // cases where we have a return parameter that is echoed to all members,
8035 // the main case of this currently is with fortran descriptors. It may
8036 // need more finessing for C/C++ in the future or descriptors that are
8037 // members of derived types.
8038 mapFlag &= ~llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
8039
8040 // TODO: We may want to skip arrays/array sections in this as Clang does.
8041 // It appears to be an optimisation rather than a necessity though,
8042 // but this requires further investigation. However, we would have to make
8043 // sure to not exclude maps with bounds that ARE pointers, as these are
8044 // processed as separate components, i.e. pointer + data.
8045 for (auto v : overlapIdxs) {
8046 auto mapDataOverlapIdx = getMapDataMemberIdx(
8047 mapData,
8048 cast<omp::MapInfoOp>(parentClause.getMembers()[v].getDefiningOp()));
8049 auto isPtrMap = checkIfPointerMap(
8050 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataOverlapIdx]));
8051 combinedInfo.Types.emplace_back(mapFlag);
8052 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
8053 combinedInfo.HasAttachPtr.emplace_back(false);
8054 combinedInfo.DevicePointers.emplace_back(
8055 llvm::OpenMPIRBuilder::DeviceInfoTy::None);
8056 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
8057 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder));
8058 combinedInfo.BasePointers.emplace_back(
8059 mapData.BasePointers[mapDataIndex]);
8060 combinedInfo.Mappers.emplace_back(nullptr);
8061 combinedInfo.Pointers.emplace_back(lowAddr);
8062 auto sizeCalc = builder.CreateIntCast(
8063 builder.CreatePtrDiff(builder.getInt8Ty(),
8064 mapData.OriginalValue[mapDataOverlapIdx],
8065 lowAddr),
8066 builder.getInt64Ty(), /*isSigned=*/true);
8067 // In certain cases, we'll generate a size of 0 if we're not careful
8068 // (e.g. if lowAddr happens to be the first member), which isn't
8069 // correct, even if the runtimes is sometimes fine with it so, in these
8070 // scenarios we select the types size instead.
8071 auto sizeSel = builder.CreateSelect(
8072 builder.CreateICmpNE(builder.getInt64(0), sizeCalc), sizeCalc,
8073 isPtrMap ? llvm::ConstantExpr::getSizeOf(builder.getPtrTy())
8074 : mapData.Sizes[mapDataOverlapIdx]);
8075 combinedInfo.Sizes.emplace_back(sizeSel);
8076 lowAddr = builder.CreateConstGEP1_32(
8077 isPtrMap ? builder.getPtrTy() : mapData.BaseType[mapDataOverlapIdx],
8078 mapData.BasePointers[mapDataOverlapIdx], 1);
8079 }
8080
8081 combinedInfo.Types.emplace_back(mapFlag);
8082 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
8083 combinedInfo.HasAttachPtr.emplace_back(false);
8084 combinedInfo.DevicePointers.emplace_back(
8085 llvm::OpenMPIRBuilder::DeviceInfoTy::None);
8086 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
8087 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder));
8088 combinedInfo.BasePointers.emplace_back(
8089 mapData.BasePointers[mapDataIndex]);
8090 combinedInfo.Mappers.emplace_back(nullptr);
8091 combinedInfo.Pointers.emplace_back(lowAddr);
8092 combinedInfo.Sizes.emplace_back(builder.CreateIntCast(
8093 builder.CreatePtrDiff(builder.getInt8Ty(), highAddr, lowAddr),
8094 builder.getInt64Ty(), true));
8095 }
8096 }
8097}
8098
8100 llvm::IRBuilderBase &builder,
8101 llvm::OpenMPIRBuilder &ompBuilder,
8102 DataLayout &dl, MapInfosTy &combinedInfo,
8103 MapInfoData &mapData, uint64_t mapDataIndex,
8104 TargetDirectiveEnumTy targetDirective) {
8105 assert(!ompBuilder.Config.isTargetDevice() &&
8106 "function only supported for host device codegen");
8107
8108 auto parentClause =
8109 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
8110
8111 // If we have a partial map (no parent referenced in the map clauses of the
8112 // directive, only members) and only a single member, we do not need to bind
8113 // the map of the member to the parent, we can pass the member separately.
8114 if (parentClause.getMembers().size() == 1 && parentClause.getPartialMap()) {
8115 auto memberClause = llvm::cast<omp::MapInfoOp>(
8116 parentClause.getMembers()[0].getDefiningOp());
8117 int memberDataIdx = getMapDataMemberIdx(mapData, memberClause);
8118 // Note: Clang treats arrays with explicit bounds that fall into this
8119 // category as a parent with map case, however, it seems this isn't a
8120 // requirement, and processing them as an individual map is fine. So,
8121 // we will handle them as individual maps for the moment, as it's
8122 // difficult for us to check this as we always require bounds to be
8123 // specified currently and it's also marginally more optimal (single
8124 // map rather than two). The difference may come from the fact that
8125 // Clang maps array without bounds as pointers (which we do not
8126 // currently do), whereas we treat them as arrays in all cases
8127 // currently.
8129 builder, ompBuilder, mapData, memberDataIdx, combinedInfo,
8130 targetDirective,
8131 /*MemberOfFlag=*/llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE,
8132 /*isTargetParam=*/true, mapDataIndex);
8133 return;
8134 }
8135
8136 auto collectMapInfoIdxs =
8137 [&](llvm::SmallVectorImpl<int64_t> &mapsAndInfoIdx) {
8138 auto parentClause =
8139 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
8140 mapsAndInfoIdx.push_back(getMapDataMemberIdx(mapData, parentClause));
8141 for (auto member : parentClause.getMembers())
8142 mapsAndInfoIdx.push_back(getMapDataMemberIdx(
8143 mapData, llvm::cast<omp::MapInfoOp>(member.getDefiningOp())));
8144 };
8145
8146 llvm::SmallVector<int64_t> mapInfoIdx;
8147 collectMapInfoIdxs(mapInfoIdx);
8148
8149 llvm::omp::OpenMPOffloadMappingFlags memberOfFlag =
8150 ompBuilder.getMemberOfFlag(combinedInfo.Types.size());
8151
8152 // The first index is the parent map, the rest are its members. The parent
8153 // normally undergoes the standard parent-with-members mapping, contributing
8154 // the MEMBER_OF flag that binds each member to it. The one exception is a
8155 // privatizeable attach map (a privatized pointer/descriptor passed directly
8156 // as a kernel argument): here the parent is emitted as an individual map
8157 // instead, for the time being, as it's used only in pointer/allocatable to
8158 // array cases for the moment. This only ever applies to the parent, so it is
8159 // checked once here rather than inside the loop below.
8160 bool parentIsPrivatizeableAttach =
8161 isPrivatizeableAttachMap(parentClause.getMapType());
8162 for (auto [i, idx] : llvm::enumerate(mapInfoIdx)) {
8163 bool emitParentMap = i == 0 && !parentIsPrivatizeableAttach;
8164 if (emitParentMap) {
8165 mapParentWithMembers(moduleTranslation, builder, ompBuilder, dl,
8166 combinedInfo, mapData, idx, memberOfFlag,
8167 targetDirective);
8168 } else {
8170 builder, ompBuilder, mapData, idx, combinedInfo, targetDirective,
8171 parentIsPrivatizeableAttach
8172 ? llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE
8173 : memberOfFlag,
8174 /*isTargetParam=*/false, mapDataIndex);
8175 }
8176 }
8177}
8178
8179// This is a variation on Clang's GenerateOpenMPCapturedVars, which
8180// generates different operation (e.g. load/store) combinations for
8181// arguments to the kernel, based on map capture kinds which are then
8182// utilised in the combinedInfo in place of the original Map value.
8183static void
8184createAlteredByCaptureMap(MapInfoData &mapData,
8185 LLVM::ModuleTranslation &moduleTranslation,
8186 llvm::IRBuilderBase &builder) {
8187 assert(!moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
8188 "function only supported for host device codegen");
8189 for (size_t i = 0; i < mapData.MapClause.size(); ++i) {
8190 auto mapOp = cast<omp::MapInfoOp>(mapData.MapClause[i]);
8191 bool isAttachMap =
8192 ((convertClauseMapFlags(mapOp.getMapType()) &
8193 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) ==
8194 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH);
8195
8196 // If it's declare target, skip it, it's handled separately. However, if
8197 // it's declare target, and an attach map, we want to calculate the exact
8198 // address offset so that we attach correctly.
8199 if (!mapData.IsDeclareTarget[i] ||
8200 (mapData.IsDeclareTarget[i] && isAttachMap)) {
8201 omp::VariableCaptureKind captureKind = mapOp.getMapCaptureType();
8202 bool isPtrTy = checkIfPointerMap(mapOp);
8203
8204 // Currently handles array sectioning lowerbound case, but more
8205 // logic may be required in the future. Clang invokes EmitLValue,
8206 // which has specialised logic for special Clang types such as user
8207 // defines, so it is possible we will have to extend this for
8208 // structures or other complex types. As the general idea is that this
8209 // function mimics some of the logic from Clang that we require for
8210 // kernel argument passing from host -> device.
8211 switch (captureKind) {
8212 case omp::VariableCaptureKind::ByRef: {
8213 llvm::Value *newV = mapData.Pointers[i];
8214 std::vector<llvm::Value *> offsetIdx = calculateBoundsOffset(
8215 moduleTranslation, builder, mapData.BaseType[i]->isArrayTy(),
8216 mapOp.getBounds());
8217 if (isPtrTy)
8218 newV = builder.CreateLoad(builder.getPtrTy(), newV);
8219
8220 if (!offsetIdx.empty())
8221 newV = builder.CreateInBoundsGEP(mapData.BaseType[i], newV, offsetIdx,
8222 "array_offset");
8223 mapData.Pointers[i] = newV;
8224 } break;
8225 case omp::VariableCaptureKind::ByCopy: {
8226 llvm::Type *type = mapData.BaseType[i];
8227 llvm::Value *newV;
8228 if (mapData.Pointers[i]->getType()->isPointerTy())
8229 newV = builder.CreateLoad(type, mapData.Pointers[i]);
8230 else
8231 newV = mapData.Pointers[i];
8232
8233 if (!isPtrTy) {
8234 auto curInsert = builder.saveIP();
8235 llvm::DebugLoc DbgLoc = builder.getCurrentDebugLocation();
8236 builder.restoreIP(findAllocInsertPoints(builder, moduleTranslation));
8237 auto *memTempAlloc =
8238 builder.CreateAlloca(builder.getPtrTy(), nullptr, ".casted");
8239 builder.SetCurrentDebugLocation(DbgLoc);
8240 builder.restoreIP(curInsert);
8241
8242 builder.CreateStore(newV, memTempAlloc);
8243 newV = builder.CreateLoad(builder.getPtrTy(), memTempAlloc);
8244 }
8245
8246 mapData.Pointers[i] = newV;
8247 mapData.BasePointers[i] = newV;
8248 } break;
8249 case omp::VariableCaptureKind::This:
8250 case omp::VariableCaptureKind::VLAType:
8251 mapData.MapClause[i]->emitOpError("Unhandled capture kind");
8252 break;
8253 }
8254 }
8255 }
8256}
8257
8258// Generate all map related information and fill the combinedInfo.
8259static void genMapInfos(llvm::IRBuilderBase &builder,
8260 LLVM::ModuleTranslation &moduleTranslation,
8261 DataLayout &dl, MapInfosTy &combinedInfo,
8262 MapInfoData &mapData,
8263 TargetDirectiveEnumTy targetDirective) {
8264 assert(!moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
8265 "function only supported for host device codegen");
8266 // We wish to modify some of the methods in which arguments are
8267 // passed based on their capture type by the target region, this can
8268 // involve generating new loads and stores, which changes the
8269 // MLIR value to LLVM value mapping, however, we only wish to do this
8270 // locally for the current function/target and also avoid altering
8271 // ModuleTranslation, so we remap the base pointer or pointer stored
8272 // in the map infos corresponding MapInfoData, which is later accessed
8273 // by genMapInfos and createTarget to help generate the kernel and
8274 // kernel arg structure. It primarily becomes relevant in cases like
8275 // bycopy, or byref range'd arrays. In the default case, we simply
8276 // pass thee pointer byref as both basePointer and pointer.
8277 createAlteredByCaptureMap(mapData, moduleTranslation, builder);
8278
8279 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8280
8281 // We operate under the assumption that all vectors that are
8282 // required in MapInfoData are of equal lengths (either filled with
8283 // default constructed data or appropiate information) so we can
8284 // utilise the size from any component of MapInfoData, if we can't
8285 // something is missing from the initial MapInfoData construction.
8286 for (size_t i = 0; i < mapData.MapClause.size(); ++i) {
8287 if (mapData.IsAMember[i])
8288 continue;
8289
8290 auto mapInfoOp = dyn_cast<omp::MapInfoOp>(mapData.MapClause[i]);
8291 if (!mapInfoOp.getMembers().empty()) {
8292 processMapWithMembersOf(moduleTranslation, builder, *ompBuilder, dl,
8293 combinedInfo, mapData, i, targetDirective);
8294 continue;
8295 }
8296
8297 processIndividualMap(builder, *ompBuilder, mapData, i, combinedInfo,
8298 targetDirective);
8299 }
8300}
8301
8302static llvm::Expected<llvm::Function *>
8303emitUserDefinedMapper(Operation *declMapperOp, llvm::IRBuilderBase &builder,
8304 LLVM::ModuleTranslation &moduleTranslation,
8305 llvm::StringRef mapperFuncName,
8306 TargetDirectiveEnumTy targetDirective);
8307
8308static llvm::Expected<llvm::Function *>
8309getOrCreateUserDefinedMapperFunc(Operation *op, llvm::IRBuilderBase &builder,
8310 LLVM::ModuleTranslation &moduleTranslation,
8311 TargetDirectiveEnumTy targetDirective) {
8312 assert(!moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
8313 "function only supported for host device codegen");
8314 auto declMapperOp = cast<omp::DeclareMapperOp>(op);
8315 std::string mapperFuncName =
8316 moduleTranslation.getOpenMPBuilder()->createPlatformSpecificName(
8317 {"omp_mapper", declMapperOp.getSymName()});
8318
8319 if (auto *lookupFunc = moduleTranslation.lookupFunction(mapperFuncName))
8320 return lookupFunc;
8321
8322 // Recursive types can cause re-entrant mapper emission. The mapper function
8323 // is created by OpenMPIRBuilder before the callbacks run, so it may already
8324 // exist in the LLVM module even though it is not yet registered in the
8325 // ModuleTranslation mapping table. Reuse and register it to break the
8326 // recursion.
8327 if (llvm::Function *existingFunc =
8328 moduleTranslation.getLLVMModule()->getFunction(mapperFuncName)) {
8329 moduleTranslation.mapFunction(mapperFuncName, existingFunc);
8330 return existingFunc;
8331 }
8332
8333 return emitUserDefinedMapper(declMapperOp, builder, moduleTranslation,
8334 mapperFuncName, targetDirective);
8335}
8336
8337static llvm::Expected<llvm::Function *>
8338emitUserDefinedMapper(Operation *op, llvm::IRBuilderBase &builder,
8339 LLVM::ModuleTranslation &moduleTranslation,
8340 llvm::StringRef mapperFuncName,
8341 TargetDirectiveEnumTy targetDirective) {
8342 assert(!moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
8343 "function only supported for host device codegen");
8344 auto declMapperOp = cast<omp::DeclareMapperOp>(op);
8345 auto declMapperInfoOp = declMapperOp.getDeclareMapperInfo();
8346 if (failed(checkImplementationStatus(*declMapperInfoOp)))
8347 return llvm::make_error<PreviouslyReportedError>();
8348
8349 DataLayout dl = DataLayout(declMapperOp->getParentOfType<ModuleOp>());
8350 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8351 llvm::Type *varType = moduleTranslation.convertType(declMapperOp.getType());
8352 SmallVector<Value> mapVars = declMapperInfoOp.getMapVars();
8353
8354 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
8355
8356 // Fill up the arrays with all the mapped variables.
8357 MapInfosTy combinedInfo;
8358 auto genMapInfoCB =
8359 [&](InsertPointTy codeGenIP, llvm::Value *ptrPHI,
8360 llvm::Value *unused2) -> llvm::OpenMPIRBuilder::MapInfosOrErrorTy {
8361 builder.restoreIP(codeGenIP);
8362 moduleTranslation.mapValue(declMapperOp.getSymVal(), ptrPHI);
8363 moduleTranslation.mapBlock(&declMapperOp.getRegion().front(),
8364 builder.GetInsertBlock());
8365 if (failed(moduleTranslation.convertBlock(declMapperOp.getRegion().front(),
8366 /*ignoreArguments=*/true,
8367 builder)))
8368 return llvm::make_error<PreviouslyReportedError>();
8369 MapInfoData mapData;
8370 collectMapDataFromMapOperands(mapData, mapVars, moduleTranslation, dl,
8371 builder);
8372 genMapInfos(builder, moduleTranslation, dl, combinedInfo, mapData,
8373 targetDirective);
8374
8375 // Drop the mapping that is no longer necessary so that the same region
8376 // can be processed multiple times.
8377 moduleTranslation.forgetMapping(declMapperOp.getRegion());
8378 return combinedInfo;
8379 };
8380
8381 auto customMapperCB = [&](unsigned i) -> llvm::Expected<llvm::Function *> {
8382 if (!combinedInfo.Mappers[i])
8383 return nullptr;
8384 return getOrCreateUserDefinedMapperFunc(combinedInfo.Mappers[i], builder,
8385 moduleTranslation, targetDirective);
8386 };
8387
8388 llvm::Expected<llvm::Function *> newFn = ompBuilder->emitUserDefinedMapper(
8389 genMapInfoCB, varType, mapperFuncName, customMapperCB,
8390 /*PreserveMemberOfFlags=*/true);
8391 if (!newFn)
8392 return newFn.takeError();
8393 if ([[maybe_unused]] llvm::Function *mappedFunc =
8394 moduleTranslation.lookupFunction(mapperFuncName)) {
8395 assert(mappedFunc == *newFn &&
8396 "mapper function mapping disagrees with emitted function");
8397 } else {
8398 moduleTranslation.mapFunction(mapperFuncName, *newFn);
8399 }
8400 return *newFn;
8401}
8402
8403static LogicalResult
8404convertOmpTargetData(Operation *op, llvm::IRBuilderBase &builder,
8405 LLVM::ModuleTranslation &moduleTranslation) {
8406 llvm::Value *ifCond = nullptr;
8407 llvm::Value *deviceID = builder.getInt64(llvm::omp::OMP_DEVICEID_UNDEF);
8408 SmallVector<Value> mapVars;
8409 SmallVector<Value> useDevicePtrVars;
8410 SmallVector<Value> useDeviceAddrVars;
8411 llvm::omp::RuntimeFunction RTLFn;
8412 DataLayout DL = DataLayout(op->getParentOfType<ModuleOp>());
8413 TargetDirectiveEnumTy targetDirective = getTargetDirectiveEnumTyFromOp(op);
8414
8415 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8416 llvm::OpenMPIRBuilder::TargetDataInfo info(
8417 /*RequiresDevicePointerInfo=*/true,
8418 /*SeparateBeginEndCalls=*/true);
8419
8420 if (ompBuilder->Config.isTargetDevice())
8421 return op->emitOpError() << "not allowed in a target device";
8422
8423 bool isOffloadEntry = !ompBuilder->Config.TargetTriples.empty();
8424
8425 auto getDeviceID = [&](mlir::Value dev) -> llvm::Value * {
8426 llvm::Value *v = moduleTranslation.lookupValue(dev);
8427 return builder.CreateIntCast(v, builder.getInt64Ty(), /*isSigned=*/true);
8428 };
8429
8430 LogicalResult result =
8432 .Case([&](omp::TargetDataOp dataOp) {
8433 if (failed(checkImplementationStatus(*dataOp)))
8434 return failure();
8435
8436 if (auto ifVar = dataOp.getIfExpr())
8437 ifCond = moduleTranslation.lookupValue(ifVar);
8438
8439 if (mlir::Value devId = dataOp.getDevice())
8440 deviceID = getDeviceID(devId);
8441
8442 mapVars = dataOp.getMapVars();
8443 useDevicePtrVars = dataOp.getUseDevicePtrVars();
8444 useDeviceAddrVars = dataOp.getUseDeviceAddrVars();
8445 return success();
8446 })
8447 .Case([&](omp::TargetEnterDataOp enterDataOp) -> LogicalResult {
8448 if (failed(checkImplementationStatus(*enterDataOp)))
8449 return failure();
8450
8451 if (auto ifVar = enterDataOp.getIfExpr())
8452 ifCond = moduleTranslation.lookupValue(ifVar);
8453
8454 if (mlir::Value devId = enterDataOp.getDevice())
8455 deviceID = getDeviceID(devId);
8456
8457 RTLFn =
8458 enterDataOp.getNowait()
8459 ? llvm::omp::OMPRTL___tgt_target_data_begin_nowait_mapper
8460 : llvm::omp::OMPRTL___tgt_target_data_begin_mapper;
8461 mapVars = enterDataOp.getMapVars();
8462 info.HasNoWait = enterDataOp.getNowait();
8463 return success();
8464 })
8465 .Case([&](omp::TargetExitDataOp exitDataOp) -> LogicalResult {
8466 if (failed(checkImplementationStatus(*exitDataOp)))
8467 return failure();
8468
8469 if (auto ifVar = exitDataOp.getIfExpr())
8470 ifCond = moduleTranslation.lookupValue(ifVar);
8471
8472 if (mlir::Value devId = exitDataOp.getDevice())
8473 deviceID = getDeviceID(devId);
8474
8475 RTLFn = exitDataOp.getNowait()
8476 ? llvm::omp::OMPRTL___tgt_target_data_end_nowait_mapper
8477 : llvm::omp::OMPRTL___tgt_target_data_end_mapper;
8478 mapVars = exitDataOp.getMapVars();
8479 info.HasNoWait = exitDataOp.getNowait();
8480 return success();
8481 })
8482 .Case([&](omp::TargetUpdateOp updateDataOp) -> LogicalResult {
8483 if (failed(checkImplementationStatus(*updateDataOp)))
8484 return failure();
8485
8486 if (auto ifVar = updateDataOp.getIfExpr())
8487 ifCond = moduleTranslation.lookupValue(ifVar);
8488
8489 if (mlir::Value devId = updateDataOp.getDevice())
8490 deviceID = getDeviceID(devId);
8491
8492 RTLFn =
8493 updateDataOp.getNowait()
8494 ? llvm::omp::OMPRTL___tgt_target_data_update_nowait_mapper
8495 : llvm::omp::OMPRTL___tgt_target_data_update_mapper;
8496 mapVars = updateDataOp.getMapVars();
8497 info.HasNoWait = updateDataOp.getNowait();
8498 return success();
8499 })
8500 .DefaultUnreachable("unexpected operation");
8501
8502 if (failed(result))
8503 return failure();
8504 // Pretend we have IF(false) if we're not doing offload.
8505 if (!isOffloadEntry)
8506 ifCond = builder.getFalse();
8507
8508 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
8509 MapInfoData mapData;
8510 collectMapDataFromMapOperands(mapData, mapVars, moduleTranslation, DL,
8511 builder, useDevicePtrVars, useDeviceAddrVars);
8512
8513 // Fill up the arrays with all the mapped variables.
8514 MapInfosTy combinedInfo;
8515 auto genMapInfoCB = [&](InsertPointTy codeGenIP) -> MapInfosTy & {
8516 builder.restoreIP(codeGenIP);
8517 genMapInfos(builder, moduleTranslation, DL, combinedInfo, mapData,
8518 targetDirective);
8519 return combinedInfo;
8520 };
8521
8522 // Define a lambda to apply mappings between use_device_addr and
8523 // use_device_ptr base pointers, and their associated block arguments.
8524 auto mapUseDevice =
8525 [&moduleTranslation](
8526 llvm::OpenMPIRBuilder::DeviceInfoTy type,
8528 llvm::SmallVectorImpl<Value> &useDeviceVars, MapInfoData &mapInfoData,
8529 llvm::function_ref<llvm::Value *(llvm::Value *)> mapper = nullptr) {
8530 for (auto [arg, useDevVar] :
8531 llvm::zip_equal(blockArgs, useDeviceVars)) {
8532
8533 auto getMapBasePtr = [](omp::MapInfoOp mapInfoOp) {
8534 return mapInfoOp.getVarPtrPtr() ? mapInfoOp.getVarPtrPtr()
8535 : mapInfoOp.getVarPtr();
8536 };
8537
8538 auto useDevMap = cast<omp::MapInfoOp>(useDevVar.getDefiningOp());
8539 for (auto [mapClause, devicePointer, basePointer] : llvm::zip_equal(
8540 mapInfoData.MapClause, mapInfoData.DevicePointers,
8541 mapInfoData.BasePointers)) {
8542 auto mapOp = cast<omp::MapInfoOp>(mapClause);
8543 if (getMapBasePtr(mapOp) != getMapBasePtr(useDevMap) ||
8544 devicePointer != type)
8545 continue;
8546
8547 if (llvm::Value *devPtrInfoMap =
8548 mapper ? mapper(basePointer) : basePointer) {
8549 moduleTranslation.mapValue(arg, devPtrInfoMap);
8550 break;
8551 }
8552 }
8553 }
8554 };
8555
8556 using BodyGenTy = llvm::OpenMPIRBuilder::BodyGenTy;
8557 auto bodyGenCB = [&](InsertPointTy codeGenIP, BodyGenTy bodyGenType)
8558 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
8559 // We must always restoreIP regardless of doing anything the caller
8560 // does not restore it, leading to incorrect (no) branch generation.
8561 builder.restoreIP(codeGenIP);
8562 assert(isa<omp::TargetDataOp>(op) &&
8563 "BodyGen requested for non TargetDataOp");
8564 auto blockArgIface = cast<omp::BlockArgOpenMPOpInterface>(op);
8565 Region &region = cast<omp::TargetDataOp>(op).getRegion();
8566 switch (bodyGenType) {
8567 case BodyGenTy::Priv:
8568 // Check if any device ptr/addr info is available
8569 if (!info.DevicePtrInfoMap.empty()) {
8570 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Address,
8571 blockArgIface.getUseDeviceAddrBlockArgs(),
8572 useDeviceAddrVars, mapData,
8573 [&](llvm::Value *basePointer) -> llvm::Value * {
8574 if (!info.DevicePtrInfoMap[basePointer].second)
8575 return nullptr;
8576 return builder.CreateLoad(
8577 builder.getPtrTy(),
8578 info.DevicePtrInfoMap[basePointer].second);
8579 });
8580 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer,
8581 blockArgIface.getUseDevicePtrBlockArgs(), useDevicePtrVars,
8582 mapData, [&](llvm::Value *basePointer) {
8583 return info.DevicePtrInfoMap[basePointer].second;
8584 });
8585
8586 if (failed(inlineConvertOmpRegions(region, "omp.data.region", builder,
8587 moduleTranslation)))
8588 return llvm::make_error<PreviouslyReportedError>();
8589 }
8590 break;
8591 case BodyGenTy::DupNoPriv:
8592 if (info.DevicePtrInfoMap.empty()) {
8593 // For host device we still need to do the mapping for codegen,
8594 // otherwise it may try to lookup a missing value.
8595 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Address,
8596 blockArgIface.getUseDeviceAddrBlockArgs(),
8597 useDeviceAddrVars, mapData);
8598 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer,
8599 blockArgIface.getUseDevicePtrBlockArgs(), useDevicePtrVars,
8600 mapData);
8601 }
8602 break;
8603 case BodyGenTy::NoPriv:
8604 // If device info is available then region has already been generated
8605 if (info.DevicePtrInfoMap.empty()) {
8606 if (failed(inlineConvertOmpRegions(region, "omp.data.region", builder,
8607 moduleTranslation)))
8608 return llvm::make_error<PreviouslyReportedError>();
8609 }
8610 break;
8611 }
8612 return builder.saveIP();
8613 };
8614
8615 auto customMapperCB =
8616 [&](unsigned int i) -> llvm::Expected<llvm::Function *> {
8617 if (!combinedInfo.Mappers[i])
8618 return nullptr;
8619 info.HasMapper = true;
8620 return getOrCreateUserDefinedMapperFunc(combinedInfo.Mappers[i], builder,
8621 moduleTranslation, targetDirective);
8622 };
8623
8624 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
8626 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
8627 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
8628 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP = [&]() {
8629 if (isa<omp::TargetDataOp>(op))
8630 return ompBuilder->createTargetData(ompLoc, allocaIP, builder.saveIP(),
8631 deallocBlocks, deviceID, ifCond, info,
8632 genMapInfoCB, customMapperCB,
8633 /*MapperFunc=*/nullptr, bodyGenCB,
8634 /*DeviceAddrCB=*/nullptr);
8635 return ompBuilder->createTargetData(ompLoc, allocaIP, builder.saveIP(),
8636 deallocBlocks, deviceID, ifCond, info,
8637 genMapInfoCB, customMapperCB, &RTLFn);
8638 }();
8639
8640 if (failed(handleError(afterIP, *op)))
8641 return failure();
8642
8643 builder.restoreIP(*afterIP);
8644 return success();
8645}
8646
8647static LogicalResult
8648convertOmpDistribute(Operation &opInst, llvm::IRBuilderBase &builder,
8649 LLVM::ModuleTranslation &moduleTranslation) {
8650 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8651 auto distributeOp = cast<omp::DistributeOp>(opInst);
8652 if (failed(checkImplementationStatus(opInst)))
8653 return failure();
8654
8655 /// Process teams op reduction in distribute if the reduction is contained in
8656 /// this specific distribute op.
8657 omp::TeamsOp teamsOp = opInst.getParentOfType<omp::TeamsOp>();
8658 bool doDistributeReduction =
8659 teamsOp && getDistributeCapturingTeamsReduction(teamsOp) == distributeOp;
8660
8661 DenseMap<Value, llvm::Value *> reductionVariableMap;
8662 unsigned numReductionVars = teamsOp ? teamsOp.getNumReductionVars() : 0;
8664 SmallVector<llvm::Value *> privateReductionVariables(numReductionVars);
8665 llvm::ArrayRef<bool> isByRef;
8666
8667 if (doDistributeReduction) {
8668 isByRef = getIsByRef(teamsOp.getReductionByref());
8669 assert(isByRef.size() == teamsOp.getNumReductionVars());
8670
8671 collectReductionDecls(teamsOp, reductionDecls);
8672 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
8673 findAllocInsertPoints(builder, moduleTranslation);
8674
8675 MutableArrayRef<BlockArgument> reductionArgs =
8676 llvm::cast<omp::BlockArgOpenMPOpInterface>(*teamsOp)
8677 .getReductionBlockArgs();
8678
8680 teamsOp, reductionArgs, builder, moduleTranslation, allocaIP,
8681 reductionDecls, privateReductionVariables, reductionVariableMap,
8682 isByRef)))
8683 return failure();
8684 }
8685
8686 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
8687 auto bodyGenCB =
8688 [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
8689 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
8690 // Save the alloca insertion point on ModuleTranslation stack for use in
8691 // nested regions.
8693 moduleTranslation, allocaIP, deallocBlocks);
8694
8695 // DistributeOp has only one region associated with it.
8696 builder.restoreIP(codeGenIP);
8697 PrivateVarsInfo privVarsInfo(distributeOp);
8698
8700 distributeOp, builder, moduleTranslation, privVarsInfo, allocaIP);
8701 if (handleError(afterAllocas, opInst).failed())
8702 return llvm::make_error<PreviouslyReportedError>();
8703
8704 if (handleError(initPrivateVars(builder, moduleTranslation, privVarsInfo),
8705 opInst)
8706 .failed())
8707 return llvm::make_error<PreviouslyReportedError>();
8708
8709 if (failed(copyFirstPrivateVars(
8710 distributeOp, builder, moduleTranslation, privVarsInfo.mlirVars,
8711 privVarsInfo.llvmVars, privVarsInfo.privatizers,
8712 distributeOp.getPrivateNeedsBarrier())))
8713 return llvm::make_error<PreviouslyReportedError>();
8714
8715 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8716 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
8718 convertOmpOpRegions(distributeOp.getRegion(), "omp.distribute.region",
8719 builder, moduleTranslation);
8720 if (!regionBlock)
8721 return regionBlock.takeError();
8722 builder.SetInsertPoint(*regionBlock, (*regionBlock)->begin());
8723
8724 // Skip applying a workshare loop below when translating 'distribute
8725 // parallel do' (it's been already handled by this point while translating
8726 // the nested omp.wsloop).
8727 if (!isa_and_present<omp::WsloopOp>(distributeOp.getNestedWrapper())) {
8728 // TODO: Add support for clauses which are valid for DISTRIBUTE
8729 // constructs. Static schedule is the default.
8730 bool hasDistSchedule = distributeOp.getDistScheduleStatic();
8731 auto schedule = hasDistSchedule ? omp::ClauseScheduleKind::Distribute
8732 : omp::ClauseScheduleKind::Static;
8733 // dist_schedule clauses are ordered - otherise this should be false
8734 bool isOrdered = hasDistSchedule;
8735 std::optional<omp::ScheduleModifier> scheduleMod;
8736 bool isSimd = false;
8737 llvm::omp::WorksharingLoopType workshareLoopType =
8738 llvm::omp::WorksharingLoopType::DistributeStaticLoop;
8739 bool loopNeedsBarrier = false;
8740 llvm::Value *chunk = moduleTranslation.lookupValue(
8741 distributeOp.getDistScheduleChunkSize());
8742 llvm::CanonicalLoopInfo *loopInfo =
8743 findCurrentLoopInfo(moduleTranslation);
8744 llvm::OpenMPIRBuilder::InsertPointOrErrorTy wsloopIP =
8745 ompBuilder->applyWorkshareLoop(
8746 ompLoc.DL, loopInfo, allocaIP, loopNeedsBarrier,
8747 convertToScheduleKind(schedule), chunk, isSimd,
8748 scheduleMod == omp::ScheduleModifier::monotonic,
8749 scheduleMod == omp::ScheduleModifier::nonmonotonic, isOrdered,
8750 workshareLoopType, false, hasDistSchedule, chunk);
8751
8752 if (!wsloopIP)
8753 return wsloopIP.takeError();
8754 }
8755 if (failed(cleanupPrivateVars(distributeOp, builder, moduleTranslation,
8756 distributeOp.getLoc(), privVarsInfo)))
8757 return llvm::make_error<PreviouslyReportedError>();
8758
8759 return llvm::Error::success();
8760 };
8761
8763 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
8764 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
8765 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
8766 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
8767 ompBuilder->createDistribute(ompLoc, allocaIP, deallocBlocks, bodyGenCB);
8768
8769 if (failed(handleError(afterIP, opInst)))
8770 return failure();
8771
8772 builder.restoreIP(*afterIP);
8773
8774 if (doDistributeReduction) {
8775 // Process the reductions if required.
8777 teamsOp, builder, moduleTranslation, allocaIP, reductionDecls,
8778 privateReductionVariables, isByRef,
8779 /*isNoWait*/ false, /*isTeamsReduction*/ true);
8780 }
8781 return success();
8782}
8783
8784/// Lowers the FlagsAttr which is applied to the module when offloading. This
8785/// attribute contains OpenMP RTL globals that can be passed as flags to the
8786/// frontend, otherwise they are set to default
8787static LogicalResult
8788convertFlagsAttr(Operation *op, mlir::omp::FlagsAttr attribute,
8789 LLVM::ModuleTranslation &moduleTranslation) {
8790 auto offloadMod = dyn_cast<omp::OffloadModuleInterface>(op);
8791 if (!offloadMod)
8792 return op->emitOpError() << "omp flags attached to non offload module op";
8793
8794 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8795
8796 if (offloadMod.getIsTargetDevice())
8797 ompBuilder->M.addModuleFlag(llvm::Module::Max, "openmp-device",
8798 attribute.getOpenmpDeviceVersion());
8799
8800 // The flags below are only intended to be emitted for GPU offload targets.
8801 if (!offloadMod.getIsGPU())
8802 return success();
8803
8804 if (attribute.getNoGpuLib())
8805 return success();
8806
8807 ompBuilder->createGlobalFlag(attribute.getDebugKind(),
8808 "__omp_rtl_debug_kind");
8809 ompBuilder->createGlobalFlag(attribute.getAssumeTeamsOversubscription(),
8810 "__omp_rtl_assume_teams_oversubscription");
8811 ompBuilder->createGlobalFlag(attribute.getAssumeThreadsOversubscription(),
8812 "__omp_rtl_assume_threads_oversubscription");
8813 ompBuilder->createGlobalFlag(attribute.getAssumeNoThreadState(),
8814 "__omp_rtl_assume_no_thread_state");
8815 ompBuilder->createGlobalFlag(attribute.getAssumeNoNestedParallelism(),
8816 "__omp_rtl_assume_no_nested_parallelism");
8817 return success();
8818}
8819
8820static void getTargetEntryUniqueInfo(llvm::TargetRegionEntryInfo &targetInfo,
8821 omp::TargetOp targetOp,
8822 llvm::OpenMPIRBuilder &ompBuilder,
8823 llvm::vfs::FileSystem &vfs,
8824 llvm::StringRef parentName = "") {
8825 auto fileLoc = targetOp.getLoc()->findInstanceOf<FileLineColLoc>();
8826 assert(fileLoc && "No file found from location");
8827
8828 auto fileInfoCallBack = [&fileLoc]() {
8829 return std::pair<std::string, uint64_t>(
8830 llvm::StringRef(fileLoc.getFilename()), fileLoc.getLine());
8831 };
8832
8833 targetInfo =
8834 ompBuilder.getTargetEntryUniqueInfo(fileInfoCallBack, vfs, parentName);
8835}
8836
8837// The createDeviceArgumentAccessor function generates
8838// instructions for retrieving (acessing) kernel
8839// arguments inside of the device kernel for use by
8840// the kernel. This enables different semantics such as
8841// the creation of temporary copies of data allowing
8842// semantics like read-only/no host write back kernel
8843// arguments.
8844//
8845// This currently implements a very light version of Clang's
8846// EmitParmDecl's handling of direct argument handling as well
8847// as a portion of the argument access generation based on
8848// capture types found at the end of emitOutlinedFunctionPrologue
8849// in Clang. The indirect path handling of EmitParmDecl's may be
8850// required for future work, but a direct 1-to-1 copy doesn't seem
8851// possible as the logic is rather scattered throughout Clang's
8852// lowering and perhaps we wish to deviate slightly.
8853//
8854// \param mapData - A container containing vectors of information
8855// corresponding to the input argument, which should have a
8856// corresponding entry in the MapInfoData containers
8857// OrigialValue's.
8858// \param arg - This is the generated kernel function argument that
8859// corresponds to the passed in input argument. We generated different
8860// accesses of this Argument, based on capture type and other Input
8861// related information.
8862// \param input - This is the host side value that will be passed to
8863// the kernel i.e. the kernel input, we rewrite all uses of this within
8864// the kernel (as we generate the kernel body based on the target's region
8865// which maintians references to the original input) to the retVal argument
8866// apon exit of this function inside of the OMPIRBuilder. This interlinks
8867// the kernel argument to future uses of it in the function providing
8868// appropriate "glue" instructions inbetween.
8869// \param retVal - This is the value that all uses of input inside of the
8870// kernel will be re-written to, the goal of this function is to generate
8871// an appropriate location for the kernel argument to be accessed from,
8872// e.g. ByRef will result in a temporary allocation location and then
8873// a store of the kernel argument into this allocated memory which
8874// will then be loaded from, ByCopy will use the allocated memory
8875// directly.
8876static llvm::IRBuilderBase::InsertPoint createDeviceArgumentAccessor(
8877 omp::TargetOp targetOp, MapInfoData &mapData, llvm::Argument &arg,
8878 llvm::Value *input, llvm::Value *&retVal, llvm::IRBuilderBase &builder,
8879 llvm::OpenMPIRBuilder &ompBuilder,
8880 LLVM::ModuleTranslation &moduleTranslation,
8881 llvm::IRBuilderBase::InsertPoint allocaIP,
8882 llvm::IRBuilderBase::InsertPoint codeGenIP,
8884 assert(ompBuilder.Config.isTargetDevice() &&
8885 "function only supported for target device codegen");
8886 builder.restoreIP(allocaIP);
8887
8888 omp::VariableCaptureKind capture = omp::VariableCaptureKind::ByRef;
8889 LLVM::TypeToLLVMIRTranslator typeToLLVMIRTranslator(
8890 ompBuilder.M.getContext());
8891 unsigned alignmentValue = 0;
8892 BlockArgument mlirArg;
8894 cast<omp::BlockArgOpenMPOpInterface>(*targetOp).getBlockArgsPairs(
8895 blockArgsPairs);
8896 // Find the associated MapInfoData entry for the current input
8897 for (size_t i = 0; i < mapData.MapClause.size(); ++i) {
8898 if (mapData.OriginalValue[i] == input) {
8899 auto mapOp = cast<omp::MapInfoOp>(mapData.MapClause[i]);
8900 capture = mapOp.getMapCaptureType();
8901 // Get information of alignment of mapped object
8902 alignmentValue = typeToLLVMIRTranslator.getPreferredAlignment(
8903 mapOp.getVarPtrType(), ompBuilder.M.getDataLayout());
8904
8905 // Find the corresponding entry block argument, which can be associated to
8906 // a map, use_device* or has_device* clause.
8907 for (auto &[val, arg] : blockArgsPairs) {
8908 if (mapOp.getResult() == val) {
8909 mlirArg = arg;
8910 break;
8911 }
8912 }
8913 assert(mlirArg && "expected to find entry block argument for map clause");
8914 break;
8915 }
8916 }
8917
8918 unsigned int allocaAS = ompBuilder.M.getDataLayout().getAllocaAddrSpace();
8919 unsigned int defaultAS =
8920 ompBuilder.M.getDataLayout().getProgramAddressSpace();
8921
8922 // Create the allocation for the argument.
8923 llvm::Value *v = nullptr;
8924 if (omp::opInSharedDeviceContext(*targetOp) &&
8926 // Use the beginning of the codeGenIP rather than the usual allocation point
8927 // for shared memory allocations because otherwise these would be done prior
8928 // to the target initialization call. Also, the exit block (where the
8929 // deallocation is placed) is only executed if the initialization call
8930 // succeeds.
8931 builder.SetInsertPoint(codeGenIP.getBlock()->getFirstInsertionPt());
8932 v = ompBuilder.createOMPAllocShared(builder, arg.getType());
8933
8934 // Create deallocations in all provided deallocation points and then restore
8935 // the insertion point to right after the new allocations.
8936 llvm::IRBuilderBase::InsertPointGuard guard(builder);
8937 for (auto deallocIP : deallocIPs) {
8938 builder.SetInsertPoint(deallocIP.getBlock(), deallocIP.getPoint());
8939 ompBuilder.createOMPFreeShared(builder, v, arg.getType());
8940 }
8941 } else {
8942 // Use the current point, which was previously set to allocaIP.
8943 v = builder.CreateAlloca(arg.getType(), allocaAS);
8944
8945 if (allocaAS != defaultAS && arg.getType()->isPointerTy())
8946 v = builder.CreateAddrSpaceCast(v, builder.getPtrTy(defaultAS));
8947 }
8948
8949 builder.CreateStore(&arg, v);
8950
8951 builder.restoreIP(codeGenIP);
8952
8953 switch (capture) {
8954 case omp::VariableCaptureKind::ByCopy: {
8955 retVal = v;
8956 break;
8957 }
8958 case omp::VariableCaptureKind::ByRef: {
8959 llvm::LoadInst *loadInst = builder.CreateAlignedLoad(
8960 v->getType(), v,
8961 ompBuilder.M.getDataLayout().getPrefTypeAlign(v->getType()));
8962 // CreateAlignedLoad function creates similar LLVM IR:
8963 // %res = load ptr, ptr %input, align 8
8964 // This LLVM IR does not contain information about alignment
8965 // of the loaded value. We need to add !align metadata to unblock
8966 // optimizer. The existence of the !align metadata on the instruction
8967 // tells the optimizer that the value loaded is known to be aligned to
8968 // a boundary specified by the integer value in the metadata node.
8969 // Example:
8970 // %res = load ptr, ptr %input, align 8, !align !align_md_node
8971 // ^ ^
8972 // | |
8973 // alignment of %input address |
8974 // |
8975 // alignment of %res object
8976 if (v->getType()->isPointerTy() && alignmentValue) {
8977 llvm::MDBuilder MDB(builder.getContext());
8978 loadInst->setMetadata(
8979 llvm::LLVMContext::MD_align,
8980 llvm::MDNode::get(builder.getContext(),
8981 MDB.createConstant(llvm::ConstantInt::get(
8982 llvm::Type::getInt64Ty(builder.getContext()),
8983 alignmentValue))));
8984 }
8985 retVal = loadInst;
8986
8987 break;
8988 }
8989 case omp::VariableCaptureKind::This:
8990 case omp::VariableCaptureKind::VLAType:
8991 // TODO: Consider returning error to use standard reporting for
8992 // unimplemented features.
8993 assert(false && "Currently unsupported capture kind");
8994 break;
8995 }
8996
8997 return builder.saveIP();
8998}
8999
9000/// Follow uses of `host_eval`-defined block arguments of the given `omp.target`
9001/// operation and populate output variables with their corresponding host value
9002/// (i.e. operand evaluated outside of the target region), based on their uses
9003/// inside of the target region.
9004///
9005/// Loop bounds and steps are only optionally populated, if output vectors are
9006/// provided.
9007static void
9008extractHostEvalClauses(omp::TargetOp targetOp, Value &numThreads,
9009 Value &numTeamsLower, Value &numTeamsUpper,
9010 Value &threadLimit,
9011 llvm::SmallVectorImpl<Value> *lowerBounds = nullptr,
9012 llvm::SmallVectorImpl<Value> *upperBounds = nullptr,
9013 llvm::SmallVectorImpl<Value> *steps = nullptr) {
9014 auto blockArgIface = llvm::cast<omp::BlockArgOpenMPOpInterface>(*targetOp);
9015 for (auto item : llvm::zip_equal(targetOp.getHostEvalVars(),
9016 blockArgIface.getHostEvalBlockArgs())) {
9017 Value hostEvalVar = std::get<0>(item), blockArg = std::get<1>(item);
9018
9019 for (Operation *user : blockArg.getUsers()) {
9021 .Case([&](omp::TeamsOp teamsOp) {
9022 if (teamsOp.getNumTeamsLower() == blockArg)
9023 numTeamsLower = hostEvalVar;
9024 else if (llvm::is_contained(teamsOp.getNumTeamsUpperVars(),
9025 blockArg))
9026 numTeamsUpper = hostEvalVar;
9027 else if (!teamsOp.getThreadLimitVars().empty() &&
9028 teamsOp.getThreadLimit(0) == blockArg)
9029 threadLimit = hostEvalVar;
9030 else
9031 llvm_unreachable("unsupported host_eval use");
9032 })
9033 .Case([&](omp::ParallelOp parallelOp) {
9034 if (!parallelOp.getNumThreadsVars().empty() &&
9035 parallelOp.getNumThreads(0) == blockArg)
9036 numThreads = hostEvalVar;
9037 else
9038 llvm_unreachable("unsupported host_eval use");
9039 })
9040 .Case([&](omp::LoopNestOp loopOp) {
9041 auto processBounds =
9042 [&](OperandRange opBounds,
9043 llvm::SmallVectorImpl<Value> *outBounds) -> bool {
9044 bool found = false;
9045 for (auto [i, lb] : llvm::enumerate(opBounds)) {
9046 if (lb == blockArg) {
9047 found = true;
9048 if (outBounds)
9049 (*outBounds)[i] = hostEvalVar;
9050 }
9051 }
9052 return found;
9053 };
9054 bool found =
9055 processBounds(loopOp.getLoopLowerBounds(), lowerBounds);
9056 found = processBounds(loopOp.getLoopUpperBounds(), upperBounds) ||
9057 found;
9058 found = processBounds(loopOp.getLoopSteps(), steps) || found;
9059 (void)found;
9060 assert(found && "unsupported host_eval use");
9061 })
9062 .DefaultUnreachable("unsupported host_eval use");
9063 }
9064 }
9065}
9066
9067/// If \p op is of the given type parameter, return it casted to that type.
9068/// Otherwise, if its immediate parent operation (or some other higher-level
9069/// parent, if \p immediateParent is false) is of that type, return that parent
9070/// casted to the given type.
9071///
9072/// If \p op is \c null or neither it or its parent(s) are of the specified
9073/// type, return a \c null operation.
9074template <typename OpTy>
9075static OpTy castOrGetParentOfType(Operation *op, bool immediateParent = false) {
9076 if (!op)
9077 return OpTy();
9078
9079 if (OpTy casted = dyn_cast<OpTy>(op))
9080 return casted;
9081
9082 if (immediateParent)
9083 return dyn_cast_if_present<OpTy>(op->getParentOp());
9084
9085 return op->getParentOfType<OpTy>();
9086}
9087
9088/// If the given \p value is defined by an \c llvm.mlir.constant operation and
9089/// it is of an integer type, return its value.
9090static std::optional<int64_t> extractConstInteger(Value value) {
9091 if (!value)
9092 return std::nullopt;
9093
9094 if (auto constOp = value.getDefiningOp<LLVM::ConstantOp>())
9095 if (auto constAttr = dyn_cast<IntegerAttr>(constOp.getValue()))
9096 return constAttr.getInt();
9097
9098 return std::nullopt;
9099}
9100
9101static uint64_t getTypeByteSize(mlir::Type type, const DataLayout &dl) {
9102 uint64_t sizeInBits = dl.getTypeSizeInBits(type);
9103 uint64_t sizeInBytes = sizeInBits / 8;
9104 return sizeInBytes;
9105}
9106
9107template <typename OpTy>
9108static uint64_t getReductionDataSize(OpTy &op) {
9109 if (op.getNumReductionVars() > 0) {
9111 collectReductionDecls(op, reductions);
9112
9114 members.reserve(reductions.size());
9115 for (omp::DeclareReductionOp &red : reductions) {
9116 // For by-ref reductions, use the actual element type rather than the
9117 // pointer type so that the buffer size matches the access pattern in
9118 // the copy/reduce callbacks generated by OMPIRBuilder.
9119 if (red.getByrefElementType())
9120 members.push_back(*red.getByrefElementType());
9121 else
9122 members.push_back(red.getType());
9123 }
9124 Operation *opp = op.getOperation();
9125 auto structType = mlir::LLVM::LLVMStructType::getLiteral(
9126 opp->getContext(), members, /*isPacked=*/false);
9127 DataLayout dl = DataLayout(opp->getParentOfType<ModuleOp>());
9128 return getTypeByteSize(structType, dl);
9129 }
9130 return 0;
9131}
9132
9133/// Populate default `MinTeams`, `MaxTeams` and `MaxThreads` to their default
9134/// values as stated by the corresponding clauses, if constant.
9135///
9136/// These default values must be set before the creation of the outlined LLVM
9137/// function for the target region, so that they can be used to initialize the
9138/// corresponding global `ConfigurationEnvironmentTy` structure.
9139static void
9140initTargetDefaultAttrs(omp::TargetOp targetOp, Operation *capturedOp,
9141 llvm::OpenMPIRBuilder::TargetKernelDefaultAttrs &attrs,
9142 bool isTargetDevice, bool isGPU) {
9143 // TODO: Handle constant 'if' clauses.
9144
9145 Value numThreads, numTeamsLower, numTeamsUpper, threadLimit;
9146 if (!isTargetDevice) {
9147 extractHostEvalClauses(targetOp, numThreads, numTeamsLower, numTeamsUpper,
9148 threadLimit);
9149 } else {
9150 // In the target device, values for these clauses are not passed as
9151 // host_eval, but instead evaluated prior to entry to the region. This
9152 // ensures values are mapped and available inside of the target region.
9153 if (auto teamsOp = castOrGetParentOfType<omp::TeamsOp>(capturedOp)) {
9154 numTeamsLower = teamsOp.getNumTeamsLower();
9155 // Handle num_teams upper bounds (only first value for now)
9156 if (!teamsOp.getNumTeamsUpperVars().empty())
9157 numTeamsUpper = teamsOp.getNumTeams(0);
9158 if (!teamsOp.getThreadLimitVars().empty())
9159 threadLimit = teamsOp.getThreadLimit(0);
9160 }
9161
9162 if (auto parallelOp = castOrGetParentOfType<omp::ParallelOp>(capturedOp)) {
9163 if (!parallelOp.getNumThreadsVars().empty())
9164 numThreads = parallelOp.getNumThreads(0);
9165 }
9166 }
9167
9168 // Handle clauses impacting the number of teams.
9169
9170 int32_t minTeamsVal = 1, maxTeamsVal = -1;
9171 if (castOrGetParentOfType<omp::TeamsOp>(capturedOp)) {
9172 // TODO: Use `hostNumTeamsLower` to initialize `minTeamsVal`. For now,
9173 // match clang and set min and max to the same value.
9174 if (numTeamsUpper) {
9175 if (auto val = extractConstInteger(numTeamsUpper))
9176 minTeamsVal = maxTeamsVal = *val;
9177 } else {
9178 minTeamsVal = maxTeamsVal = 0;
9179 }
9180 } else if (castOrGetParentOfType<omp::ParallelOp>(capturedOp,
9181 /*immediateParent=*/true) ||
9183 /*immediateParent=*/true)) {
9184 minTeamsVal = maxTeamsVal = 1;
9185 } else {
9186 minTeamsVal = maxTeamsVal = -1;
9187 }
9188
9189 // Handle clauses impacting the number of threads.
9190
9191 auto setMaxValueFromClause = [](Value clauseValue, int32_t &result) {
9192 if (!clauseValue)
9193 return;
9194
9195 if (auto val = extractConstInteger(clauseValue))
9196 result = *val;
9197
9198 // Found an applicable clause, so it's not undefined. Mark as unknown
9199 // because it's not constant.
9200 if (result < 0)
9201 result = 0;
9202 };
9203
9204 // Extract 'thread_limit' clause from 'target' and 'teams' directives.
9205 int32_t targetThreadLimitVal = -1, teamsThreadLimitVal = -1;
9206 if (!targetOp.getThreadLimitVars().empty())
9207 setMaxValueFromClause(targetOp.getThreadLimit(0), targetThreadLimitVal);
9208 setMaxValueFromClause(threadLimit, teamsThreadLimitVal);
9209
9210 // Extract 'max_threads' clause from 'parallel' or set to 1 if it's SIMD.
9211 int32_t maxThreadsVal = -1;
9213 setMaxValueFromClause(numThreads, maxThreadsVal);
9214 else if (castOrGetParentOfType<omp::SimdOp>(capturedOp,
9215 /*immediateParent=*/true))
9216 maxThreadsVal = 1;
9217
9218 // For max values, < 0 means unset, == 0 means set but unknown. Select the
9219 // minimum value between 'max_threads' and 'thread_limit' clauses that were
9220 // set.
9221 int32_t combinedMaxThreadsVal = targetThreadLimitVal;
9222 if (combinedMaxThreadsVal < 0 ||
9223 (teamsThreadLimitVal >= 0 && teamsThreadLimitVal < combinedMaxThreadsVal))
9224 combinedMaxThreadsVal = teamsThreadLimitVal;
9225
9226 if (combinedMaxThreadsVal < 0 ||
9227 (maxThreadsVal >= 0 && maxThreadsVal < combinedMaxThreadsVal))
9228 combinedMaxThreadsVal = maxThreadsVal;
9229
9230 int32_t reductionDataSize = 0;
9231 if (isGPU && capturedOp) {
9232 if (auto teamsOp = castOrGetParentOfType<omp::TeamsOp>(capturedOp))
9233 reductionDataSize = getReductionDataSize(teamsOp);
9234 }
9235
9236 // Update kernel bounds structure for the `OpenMPIRBuilder` to use.
9237 // Use the kernel_type attribute set by the frontend instead of analyzing IR.
9238 omp::TargetExecMode execMode = targetOp.getKernelType();
9239 switch (execMode) {
9240 case omp::TargetExecMode::bare:
9241 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_BARE;
9242 break;
9243 case omp::TargetExecMode::generic:
9244 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_GENERIC;
9245 break;
9246 case omp::TargetExecMode::spmd:
9247 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_SPMD;
9248 break;
9249 case omp::TargetExecMode::spmd_no_loop:
9250 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_SPMD_NO_LOOP;
9251 break;
9252 }
9253 attrs.MinTeams.front() = minTeamsVal;
9254 attrs.MaxTeams.front() = maxTeamsVal;
9255 attrs.MinThreads.front() = 1;
9256 attrs.MaxThreads.front() = combinedMaxThreadsVal;
9257 attrs.ReductionDataSize = reductionDataSize;
9258}
9259
9260/// Gather LLVM runtime values for all clauses evaluated in the host that are
9261/// passed to the kernel invocation.
9262///
9263/// This function must be called only when compiling for the host. Also, it will
9264/// only provide correct results if it's called after the body of \c targetOp
9265/// has been fully generated.
9266static void
9267initTargetRuntimeAttrs(llvm::IRBuilderBase &builder,
9268 LLVM::ModuleTranslation &moduleTranslation,
9269 omp::TargetOp targetOp, Operation *capturedOp,
9270 llvm::OpenMPIRBuilder::TargetKernelRuntimeAttrs &attrs) {
9271 omp::LoopNestOp loopOp = castOrGetParentOfType<omp::LoopNestOp>(capturedOp);
9272 unsigned numLoops = loopOp ? loopOp.getNumLoops() : 0;
9273
9274 Value numThreads, numTeamsLower, numTeamsUpper, teamsThreadLimit;
9275 llvm::SmallVector<Value> lowerBounds(numLoops), upperBounds(numLoops),
9276 steps(numLoops);
9277 extractHostEvalClauses(targetOp, numThreads, numTeamsLower, numTeamsUpper,
9278 teamsThreadLimit, &lowerBounds, &upperBounds, &steps);
9279
9280 // TODO: Handle constant 'if' clauses.
9281 if (!targetOp.getThreadLimitVars().empty()) {
9282 Value targetThreadLimit = targetOp.getThreadLimit(0);
9283 attrs.TargetThreadLimit.front() =
9284 moduleTranslation.lookupValue(targetThreadLimit);
9285 }
9286
9287 // The __kmpc_push_num_teams_51 function expects int32 as the arguments. So,
9288 // truncate or sign extend lower and upper num_teams bounds as well as
9289 // thread_limit to match int32 ABI requirements for the OpenMP runtime.
9290 if (numTeamsLower)
9291 attrs.MinTeams.front() = builder.CreateSExtOrTrunc(
9292 moduleTranslation.lookupValue(numTeamsLower), builder.getInt32Ty());
9293
9294 if (numTeamsUpper)
9295 attrs.MaxTeams.front() = builder.CreateSExtOrTrunc(
9296 moduleTranslation.lookupValue(numTeamsUpper), builder.getInt32Ty());
9297
9298 if (teamsThreadLimit)
9299 attrs.TeamsThreadLimit.front() = builder.CreateSExtOrTrunc(
9300 moduleTranslation.lookupValue(teamsThreadLimit), builder.getInt32Ty());
9301
9302 if (numThreads)
9303 attrs.MaxThreads.front() = moduleTranslation.lookupValue(numThreads);
9304
9305 if (targetOp.hasHostEvalTripCount()) {
9306 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9307 attrs.LoopTripCount = nullptr;
9308
9309 // To calculate the trip count, we multiply together the trip counts of
9310 // every collapsed canonical loop. We don't need to create the loop nests
9311 // here, since we're only interested in the trip count.
9312 for (auto [loopLower, loopUpper, loopStep] :
9313 llvm::zip_equal(lowerBounds, upperBounds, steps)) {
9314 llvm::Value *lowerBound = moduleTranslation.lookupValue(loopLower);
9315 llvm::Value *upperBound = moduleTranslation.lookupValue(loopUpper);
9316 llvm::Value *step = moduleTranslation.lookupValue(loopStep);
9317
9318 if (!lowerBound || !upperBound || !step) {
9319 attrs.LoopTripCount = nullptr;
9320 break;
9321 }
9322
9323 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
9324 llvm::Value *tripCount = ompBuilder->calculateCanonicalLoopTripCount(
9325 loc, lowerBound, upperBound, step, /*IsSigned=*/true,
9326 loopOp.getLoopInclusive());
9327
9328 if (!attrs.LoopTripCount) {
9329 attrs.LoopTripCount = tripCount;
9330 continue;
9331 }
9332
9333 // TODO: Enable UndefinedSanitizer to diagnose an overflow here.
9334 attrs.LoopTripCount = builder.CreateMul(attrs.LoopTripCount, tripCount,
9335 {}, /*HasNUW=*/true);
9336 }
9337 }
9338
9339 attrs.DeviceID = builder.getInt64(llvm::omp::OMP_DEVICEID_UNDEF);
9340 if (mlir::Value devId = targetOp.getDevice()) {
9341 attrs.DeviceID = moduleTranslation.lookupValue(devId);
9342 attrs.DeviceID =
9343 builder.CreateSExtOrTrunc(attrs.DeviceID, builder.getInt64Ty());
9344 }
9345}
9346
9347static llvm::omp::OMPDynGroupprivateFallbackType
9348getDynGroupprivateFallbackType(omp::FallbackModifierAttr fallbackAttr) {
9349 omp::FallbackModifier fb = fallbackAttr ? fallbackAttr.getValue()
9350 : omp::FallbackModifier::default_mem;
9351 switch (fb) {
9352 case omp::FallbackModifier::abort:
9353 return llvm::omp::OMPDynGroupprivateFallbackType::Abort;
9354 case omp::FallbackModifier::null:
9355 return llvm::omp::OMPDynGroupprivateFallbackType::Null;
9356 case omp::FallbackModifier::default_mem:
9357 return llvm::omp::OMPDynGroupprivateFallbackType::DefaultMem;
9358 }
9359
9360 llvm_unreachable("unexpected dyn_groupprivate fallback type");
9361}
9362
9363static LogicalResult
9364convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
9365 LLVM::ModuleTranslation &moduleTranslation) {
9366 auto targetOp = cast<omp::TargetOp>(opInst);
9367
9368 // The current debug location already has the DISubprogram for the outlined
9369 // function that will be created for the target op. We save it here so that
9370 // we can set it on the outlined function.
9371 llvm::DebugLoc outlinedFnLoc = builder.getCurrentDebugLocation();
9372 if (failed(checkImplementationStatus(opInst)))
9373 return failure();
9374
9375 // During the handling of target op, we will generate instructions in the
9376 // parent function like call to the oulined function or branch to a new
9377 // BasicBlock. We set the debug location here to parent function so that those
9378 // get the correct debug locations. For outlined functions, the normal MLIR op
9379 // conversion will automatically pick the correct location.
9380 llvm::BasicBlock *parentBB = builder.GetInsertBlock();
9381 assert(parentBB && "No insert block is set for the builder");
9382 llvm::Function *parentLLVMFn = parentBB->getParent();
9383 assert(parentLLVMFn && "Parent Function must be valid");
9384 if (llvm::DISubprogram *SP = parentLLVMFn->getSubprogram())
9385 builder.SetCurrentDebugLocation(llvm::DILocation::get(
9386 parentLLVMFn->getContext(), outlinedFnLoc.getLine(),
9387 outlinedFnLoc.getCol(), SP, outlinedFnLoc.getInlinedAt()));
9388
9389 // OMPIRBuilder emits runtime calls into the outlined function before bodyCB
9390 // below gets a chance to attach the subprogram to it, so it needs the
9391 // outlined function's location handed to it separately. Only pass it under
9392 // the same condition that decides whether the subprogram is attached at all:
9393 // a location may not be attached to an instruction in a function that has no
9394 // subprogram.
9395 llvm::DebugLoc outlinedFnDbgLoc;
9396 if (outlinedFnLoc && parentLLVMFn->getSubprogram())
9397 outlinedFnDbgLoc = outlinedFnLoc;
9398
9399 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9400 bool isTargetDevice = ompBuilder->Config.isTargetDevice();
9401 bool isGPU = ompBuilder->Config.isGPU();
9402
9403 auto parentFn = opInst.getParentOfType<LLVM::LLVMFuncOp>();
9404 auto argIface = cast<omp::BlockArgOpenMPOpInterface>(opInst);
9405 auto &targetRegion = targetOp.getRegion();
9406 // Holds the private vars that have been mapped along with the block
9407 // argument that corresponds to the MapInfoOp corresponding to the private
9408 // var in question. So, for instance:
9409 //
9410 // %10 = omp.map.info var_ptr(%6#0 : !fir.ref<!fir.box<!fir.heap<i32>>>, ..)
9411 // omp.target map_entries(%10 -> %arg0) private(@box.privatizer %6#0-> %arg1)
9412 //
9413 // Then, %10 has been created so that the descriptor can be used by the
9414 // privatizer @box.privatizer on the device side. Here we'd record {%6#0,
9415 // %arg0} in the mappedPrivateVars map.
9416 llvm::DenseMap<Value, Value> mappedPrivateVars;
9417 DataLayout dl = DataLayout(opInst.getParentOfType<ModuleOp>());
9418 SmallVector<Value> mapVars = targetOp.getMapVars();
9419 SmallVector<Value> hdaVars = targetOp.getHasDeviceAddrVars();
9420 ArrayRef<BlockArgument> mapBlockArgs = argIface.getMapBlockArgs();
9421 ArrayRef<BlockArgument> hdaBlockArgs = argIface.getHasDeviceAddrBlockArgs();
9422 llvm::Function *llvmOutlinedFn = nullptr;
9423 TargetDirectiveEnumTy targetDirective =
9424 getTargetDirectiveEnumTyFromOp(&opInst);
9425
9426 // TODO: It can also be false if a compile-time constant `false` IF clause is
9427 // specified.
9428 bool isOffloadEntry =
9429 isTargetDevice || !ompBuilder->Config.TargetTriples.empty();
9430
9431 // Resolve in_reduction clauses on omp.target for the host. From the target
9432 // device's perspective an in_reduction list item behaves as a regular
9433 // map(tofrom) variable, so no special handling is needed there; only the
9434 // host redirects the mapped value to the per-task reduction-private storage
9435 // returned by __kmpc_task_reduction_get_th_data (emitted inside the
9436 // to-be-outlined target task body). This applies to both offloading and
9437 // non-offloading host modules.
9438 //
9439 // The target body has no dedicated in_reduction block argument: each
9440 // in_reduction variable is accessed through its map_entries block argument.
9441 // So each in_reduction variable must also be captured by a matching
9442 // map_entries entry (guaranteed by the verifier); without one the outlined
9443 // body would reference a value defined in the host function. Record, for each
9444 // in_reduction variable, the position of that map entry so the corresponding
9445 // map block argument can be redirected inside the body. The in_reduction
9446 // operand itself is used as the `orig` argument of the runtime lookup.
9447 SmallVector<llvm::Value *> inRedOrigPtrs;
9448 SmallVector<unsigned> inRedMapArgIdx;
9449 if (!targetOp.getInReductionVars().empty() && !isTargetDevice) {
9450 inRedOrigPtrs.reserve(targetOp.getInReductionVars().size());
9451 inRedMapArgIdx.reserve(targetOp.getInReductionVars().size());
9452 for (Value v : targetOp.getInReductionVars()) {
9453 // Select the map_entries entry that captures this in_reduction operand.
9454 // The verifier guarantees at least one match exists; more than one
9455 // matching entry is a lowering ambiguity (the redirect cannot pick which
9456 // map argument to rebind).
9457 std::optional<unsigned> matchIdx;
9458 for (auto [idx, mapV] : llvm::enumerate(targetOp.getMapVars())) {
9459 auto mapInfo = mapV.getDefiningOp<omp::MapInfoOp>();
9460 if (v != mapInfo.getVarPtr())
9461 continue;
9462 if (matchIdx)
9463 return targetOp.emitError()
9464 << "in_reduction variable on omp.target has multiple matching "
9465 "map_entries entries; the redirect target is ambiguous";
9466 matchIdx = idx;
9467 }
9468 // The verifier requires a capturing map entry for every in_reduction
9469 // operand, so a match must exist here.
9470 assert(matchIdx &&
9471 "TargetOp verifier guarantees a matching map_entries entry for "
9472 "each in_reduction variable");
9473 inRedMapArgIdx.push_back(*matchIdx);
9474 // The runtime `orig` pointer is the in_reduction operand itself, the
9475 // reduction variable the enclosing taskgroup registered.
9476 inRedOrigPtrs.push_back(moduleTranslation.lookupValue(v));
9477 }
9478 }
9479
9480 // For some private variables, the MapsForPrivatizedVariablesPass
9481 // creates MapInfoOp instances. Go through the private variables and
9482 // the mapped variables so that during codegeneration we are able
9483 // to quickly look up the corresponding map variable, if any for each
9484 // private variable.
9485 if (!targetOp.getPrivateVars().empty() && !targetOp.getMapVars().empty()) {
9486 OperandRange privateVars = targetOp.getPrivateVars();
9487 std::optional<ArrayAttr> privateSyms = targetOp.getPrivateSyms();
9488 std::optional<DenseI64ArrayAttr> privateMapIndices =
9489 targetOp.getPrivateMapsAttr();
9490
9491 for (auto [privVarIdx, privVarSymPair] :
9492 llvm::enumerate(llvm::zip_equal(privateVars, *privateSyms))) {
9493 auto privVar = std::get<0>(privVarSymPair);
9494 auto privSym = std::get<1>(privVarSymPair);
9495
9496 SymbolRefAttr privatizerName = llvm::cast<SymbolRefAttr>(privSym);
9497 omp::PrivateClauseOp privatizer =
9498 findPrivatizer(targetOp, privatizerName);
9499
9500 if (!privatizer.needsMap())
9501 continue;
9502
9503 mlir::Value mappedValue =
9504 targetOp.getMappedValueForPrivateVar(privVarIdx);
9505 assert(mappedValue && "Expected to find mapped value for a privatized "
9506 "variable that needs mapping");
9507
9508 // The MapInfoOp defining the map var isn't really needed later.
9509 // So, we don't store it in any datastructure. Instead, we just
9510 // do some sanity checks on it right now.
9511 auto mapInfoOp = mappedValue.getDefiningOp<omp::MapInfoOp>();
9512 [[maybe_unused]] Type varType = mapInfoOp.getVarPtrType();
9513
9514 // Check #1: Check that the type of the private variable matches
9515 // the type of the variable being mapped.
9516 if (!isa<LLVM::LLVMPointerType>(privVar.getType()))
9517 assert(
9518 varType == privVar.getType() &&
9519 "Type of private var doesn't match the type of the mapped value");
9520
9521 // Ok, only 1 sanity check for now.
9522 // Record the block argument corresponding to this mapvar.
9523 mappedPrivateVars.insert(
9524 {privVar,
9525 targetRegion.getArgument(argIface.getMapBlockArgsStart() +
9526 (*privateMapIndices)[privVarIdx])});
9527 }
9528 }
9529
9530 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
9531 auto bodyCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
9532 ArrayRef<llvm::BasicBlock *> deallocBlocks)
9533 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
9534 llvm::IRBuilderBase::InsertPointGuard guard(builder);
9535 builder.SetCurrentDebugLocation(llvm::DebugLoc());
9536 // Forward target-cpu and target-features function attributes from the
9537 // original function to the new outlined function.
9538 llvm::Function *llvmParentFn =
9539 moduleTranslation.lookupFunction(parentFn.getName());
9540 llvmOutlinedFn = codeGenIP.getBlock()->getParent();
9541 assert(llvmParentFn && llvmOutlinedFn &&
9542 "Both parent and outlined functions must exist at this point");
9543
9544 if (outlinedFnLoc && llvmParentFn->getSubprogram())
9545 llvmOutlinedFn->setSubprogram(outlinedFnLoc->getScope()->getSubprogram());
9546
9547 if (auto attr = llvmParentFn->getFnAttribute("target-cpu");
9548 attr.isStringAttribute())
9549 llvmOutlinedFn->addFnAttr(attr);
9550
9551 if (auto attr = llvmParentFn->getFnAttribute("target-features");
9552 attr.isStringAttribute())
9553 llvmOutlinedFn->addFnAttr(attr);
9554
9555 for (auto [idx, arg] : llvm::enumerate(mapBlockArgs)) {
9556 // in_reduction list items on omp.target are accessed through their
9557 // map_entries block argument, which is redirected below to the per-task
9558 // reduction-private storage returned by the runtime. Skip the default
9559 // host-value mapping for those block arguments so the write-once
9560 // mapValue mapping is free to be set to the private pointer.
9561 if (llvm::is_contained(inRedMapArgIdx, idx))
9562 continue;
9563 auto mapInfoOp = cast<omp::MapInfoOp>(mapVars[idx].getDefiningOp());
9564 llvm::Value *mapOpValue =
9565 moduleTranslation.lookupValue(mapInfoOp.getVarPtr());
9566 moduleTranslation.mapValue(arg, mapOpValue);
9567 }
9568 for (auto [arg, mapOp] : llvm::zip_equal(hdaBlockArgs, hdaVars)) {
9569 auto mapInfoOp = cast<omp::MapInfoOp>(mapOp.getDefiningOp());
9570 llvm::Value *mapOpValue =
9571 moduleTranslation.lookupValue(mapInfoOp.getVarPtr());
9572 moduleTranslation.mapValue(arg, mapOpValue);
9573 }
9574
9575 // Do privatization after moduleTranslation has already recorded
9576 // mapped values.
9577 PrivateVarsInfo privateVarsInfo(targetOp);
9578
9580 allocatePrivateVars(targetOp, builder, moduleTranslation,
9581 privateVarsInfo, allocaIP, &mappedPrivateVars);
9582
9583 if (failed(handleError(afterAllocas, *targetOp)))
9584 return llvm::make_error<PreviouslyReportedError>();
9585
9586 builder.restoreIP(codeGenIP);
9587 if (handleError(initPrivateVars(builder, moduleTranslation, privateVarsInfo,
9588 &mappedPrivateVars),
9589 *targetOp)
9590 .failed())
9591 return llvm::make_error<PreviouslyReportedError>();
9592
9593 if (failed(copyFirstPrivateVars(
9594 targetOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
9595 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
9596 targetOp.getPrivateNeedsBarrier(), &mappedPrivateVars)))
9597 return llvm::make_error<PreviouslyReportedError>();
9598
9599 // The target body accesses each in_reduction variable through its
9600 // map_entries block argument. Redirect that block argument to the per-task
9601 // private storage returned by __kmpc_task_reduction_get_th_data so the body
9602 // accumulates into the reduction-private copy rather than the mapped
9603 // original. The lookup must run inside the target task body so the gtid
9604 // corresponds to the executing thread. The descriptor argument is NULL: the
9605 // runtime walks enclosing taskgroups to locate the matching task_reduction
9606 // registration for `origPtr`. Mirrors the in_reduction handling on
9607 // omp.taskloop.context.
9608 if (!inRedOrigPtrs.empty()) {
9609 // Collect, per item, the type the private pointer must have (the map
9610 // block argument's type), and, through the callback, rebind the map block
9611 // argument that stands in for each in_reduction list item to the per-task
9612 // reduction-private storage the runtime returns.
9613 SmallVector<llvm::Type *> inRedResultPtrTys;
9614 inRedResultPtrTys.reserve(inRedMapArgIdx.size());
9615 for (unsigned mapArgIdx : inRedMapArgIdx)
9616 inRedResultPtrTys.push_back(
9617 moduleTranslation.convertType(mapBlockArgs[mapArgIdx].getType()));
9618
9619 llvm::OpenMPIRBuilder::LocationDescription bodyLoc(builder);
9620 llvm::OpenMPIRBuilder::InsertPointTy redIP =
9621 ompBuilder->createTargetInReduction(
9622 bodyLoc, inRedOrigPtrs, inRedResultPtrTys,
9623 [&](unsigned idx, llvm::Value *priv) {
9624 moduleTranslation.mapValue(mapBlockArgs[inRedMapArgIdx[idx]],
9625 priv);
9626 });
9627 builder.restoreIP(redIP);
9628 }
9629
9631 moduleTranslation, allocaIP, deallocBlocks);
9633 targetRegion, "omp.target", builder, moduleTranslation);
9634
9635 if (failed(handleError(exitBlock, *targetOp)))
9636 return llvm::make_error<PreviouslyReportedError>();
9637
9638 builder.SetInsertPoint(exitBlock.get()->getTerminator());
9639
9640 if (failed(cleanupPrivateVars(targetOp, builder, moduleTranslation,
9641 targetOp.getLoc(), privateVarsInfo)))
9642 return llvm::make_error<PreviouslyReportedError>();
9643
9644 return builder.saveIP();
9645 };
9646
9647 StringRef parentName = parentFn.getName();
9648
9649 llvm::TargetRegionEntryInfo entryInfo;
9650
9651 getTargetEntryUniqueInfo(entryInfo, targetOp,
9652 *moduleTranslation.getOpenMPBuilder(),
9653 moduleTranslation.getFileSystem(), parentName);
9654
9655 MapInfoData mapData;
9656 collectMapDataFromMapOperands(mapData, mapVars, moduleTranslation, dl,
9657 builder, /*useDevPtrOperands=*/{},
9658 /*useDevAddrOperands=*/{}, hdaVars);
9659
9660 MapInfosTy combinedInfos;
9661 auto genMapInfoCB =
9662 [&](llvm::OpenMPIRBuilder::InsertPointTy codeGenIP) -> MapInfosTy & {
9663 builder.restoreIP(codeGenIP);
9664 genMapInfos(builder, moduleTranslation, dl, combinedInfos, mapData,
9665 targetDirective);
9666
9667 // Append a null entry for the implicit dyn_ptr argument so the argument
9668 // count sent to the runtime already includes it.
9669 auto *nullPtr = llvm::Constant::getNullValue(builder.getPtrTy());
9670 combinedInfos.BasePointers.push_back(nullPtr);
9671 combinedInfos.Pointers.push_back(nullPtr);
9672 combinedInfos.DevicePointers.push_back(
9673 llvm::OpenMPIRBuilder::DeviceInfoTy::None);
9674 combinedInfos.Sizes.push_back(builder.getInt64(0));
9675 combinedInfos.Types.push_back(
9676 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TARGET_PARAM |
9677 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL);
9678 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
9679 combinedInfos.HasAttachPtr.push_back(false);
9680 if (!combinedInfos.Names.empty())
9681 combinedInfos.Names.push_back(nullPtr);
9682 combinedInfos.Mappers.push_back(nullptr);
9683
9684 return combinedInfos;
9685 };
9686
9687 auto argAccessorCB = [&](llvm::Argument &arg, llvm::Value *input,
9688 llvm::Value *&retVal, InsertPointTy allocaIP,
9689 InsertPointTy codeGenIP,
9691 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
9692 llvm::IRBuilderBase::InsertPointGuard guard(builder);
9693 builder.SetCurrentDebugLocation(llvm::DebugLoc());
9694 // We just return the unaltered argument for the host function
9695 // for now, some alterations may be required in the future to
9696 // keep host fallback functions working identically to the device
9697 // version (e.g. pass ByCopy values should be treated as such on
9698 // host and device, currently not always the case)
9699 if (!isTargetDevice) {
9700 retVal = cast<llvm::Value>(&arg);
9701 return codeGenIP;
9702 }
9703
9704 return createDeviceArgumentAccessor(targetOp, mapData, arg, input, retVal,
9705 builder, *ompBuilder, moduleTranslation,
9706 allocaIP, codeGenIP, deallocIPs);
9707 };
9708
9709 llvm::OpenMPIRBuilder::TargetKernelRuntimeAttrs runtimeAttrs;
9710 llvm::OpenMPIRBuilder::TargetKernelDefaultAttrs defaultAttrs;
9711 Operation *targetCapturedOp =
9712 cast<omp::ComposableOpInterface>(*targetOp).findCapturedOp();
9713 initTargetDefaultAttrs(targetOp, targetCapturedOp, defaultAttrs,
9714 isTargetDevice, isGPU);
9715
9716 // Collect host-evaluated values needed to properly launch the kernel from the
9717 // host.
9718 if (!isTargetDevice)
9719 initTargetRuntimeAttrs(builder, moduleTranslation, targetOp,
9720 targetCapturedOp, runtimeAttrs);
9721
9722 // Pass host-evaluated values as parameters to the kernel / host fallback,
9723 // except if they are constants. In any case, map the MLIR block argument to
9724 // the corresponding LLVM values.
9726 SmallVector<Value> hostEvalVars = targetOp.getHostEvalVars();
9727 ArrayRef<BlockArgument> hostEvalBlockArgs = argIface.getHostEvalBlockArgs();
9728 for (auto [arg, var] : llvm::zip_equal(hostEvalBlockArgs, hostEvalVars)) {
9729 llvm::Value *value = moduleTranslation.lookupValue(var);
9730 moduleTranslation.mapValue(arg, value);
9731
9732 if (!llvm::isa<llvm::Constant>(value))
9733 kernelInput.push_back(value);
9734 }
9735
9736 for (size_t i = 0, e = mapData.OriginalValue.size(); i != e; ++i) {
9737 // 1) Declare target arguments are not passed to kernels as arguments.
9738 // 2) Attach maps are not passed in as arguments to kernels, except for
9739 // private attach maps used for corresponding-pointer initialization.
9740 // 3) Children of record objects are not passed in as arguments.
9741 // TODO: We currently do not handle cases where a member is explicitly
9742 // passed in as an argument, this will likley need to be handled in
9743 // the near future, rather than using IsAMember, it may be better to
9744 // test if the relevant BlockArg is used within the target region and
9745 // then use that as a basis for exclusion in the kernel inputs.
9746 using MapFlags = llvm::omp::OpenMPOffloadMappingFlags;
9747 bool isAttachMap = (mapData.Types[i] & MapFlags::OMP_MAP_ATTACH) ==
9748 MapFlags::OMP_MAP_ATTACH;
9749 bool isPrivateTargetParam =
9750 (mapData.Types[i] &
9751 (MapFlags::OMP_MAP_PRIVATE | MapFlags::OMP_MAP_TARGET_PARAM)) ==
9752 (MapFlags::OMP_MAP_PRIVATE | MapFlags::OMP_MAP_TARGET_PARAM);
9753
9754 if (!mapData.IsDeclareTarget[i] && !mapData.IsAMember[i] &&
9755 (!isAttachMap || (isAttachMap && isPrivateTargetParam)))
9756 kernelInput.push_back(mapData.OriginalValue[i]);
9757 }
9758
9760 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
9761 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
9762
9763 llvm::OpenMPIRBuilder::DependenciesInfo dds;
9764 if (failed(buildDependData(
9765 targetOp.getDependVars(), targetOp.getDependKinds(),
9766 targetOp.getDependIterated(), targetOp.getDependIteratedKinds(),
9767 builder, moduleTranslation, dds)))
9768 return failure();
9769
9770 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
9771
9772 llvm::OpenMPIRBuilder::TargetDataInfo info(
9773 /*RequiresDevicePointerInfo=*/false,
9774 /*SeparateBeginEndCalls=*/true);
9775
9776 auto customMapperCB =
9777 [&](unsigned int i) -> llvm::Expected<llvm::Function *> {
9778 if (!combinedInfos.Mappers[i])
9779 return nullptr;
9780 info.HasMapper = true;
9781 return getOrCreateUserDefinedMapperFunc(combinedInfos.Mappers[i], builder,
9782 moduleTranslation, targetDirective);
9783 };
9784
9785 llvm::Value *ifCond = nullptr;
9786 if (Value targetIfCond = targetOp.getIfExpr())
9787 ifCond = moduleTranslation.lookupValue(targetIfCond);
9788
9789 Value dynGroupPrivateSize = targetOp.getDynGroupprivateSize();
9790 llvm::Value *dynSizeVal = nullptr;
9791 if (dynGroupPrivateSize) {
9792 dynSizeVal = moduleTranslation.lookupValue(dynGroupPrivateSize);
9793 dynSizeVal = builder.CreateIntCast(dynSizeVal, builder.getInt32Ty(),
9794 /*isSigned=*/false);
9795 }
9796
9797 llvm::omp::OMPDynGroupprivateFallbackType fallbackType =
9798 getDynGroupprivateFallbackType(targetOp.getDynGroupprivateFallbackAttr());
9799
9800 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
9801 moduleTranslation.getOpenMPBuilder()->createTarget(
9802 ompLoc, isOffloadEntry, allocaIP, builder.saveIP(), deallocBlocks,
9803 info, entryInfo, defaultAttrs, runtimeAttrs, ifCond, kernelInput,
9804 genMapInfoCB, bodyCB, argAccessorCB, customMapperCB, dds,
9805 targetOp.getNowait(), dynSizeVal, fallbackType, outlinedFnDbgLoc);
9806
9807 if (failed(handleError(afterIP, opInst)))
9808 return failure();
9809
9810 builder.restoreIP(*afterIP);
9811
9812 if (dds.DepArray)
9813 builder.CreateFree(dds.DepArray);
9814
9815 return success();
9816}
9817
9818static LogicalResult
9819convertDeclareTargetAttr(Operation *op, mlir::omp::DeclareTargetAttr attribute,
9820 llvm::OpenMPIRBuilder *ompBuilder,
9821 LLVM::ModuleTranslation &moduleTranslation) {
9822 // Amend omp.declare_target by deleting the IR of the outlined functions
9823 // created for target regions. They cannot be filtered out from MLIR earlier
9824 // because the omp.target operation inside must be translated to LLVM, but
9825 // the wrapper functions themselves must not remain at the end of the
9826 // process. We know that functions where omp.declare_target does not match
9827 // omp.is_target_device at this stage can only be wrapper functions because
9828 // those that aren't are removed earlier as an MLIR transformation pass.
9829 if (FunctionOpInterface funcOp = dyn_cast<FunctionOpInterface>(op)) {
9830 if (auto offloadMod = dyn_cast<omp::OffloadModuleInterface>(
9831 op->getParentOfType<ModuleOp>().getOperation())) {
9832 if (!offloadMod.getIsTargetDevice())
9833 return success();
9834
9835 omp::DeclareTargetDeviceType declareType =
9836 attribute.getDeviceType().getValue();
9837
9838 if (declareType == omp::DeclareTargetDeviceType::host) {
9839 llvm::Function *llvmFunc =
9840 moduleTranslation.lookupFunction(funcOp.getName());
9841 llvmFunc->dropAllReferences();
9842 llvmFunc->eraseFromParent();
9843
9844 // Invalidate the builder's current insertion point, as it now points to
9845 // a deleted block.
9846 ompBuilder->Builder.ClearInsertionPoint();
9847 ompBuilder->Builder.SetCurrentDebugLocation(llvm::DebugLoc());
9848 } else if (llvm::Function *llvmFunc =
9849 moduleTranslation.lookupFunction(funcOp.getName())) {
9850 // Device-side declare target functions are externally visible by
9851 // default so they can be referenced from other device translation
9852 // units. That also prevents the offload LTO from internalizing and
9853 // deleting them when they end up unused in the final device image.
9854 // Such dead functions can still reference internal LDS and trigger
9855 // spurious "local memory global used by non-kernel function" backend
9856 // warnings. Marking them hidden keeps the symbol usable within the
9857 // device image's linkage unit while letting LTO drop it when nothing
9858 // references it; symbols that must stay reachable (e.g. via an offload
9859 // entry that takes their address) are kept alive by that reference.
9860 if (!llvmFunc->isDeclaration() && llvmFunc->hasExternalLinkage() &&
9861 llvmFunc->getVisibility() == llvm::GlobalValue::DefaultVisibility)
9862 llvmFunc->setVisibility(llvm::GlobalValue::HiddenVisibility);
9863 }
9864 }
9865 return success();
9866 }
9867
9868 if (LLVM::GlobalOp gOp = dyn_cast<LLVM::GlobalOp>(op)) {
9869 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
9870 if (auto *gVal = llvmModule->getNamedValue(gOp.getSymName())) {
9871 auto *gVar = cast<llvm::GlobalVariable>(gVal);
9872 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9873 bool isDeclaration = gOp.isDeclaration();
9874 bool isExternallyVisible =
9875 gOp.getVisibility() != mlir::SymbolTable::Visibility::Private;
9876 auto loc = op->getLoc()->findInstanceOf<FileLineColLoc>();
9877 llvm::StringRef mangledName = gOp.getSymName();
9878 mlir::omp::DeclareTargetCaptureClause captureClause =
9879 attribute.getCaptureClause().getValue();
9880 auto captureClauseKind = convertToCaptureClauseKind(captureClause);
9881 auto deviceClause =
9882 convertToDeviceClauseKind(attribute.getDeviceType().getValue());
9883 llvm::StringRef entryMangledName = mangledName;
9884 llvm::Constant *entryAddr = llvm::cast<llvm::Constant>(gVal);
9885 std::function<llvm::GlobalValue::LinkageTypes()> variableLinkage;
9886 llvm::SmallString<128> entryNameStorage;
9887 bool requiresUSM = ompBuilder->Config.hasRequiresUnifiedSharedMemory();
9888 bool isToOrEnter =
9889 captureClause == omp::DeclareTargetCaptureClause::to ||
9890 captureClause == omp::DeclareTargetCaptureClause::enter;
9891 bool isHostOnly = attribute.getDeviceType().getValue() ==
9892 omp::DeclareTargetDeviceType::host;
9893
9894 // A to/enter declare-target variable needs a device-resident,
9895 // name-resolvable copy and a host offloading entry. A local-linkage
9896 // global provides neither, so we promote it to external.
9897 if (isToOrEnter && !isHostOnly && !requiresUSM &&
9898 gVar->hasLocalLinkage()) {
9899 gVar->setLinkage(llvm::GlobalValue::ExternalLinkage);
9900 isExternallyVisible = true;
9901
9902 // Clear the stale dso_local flag so it is referenced like a
9903 // module-scope declare target global.
9904 if (ompBuilder->Config.isTargetDevice())
9905 gVar->setDSOLocal(false);
9906 }
9907
9908 if (isToOrEnter &&
9909 deviceClause ==
9910 llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseAny &&
9911 !requiresUSM && !isDeclaration &&
9912 (gVal->hasLocalLinkage() || gVal->hasHiddenVisibility())) {
9913 // Keep the original symbol as-is for target code, but create a visible
9914 // alias for the offload entry so libomptarget can associate the host
9915 // global with the actual device global.
9916 entryNameStorage = (mangledName + llvm::Twine("_decl_tgt_entry")).str();
9917 entryMangledName = entryNameStorage;
9918 if (llvm::GlobalValue *existing =
9919 llvmModule->getNamedValue(entryMangledName)) {
9920 entryAddr = llvm::cast<llvm::Constant>(existing);
9921 } else {
9922 entryAddr = llvm::GlobalAlias::create(
9923 gVal->getValueType(), gVal->getAddressSpace(),
9924 llvm::GlobalValue::WeakAnyLinkage, entryMangledName, entryAddr,
9925 llvmModule);
9926 llvm::cast<llvm::GlobalAlias>(entryAddr)->setVisibility(
9927 llvm::GlobalValue::DefaultVisibility);
9928 }
9929 variableLinkage = [] { return llvm::GlobalValue::WeakAnyLinkage; };
9930 }
9931 // unused for MLIR at the moment, required in Clang for book
9932 // keeping
9933 std::vector<llvm::GlobalVariable *> generatedRefs;
9934
9935 std::vector<llvm::Triple> targetTriple;
9936 auto targetTripleAttr = dyn_cast_or_null<mlir::StringAttr>(
9937 op->getParentOfType<mlir::ModuleOp>()->getDiscardableAttr(
9938 LLVM::LLVMDialect::getTargetTripleAttrName()));
9939 if (targetTripleAttr)
9940 targetTriple.emplace_back(targetTripleAttr.data());
9941
9942 auto fileInfoCallBack = [&loc]() {
9943 std::string filename = "";
9944 std::uint64_t lineNo = 0;
9945
9946 if (loc) {
9947 filename = loc.getFilename().str();
9948 lineNo = loc.getLine();
9949 }
9950
9951 return std::pair<std::string, std::uint64_t>(llvm::StringRef(filename),
9952 lineNo);
9953 };
9954
9955 llvm::vfs::FileSystem &vfs = moduleTranslation.getFileSystem();
9956 ompBuilder->registerTargetGlobalVariable(
9957 captureClauseKind, deviceClause, isDeclaration, isExternallyVisible,
9958 ompBuilder->getTargetEntryUniqueInfo(fileInfoCallBack, vfs),
9959 entryMangledName, generatedRefs, /*OpenMPSimd*/ false, targetTriple,
9960 /*GlobalInitializer*/ nullptr, variableLinkage, gVal->getType(),
9961 entryAddr);
9962
9963 if (ompBuilder->Config.isTargetDevice() &&
9964 (captureClause == omp::DeclareTargetCaptureClause::link ||
9965 requiresUSM)) {
9966 // For USM and link we generate a global reference pointer in the
9967 // default address space (e.g address space 0), as opposed to the
9968 // globals original type and address space.
9969 llvm::Type *ptrTy = llvm::PointerType::get(llvmModule->getContext(), 0);
9970 llvm::Constant *refPtr = ompBuilder->getAddrOfDeclareTargetVar(
9971 captureClauseKind, deviceClause, isDeclaration, isExternallyVisible,
9972 ompBuilder->getTargetEntryUniqueInfo(fileInfoCallBack, vfs),
9973 mangledName, generatedRefs, /*OpenMPSimd*/ false, targetTriple,
9974 ptrTy, /*GlobalInitializer*/ nullptr,
9975 /*VariableLinkage*/ nullptr);
9976
9977 // For indirectly-accessed global pointers, we rely on "internal"
9978 // linkage to optimize out the unneeded full-variable storage later,
9979 // since we can't prevent the LLVM dialect from generating globals
9980 // without also breaking target lowering.
9981 if (refPtr) {
9982 gVar->setLinkage(llvm::GlobalValue::InternalLinkage);
9983
9984 // Register the (original global, reference pointer) pair so that the
9985 // OpenMPIRBuilder can rewrite uses of the original global during
9986 // finalization.
9987 if (auto *newGV =
9988 dyn_cast<llvm::GlobalValue>(refPtr->stripPointerCasts()))
9989 ompBuilder->registerDeclareTargetGlobalReplacement(gVal, newGV);
9990 }
9991 }
9992
9993 // Mark 'device_type(host) enter(...)' variables as external in the device
9994 // since they're not supposed to have their own copy. This will cause
9995 // linker errors if accesses are attempted from the target device.
9996 if (ompBuilder->Config.isTargetDevice() && isHostOnly && isToOrEnter) {
9997 gVar->setLinkage(llvm::GlobalValue::ExternalLinkage);
9998 gVar->setInitializer(nullptr);
9999 }
10000 }
10001 }
10002
10003 return success();
10004}
10005
10006namespace {
10007
10008/// Implementation of the dialect interface that converts operations belonging
10009/// to the OpenMP dialect to LLVM IR.
10010class OpenMPDialectLLVMIRTranslationInterface
10011 : public LLVMTranslationDialectInterface {
10012public:
10013 using LLVMTranslationDialectInterface::LLVMTranslationDialectInterface;
10014
10015 /// Translates the given operation to LLVM IR using the provided IR builder
10016 /// and saving the state in `moduleTranslation`.
10017 LogicalResult
10018 convertOperation(Operation *op, llvm::IRBuilderBase &builder,
10019 LLVM::ModuleTranslation &moduleTranslation) const final;
10020
10021 /// Given an OpenMP MLIR attribute, create the corresponding LLVM-IR,
10022 /// runtime calls, or operation amendments
10023 LogicalResult
10024 amendOperation(Operation *op, ArrayRef<llvm::Instruction *> instructions,
10025 NamedAttribute attribute,
10026 LLVM::ModuleTranslation &moduleTranslation) const final;
10027
10028 /// Records the LLVM alloc pointer produced for an OMP ALLOCATE variable so
10029 /// that the paired omp.allocate_free op can generate the matching
10030 /// __kmpc_free call.
10031 void registerAllocatedPtr(Value var, llvm::Value *ptr) const {
10032 ompAllocatedPtrs[var] = ptr;
10033 }
10034
10035 /// Returns the LLVM alloc pointer previously registered for var, or
10036 /// nullptr if no allocation was recorded.
10037 llvm::Value *lookupAllocatedPtr(Value var) const {
10038 auto it = ompAllocatedPtrs.find(var);
10039 return it != ompAllocatedPtrs.end() ? it->second : nullptr;
10040 }
10041
10042private:
10043 /// Maps each MLIR variable value that appeared in an omp.allocate_dir op to
10044 /// the LLVM pointer returned by the corresponding __kmpc_alloc call. The
10045 /// paired omp.allocate_free op looks up these pointers to emit __kmpc_free.
10046 mutable DenseMap<Value, llvm::Value *> ompAllocatedPtrs;
10047};
10048
10049} // namespace
10050
10051LogicalResult OpenMPDialectLLVMIRTranslationInterface::amendOperation(
10052 Operation *op, ArrayRef<llvm::Instruction *> instructions,
10053 NamedAttribute attribute,
10054 LLVM::ModuleTranslation &moduleTranslation) const {
10055 return llvm::StringSwitch<llvm::function_ref<LogicalResult(Attribute)>>(
10056 attribute.getName())
10057 .Case("omp.is_target_device",
10058 [&](Attribute attr) {
10059 if (auto deviceAttr = dyn_cast<BoolAttr>(attr)) {
10060 llvm::OpenMPIRBuilderConfig &config =
10061 moduleTranslation.getOpenMPBuilder()->Config;
10062 config.setIsTargetDevice(deviceAttr.getValue());
10063 return success();
10064 }
10065 return failure();
10066 })
10067 .Case("omp.is_gpu",
10068 [&](Attribute attr) {
10069 if (auto gpuAttr = dyn_cast<BoolAttr>(attr)) {
10070 llvm::OpenMPIRBuilderConfig &config =
10071 moduleTranslation.getOpenMPBuilder()->Config;
10072 config.setIsGPU(gpuAttr.getValue());
10073 return success();
10074 }
10075 return failure();
10076 })
10077 .Case("omp.host_ir_filepath",
10078 [&](Attribute attr) {
10079 if (auto filepathAttr = dyn_cast<StringAttr>(attr)) {
10080 llvm::OpenMPIRBuilder *ompBuilder =
10081 moduleTranslation.getOpenMPBuilder();
10082 ompBuilder->loadOffloadInfoMetadata(
10083 moduleTranslation.getFileSystem(), filepathAttr.getValue());
10084 return success();
10085 }
10086 return failure();
10087 })
10088 .Case("omp.flags",
10089 [&](Attribute attr) {
10090 if (auto rtlAttr = dyn_cast<omp::FlagsAttr>(attr))
10091 return convertFlagsAttr(op, rtlAttr, moduleTranslation);
10092 return failure();
10093 })
10094 .Case("omp.version",
10095 [&](Attribute attr) {
10096 if (auto versionAttr = dyn_cast<omp::VersionAttr>(attr)) {
10097 llvm::OpenMPIRBuilder *ompBuilder =
10098 moduleTranslation.getOpenMPBuilder();
10099 ompBuilder->M.addModuleFlag(llvm::Module::Max, "openmp",
10100 versionAttr.getVersion());
10101 return success();
10102 }
10103 return failure();
10104 })
10105 .Case("omp.declare_target",
10106 [&](Attribute attr) {
10107 if (auto declareTargetAttr =
10108 dyn_cast<omp::DeclareTargetAttr>(attr)) {
10109 llvm::OpenMPIRBuilder *ompBuilder =
10110 moduleTranslation.getOpenMPBuilder();
10111 return convertDeclareTargetAttr(op, declareTargetAttr,
10112 ompBuilder, moduleTranslation);
10113 }
10114 return failure();
10115 })
10116 .Case("omp.requires",
10117 [&](Attribute attr) {
10118 if (auto requiresAttr = dyn_cast<omp::ClauseRequiresAttr>(attr)) {
10119 using Requires = omp::ClauseRequires;
10120 Requires flags = requiresAttr.getValue();
10121 llvm::OpenMPIRBuilderConfig &config =
10122 moduleTranslation.getOpenMPBuilder()->Config;
10123 config.setHasRequiresReverseOffload(
10124 bitEnumContainsAll(flags, Requires::reverse_offload));
10125 config.setHasRequiresUnifiedAddress(
10126 bitEnumContainsAll(flags, Requires::unified_address));
10127 config.setHasRequiresUnifiedSharedMemory(
10128 bitEnumContainsAll(flags, Requires::unified_shared_memory));
10129 config.setHasRequiresDynamicAllocators(
10130 bitEnumContainsAll(flags, Requires::dynamic_allocators));
10131 return success();
10132 }
10133 return failure();
10134 })
10135 .Case("omp.target_triples",
10136 [&](Attribute attr) {
10137 if (auto triplesAttr = dyn_cast<ArrayAttr>(attr)) {
10138 llvm::OpenMPIRBuilderConfig &config =
10139 moduleTranslation.getOpenMPBuilder()->Config;
10140 config.TargetTriples.clear();
10141 config.TargetTriples.reserve(triplesAttr.size());
10142 for (Attribute tripleAttr : triplesAttr) {
10143 if (auto tripleStrAttr = dyn_cast<StringAttr>(tripleAttr))
10144 config.TargetTriples.emplace_back(tripleStrAttr.getValue());
10145 else
10146 return failure();
10147 }
10148 return success();
10149 }
10150 return failure();
10151 })
10152 .Case("omp.integer_wrap_around",
10153 [&](Attribute attr) {
10154 if (auto wrapAttr = dyn_cast<omp::IntegerWrapAroundAttr>(attr)) {
10155 llvm::OpenMPIRBuilderConfig &config =
10156 moduleTranslation.getOpenMPBuilder()->Config;
10157 config.setNoSignedWrap(!wrapAttr.getIntegerWrapAround());
10158 return success();
10159 }
10160 return failure();
10161 })
10162 .Default([](Attribute) {
10163 // Fall through for omp attributes that do not require lowering.
10164 return success();
10165 })(attribute.getValue());
10166
10167 return failure();
10168}
10169
10170// Returns true if the operation is not inside a TargetOp, it is part of a
10171// function and that function is not declare target.
10172static bool isHostDeviceOp(Operation *op) {
10173 // Assumes no reverse offloading
10174 if (op->getParentOfType<omp::TargetOp>())
10175 return false;
10176
10177 if (auto parentFn = op->getParentOfType<LLVM::LLVMFuncOp>()) {
10178 if (auto declareTargetIface =
10179 llvm::dyn_cast<mlir::omp::DeclareTargetInterface>(
10180 parentFn.getOperation()))
10181 if (declareTargetIface.isDeclareTarget() &&
10182 declareTargetIface.getDeclareTargetDeviceType() !=
10183 mlir::omp::DeclareTargetDeviceType::host)
10184 return false;
10185
10186 return true;
10187 }
10188
10189 return false;
10190}
10191
10192static llvm::Function *getOmpTargetAlloc(llvm::IRBuilderBase &builder,
10193 llvm::Module *llvmModule) {
10194 llvm::Type *i64Ty = builder.getInt64Ty();
10195 llvm::Type *i32Ty = builder.getInt32Ty();
10196 llvm::Type *returnType = builder.getPtrTy(0);
10197 llvm::FunctionType *fnType =
10198 llvm::FunctionType::get(returnType, {i64Ty, i32Ty}, false);
10199 llvm::Function *func = cast<llvm::Function>(
10200 llvmModule->getOrInsertFunction("omp_target_alloc", fnType).getCallee());
10201 return func;
10202}
10203
10204template <typename T>
10205static llvm::Value *
10206getAllocationSize(llvm::IRBuilderBase &builder,
10207 LLVM::ModuleTranslation &moduleTranslation, T op) {
10208 llvm::DataLayout dataLayout =
10209 moduleTranslation.getLLVMModule()->getDataLayout();
10210 llvm::Type *llvmHeapTy =
10211 moduleTranslation.convertType(op.getMemElemTypeAttr().getValue());
10212
10213 auto alignment = op.getMemAlignment();
10214 llvm::TypeSize typeSize = llvm::alignTo(
10215 dataLayout.getTypeStoreSize(llvmHeapTy),
10216 alignment ? *alignment : dataLayout.getABITypeAlign(llvmHeapTy).value());
10217
10218 llvm::Value *allocSize = builder.getInt64(typeSize.getFixedValue());
10219 return builder.CreateMul(
10220 allocSize,
10221 builder.CreateIntCast(moduleTranslation.lookupValue(op.getMemArraySize()),
10222 builder.getInt64Ty(),
10223 /*isSigned=*/false));
10224}
10225
10226template <>
10227llvm::Value *getAllocationSize(llvm::IRBuilderBase &builder,
10228 LLVM::ModuleTranslation &moduleTranslation,
10229 omp::TargetAllocMemOp op) {
10230 llvm::DataLayout dataLayout =
10231 moduleTranslation.getLLVMModule()->getDataLayout();
10232 llvm::Type *llvmHeapTy = moduleTranslation.convertType(op.getAllocatedType());
10233 llvm::TypeSize typeSize = dataLayout.getTypeAllocSize(llvmHeapTy);
10234 llvm::Value *allocSize = builder.getInt64(typeSize.getFixedValue());
10235 for (auto typeParam : op.getTypeparams()) {
10236 allocSize = builder.CreateMul(
10237 allocSize,
10238 builder.CreateIntCast(moduleTranslation.lookupValue(typeParam),
10239 builder.getInt64Ty(),
10240 /*isSigned=*/false));
10241 }
10242 return allocSize;
10243}
10244
10245static LogicalResult
10246convertTargetAllocMemOp(Operation &opInst, llvm::IRBuilderBase &builder,
10247 LLVM::ModuleTranslation &moduleTranslation) {
10248 auto allocMemOp = cast<omp::TargetAllocMemOp>(opInst);
10249 if (!allocMemOp)
10250 return failure();
10251
10252 // Get "omp_target_alloc" function
10253 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
10254 llvm::Function *ompTargetAllocFunc = getOmpTargetAlloc(builder, llvmModule);
10255 // Get the corresponding device value in llvm
10256 mlir::Value deviceNum = allocMemOp.getDevice();
10257 llvm::Value *llvmDeviceNum = moduleTranslation.lookupValue(deviceNum);
10258 // Get the allocation size.
10259 llvm::Value *allocSize =
10260 getAllocationSize(builder, moduleTranslation, allocMemOp);
10261 // Create call to "omp_target_alloc" with the args as translated llvm values.
10262 llvm::CallInst *call =
10263 builder.CreateCall(ompTargetAllocFunc, {allocSize, llvmDeviceNum});
10264 llvm::Value *resultI64 = builder.CreatePtrToInt(call, builder.getInt64Ty());
10265
10266 // Map the result
10267 moduleTranslation.mapValue(allocMemOp.getResult(), resultI64);
10268 return success();
10269}
10270
10271static LogicalResult
10272convertAllocSharedMemOp(omp::AllocSharedMemOp allocMemOp,
10273 llvm::IRBuilderBase &builder,
10274 LLVM::ModuleTranslation &moduleTranslation) {
10275 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
10276 llvm::Value *size = getAllocationSize(builder, moduleTranslation, allocMemOp);
10277 moduleTranslation.mapValue(allocMemOp.getResult(),
10278 ompBuilder->createOMPAllocShared(builder, size));
10279 return success();
10280}
10281
10282static LogicalResult
10283convertAllocateDirOp(Operation &opInst, llvm::IRBuilderBase &builder,
10284 LLVM::ModuleTranslation &moduleTranslation,
10285 const OpenMPDialectLLVMIRTranslationInterface &ompIface) {
10286 auto allocateDirOp = cast<omp::AllocateDirOp>(opInst);
10287 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
10288
10289 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
10290 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
10291 llvm::DataLayout dataLayout = llvmModule->getDataLayout();
10292 SmallVector<Value> vars = allocateDirOp.getVarList();
10293 std::optional<int64_t> alignAttr = allocateDirOp.getAlign();
10294
10295 llvm::Value *allocator;
10296 if (auto allocatorVar = allocateDirOp.getAllocator()) {
10297 allocator = moduleTranslation.lookupValue(allocatorVar);
10298 if (allocator->getType()->isIntegerTy())
10299 allocator = builder.CreateIntToPtr(allocator, builder.getPtrTy());
10300 else if (allocator->getType()->isPointerTy())
10301 allocator = builder.CreatePointerBitCastOrAddrSpaceCast(
10302 allocator, builder.getPtrTy());
10303 } else {
10304 allocator = llvm::ConstantPointerNull::get(builder.getPtrTy());
10305 }
10306
10307 for (Value var : vars) {
10308 Value baseVar = getBaseValueForTypeLookup(var);
10309 llvm::Type *typeToInspect =
10310 getAllocatedLlvmTypeForVariable(var, baseVar, moduleTranslation);
10311
10312 llvm::Value *size;
10313 if (std::optional<llvm::Value *> dynamicSize = getDynamicAllocatedSize(
10314 var, baseVar, moduleTranslation, builder, dataLayout)) {
10315 size = *dynamicSize;
10316 } else if (typeToInspect->isArrayTy()) {
10317 size = builder.getInt64(
10318 dataLayout.getTypeAllocSize(typeToInspect).getFixedValue());
10319 } else {
10320 size = builder.getInt64(
10321 dataLayout.getTypeAllocSize(typeToInspect).getFixedValue());
10322 }
10323
10324 uint64_t alignValue =
10325 alignAttr ? alignAttr.value()
10326 : dataLayout.getABITypeAlign(typeToInspect).value();
10327 llvm::Value *alignConst = builder.getInt64(alignValue);
10328 // Align the size: ((size + align - 1) / align) * align
10329 size = builder.CreateAdd(size, builder.getInt64(alignValue - 1), "", true);
10330 size = builder.CreateUDiv(size, alignConst);
10331 size = builder.CreateMul(size, alignConst, "", true);
10332
10333 std::string allocName =
10334 ompBuilder->createPlatformSpecificName({".void.addr"});
10335 llvm::CallInst *allocCall;
10336 if (alignAttr.has_value()) {
10337 allocCall = ompBuilder->createOMPAlignedAlloc(
10338 ompLoc, builder.getInt64(alignAttr.value()), size, allocator,
10339 allocName);
10340 } else {
10341 allocCall =
10342 ompBuilder->createOMPAlloc(ompLoc, size, allocator, allocName);
10343 }
10344 // Record the alloc pointer keyed by the MLIR variable value.
10345 ompIface.registerAllocatedPtr(var, allocCall);
10346
10347 if (llvm::Value *baseLlvm = moduleTranslation.lookupValue(baseVar)) {
10348 llvm::Value *boundPtr = builder.CreatePointerBitCastOrAddrSpaceCast(
10349 allocCall, baseLlvm->getType());
10350 moduleTranslation.remapAllValuesWith(baseLlvm, boundPtr);
10351 } else if (llvm::Value *varLlvm = moduleTranslation.lookupValue(var)) {
10352 llvm::Value *boundPtr = builder.CreatePointerBitCastOrAddrSpaceCast(
10353 allocCall, varLlvm->getType());
10354 moduleTranslation.remapAllValuesWith(varLlvm, boundPtr);
10355 }
10356 }
10357
10358 return success();
10359}
10360
10361static LogicalResult
10362convertAllocateFreeOp(Operation &opInst, llvm::IRBuilderBase &builder,
10363 LLVM::ModuleTranslation &moduleTranslation,
10364 const OpenMPDialectLLVMIRTranslationInterface &ompIface) {
10365 auto freeOp = cast<omp::AllocateFreeOp>(opInst);
10366 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
10367 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
10368
10369 llvm::Value *allocator;
10370 if (auto allocatorVar = freeOp.getAllocator()) {
10371 allocator = moduleTranslation.lookupValue(allocatorVar);
10372 if (allocator->getType()->isIntegerTy())
10373 allocator = builder.CreateIntToPtr(allocator, builder.getPtrTy());
10374 else if (allocator->getType()->isPointerTy())
10375 allocator = builder.CreatePointerBitCastOrAddrSpaceCast(
10376 allocator, builder.getPtrTy());
10377 } else {
10378 allocator = llvm::ConstantPointerNull::get(builder.getPtrTy());
10379 }
10380
10381 // Emit __kmpc_free for each variable in reverse allocation order.
10382 SmallVector<Value> vars = freeOp.getVarList();
10383 for (Value var : llvm::reverse(vars)) {
10384 llvm::Value *allocPtr = ompIface.lookupAllocatedPtr(var);
10385 if (!allocPtr)
10386 return opInst.emitError("omp.allocate_free: no allocation recorded");
10387 ompBuilder->createOMPFree(ompLoc, allocPtr, allocator, "");
10388 }
10389
10390 return success();
10391}
10392
10393static llvm::Function *getOmpTargetFree(llvm::IRBuilderBase &builder,
10394 llvm::Module *llvmModule) {
10395 llvm::Type *ptrTy = builder.getPtrTy(0);
10396 llvm::Type *i32Ty = builder.getInt32Ty();
10397 llvm::Type *voidTy = builder.getVoidTy();
10398 llvm::FunctionType *fnType =
10399 llvm::FunctionType::get(voidTy, {ptrTy, i32Ty}, false);
10400 llvm::Function *func = dyn_cast<llvm::Function>(
10401 llvmModule->getOrInsertFunction("omp_target_free", fnType).getCallee());
10402 return func;
10403}
10404
10405static LogicalResult
10406convertTargetFreeMemOp(Operation &opInst, llvm::IRBuilderBase &builder,
10407 LLVM::ModuleTranslation &moduleTranslation) {
10408 auto freeMemOp = cast<omp::TargetFreeMemOp>(opInst);
10409 if (!freeMemOp)
10410 return failure();
10411
10412 // Get "omp_target_free" function
10413 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
10414 llvm::Function *ompTragetFreeFunc = getOmpTargetFree(builder, llvmModule);
10415 // Get the corresponding device value in llvm
10416 mlir::Value deviceNum = freeMemOp.getDevice();
10417 llvm::Value *llvmDeviceNum = moduleTranslation.lookupValue(deviceNum);
10418 // Get the corresponding heapref value in llvm
10419 mlir::Value heapref = freeMemOp.getHeapref();
10420 llvm::Value *llvmHeapref = moduleTranslation.lookupValue(heapref);
10421 // Convert heapref int to ptr and call "omp_target_free"
10422 llvm::Value *intToPtr =
10423 builder.CreateIntToPtr(llvmHeapref, builder.getPtrTy(0));
10424 builder.CreateCall(ompTragetFreeFunc, {intToPtr, llvmDeviceNum});
10425 return success();
10426}
10427
10428static LogicalResult
10429convertFreeSharedMemOp(omp::FreeSharedMemOp freeMemOp,
10430 llvm::IRBuilderBase &builder,
10431 LLVM::ModuleTranslation &moduleTranslation) {
10432 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
10433 llvm::Value *size = getAllocationSize(builder, moduleTranslation, freeMemOp);
10434 ompBuilder->createOMPFreeShared(
10435 builder, moduleTranslation.lookupValue(freeMemOp.getHeapref()), size);
10436 return success();
10437}
10438
10439/// Converts an OpenMP groupprivate operation into LLVM IR.
10440static LogicalResult
10441convertOmpGroupprivate(Operation &opInst, llvm::IRBuilderBase &builder,
10442 LLVM::ModuleTranslation &moduleTranslation) {
10443 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
10444 auto groupprivateOp = cast<omp::GroupprivateOp>(opInst);
10445
10446 if (failed(checkImplementationStatus(opInst)))
10447 return failure();
10448
10449 bool isTargetDevice = ompBuilder->Config.isTargetDevice();
10450
10451 // Determine whether group-private storage should be allocated based on
10452 // device_type. When not specified, default to 'any' (allocate on both).
10453 bool shouldAllocate = true;
10454 switch (groupprivateOp.getDeviceType().value_or(
10455 mlir::omp::DeclareTargetDeviceType::any)) {
10456 case mlir::omp::DeclareTargetDeviceType::host:
10457 shouldAllocate = !isTargetDevice;
10458 break;
10459 case mlir::omp::DeclareTargetDeviceType::nohost:
10460 shouldAllocate = isTargetDevice;
10461 break;
10462 case mlir::omp::DeclareTargetDeviceType::any:
10463 shouldAllocate = true;
10464 break;
10465 }
10466
10467 // Look up the global variable directly by symbol name.
10469 &opInst, groupprivateOp.getSymNameAttr());
10470 if (!global)
10471 return opInst.emitError()
10472 << "expected symbol '" << groupprivateOp.getSymName()
10473 << "' to reference an LLVM global variable";
10474
10475 llvm::GlobalValue *globalValue = moduleTranslation.lookupGlobal(global);
10476 llvm::Type *varType = moduleTranslation.convertType(global.getType());
10477 std::string varName = globalValue->getName().str();
10478
10479 llvm::Value *resultPtr;
10480 if (shouldAllocate && isTargetDevice) {
10481 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
10482 llvm::Triple targetTriple(llvmModule->getTargetTriple());
10483 unsigned sharedAddressSpace;
10484 if (targetTriple.isAMDGCN())
10485 sharedAddressSpace = llvm::AMDGPUAS::LOCAL_ADDRESS;
10486 else if (targetTriple.isNVPTX())
10487 sharedAddressSpace = llvm::NVPTXAS::ADDRESS_SPACE_SHARED;
10488 else
10489 return opInst.emitError() << "groupprivate is not supported for target: "
10490 << targetTriple.str();
10491 llvm::GlobalVariable *sharedVar = new llvm::GlobalVariable(
10492 *llvmModule, varType, /*isConstant=*/false,
10493 llvm::GlobalValue::InternalLinkage, llvm::PoisonValue::get(varType),
10494 varName, /*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal,
10495 sharedAddressSpace,
10496 /*isExternallyInitialized=*/false);
10497 resultPtr = sharedVar;
10498 } else {
10499 if (shouldAllocate && !isTargetDevice)
10500 opInst.emitWarning("groupprivate directive is currently ignored on the "
10501 "host, using original global");
10502 resultPtr = globalValue;
10503 }
10504
10505 moduleTranslation.mapValue(opInst.getResult(0), resultPtr);
10506 return success();
10507}
10508
10509/// Given an OpenMP MLIR operation, create the corresponding LLVM IR (including
10510/// OpenMP runtime calls).
10511LogicalResult OpenMPDialectLLVMIRTranslationInterface::convertOperation(
10512 Operation *op, llvm::IRBuilderBase &builder,
10513 LLVM::ModuleTranslation &moduleTranslation) const {
10514 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
10515
10516 if (ompBuilder->Config.isTargetDevice() &&
10517 !isa<omp::TargetOp, omp::MapInfoOp, omp::TerminatorOp, omp::YieldOp>(
10518 op) &&
10519 isHostDeviceOp(op))
10520 return op->emitOpError() << "unsupported host op found in device";
10521
10522 // For each loop, introduce one stack frame to hold loop information. Ensure
10523 // this is only done for the outermost loop wrapper to prevent introducing
10524 // multiple stack frames for a single loop. Initially set to null, the loop
10525 // information structure is initialized during translation of the nested
10526 // omp.loop_nest operation, making it available to translation of all loop
10527 // wrappers after their body has been successfully translated.
10528 bool isOutermostLoopWrapper =
10529 isa_and_present<omp::LoopWrapperInterface>(op) &&
10530 !dyn_cast_if_present<omp::LoopWrapperInterface>(op->getParentOp());
10531
10532 // The TASKLOOP construct is implemented with an outer taskloop.context
10533 // operation which is not a loop wrapper, containing an inner taskloop
10534 // operation which is a loop wrapper. The stack frame should be pushed when
10535 // translating the outer taskloop.context and popped when translating the
10536 // inner taskloop which is a loop wrapper. We need access to the loop
10537 // information in the outer taskloop context so we need to create it and pop
10538 // it around the taskloop context not the inner loop wrapper.
10539 if (isa<omp::TaskloopContextOp>(op))
10540 isOutermostLoopWrapper = true;
10541 else if (isa<omp::TaskloopWrapperOp>(op))
10542 isOutermostLoopWrapper = false;
10543
10544 if (isOutermostLoopWrapper)
10545 moduleTranslation.stackPush<OpenMPLoopInfoStackFrame>();
10546
10547 auto result =
10548 llvm::TypeSwitch<Operation *, LogicalResult>(op)
10549 .Case([&](omp::BarrierOp op) -> LogicalResult {
10551 return failure();
10552
10553 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
10554 ompBuilder->createBarrier(builder, llvm::omp::OMPD_barrier);
10555 LogicalResult res = handleError(afterIP, *op);
10556 if (res.succeeded()) {
10557 // If the barrier generated a cancellation check, the insertion
10558 // point might now need to be changed to a new continuation block
10559 builder.restoreIP(*afterIP);
10560 }
10561 return res;
10562 })
10563 .Case([&](omp::TaskyieldOp op) {
10565 return failure();
10566
10567 ompBuilder->createTaskyield(builder);
10568 return success();
10569 })
10570 .Case([&](omp::FlushOp op) {
10572 return failure();
10573
10574 // No support in Openmp runtime function (__kmpc_flush) to accept
10575 // the argument list.
10576 // OpenMP standard states the following:
10577 // "An implementation may implement a flush with a list by ignoring
10578 // the list, and treating it the same as a flush without a list."
10579 //
10580 // The argument list is discarded so that, flush with a list is
10581 // treated same as a flush without a list.
10582 ompBuilder->createFlush(builder);
10583 return success();
10584 })
10585 .Case([&](omp::ErrorOp op) {
10587 return failure();
10588
10589 llvm::Value *message = nullptr;
10590 if (mlir::Value messageExpr = op.getMessageExpr())
10591 message = moduleTranslation.lookupValue(messageExpr);
10592 else if (std::optional<StringRef> msg = op.getMessage();
10593 msg && !msg->empty())
10594 message = builder.CreateGlobalString(*msg);
10595 ompBuilder->createError(
10596 llvm::OpenMPIRBuilder::LocationDescription(builder),
10597 op.getSeverity() == omp::ClauseSeverity::fatal, message);
10598 return success();
10599 })
10600 .Case([&](omp::ParallelOp op) {
10601 return convertOmpParallel(op, builder, moduleTranslation);
10602 })
10603 .Case([&](omp::MaskedOp) {
10604 return convertOmpMasked(*op, builder, moduleTranslation);
10605 })
10606 .Case([&](omp::MasterOp) {
10607 return convertOmpMaster(*op, builder, moduleTranslation);
10608 })
10609 .Case([&](omp::CriticalOp) {
10610 return convertOmpCritical(*op, builder, moduleTranslation);
10611 })
10612 .Case([&](omp::OrderedRegionOp) {
10613 return convertOmpOrderedRegion(*op, builder, moduleTranslation);
10614 })
10615 .Case([&](omp::OrderedOp) {
10616 return convertOmpOrdered(*op, builder, moduleTranslation);
10617 })
10618 .Case([&](omp::WsloopOp) {
10619 return convertOmpWsloop(*op, builder, moduleTranslation);
10620 })
10621 .Case([&](omp::SimdOp) {
10622 return convertOmpSimd(*op, builder, moduleTranslation);
10623 })
10624 .Case([&](omp::AtomicReadOp) {
10625 return convertOmpAtomicRead(*op, builder, moduleTranslation);
10626 })
10627 .Case([&](omp::AtomicWriteOp) {
10628 return convertOmpAtomicWrite(*op, builder, moduleTranslation);
10629 })
10630 .Case([&](omp::AtomicUpdateOp op) {
10631 return convertOmpAtomicUpdate(op, builder, moduleTranslation);
10632 })
10633 .Case([&](omp::AtomicCaptureOp op) {
10634 return convertOmpAtomicCapture(op, builder, moduleTranslation);
10635 })
10636 .Case([&](omp::AtomicCompareOp op) {
10637 return convertOmpAtomicCompare(op, builder, moduleTranslation);
10638 })
10639 .Case([&](omp::CancelOp op) {
10640 return convertOmpCancel(op, builder, moduleTranslation);
10641 })
10642 .Case([&](omp::CancellationPointOp op) {
10643 return convertOmpCancellationPoint(op, builder, moduleTranslation);
10644 })
10645 .Case([&](omp::SectionsOp) {
10646 return convertOmpSections(*op, builder, moduleTranslation);
10647 })
10648 .Case([&](omp::ScopeOp op) {
10649 return convertOmpScope(op, builder, moduleTranslation);
10650 })
10651 .Case([&](omp::SingleOp op) {
10652 return convertOmpSingle(op, builder, moduleTranslation);
10653 })
10654 .Case([&](omp::TeamsOp op) {
10655 return convertOmpTeams(op, builder, moduleTranslation);
10656 })
10657 .Case([&](omp::TaskOp op) {
10658 return convertOmpTaskOp(op, builder, moduleTranslation);
10659 })
10660 .Case([&](omp::TaskloopWrapperOp op) {
10661 return convertOmpTaskloopWrapperOp(op, builder, moduleTranslation);
10662 })
10663 .Case([&](omp::TaskloopContextOp op) {
10664 return convertOmpTaskloopContextOp(op, builder, moduleTranslation);
10665 })
10666 .Case([&](omp::TaskgroupOp op) {
10667 return convertOmpTaskgroupOp(op, builder, moduleTranslation);
10668 })
10669 .Case([&](omp::TaskwaitOp op) {
10670 return convertOmpTaskwaitOp(op, builder, moduleTranslation);
10671 })
10672 .Case([&](omp::InteropInitOp op) {
10673 return convertOmpInteropInitOp(op, builder, moduleTranslation);
10674 })
10675 .Case([&](omp::InteropDestroyOp op) {
10676 return convertOmpInteropDestroyOp(op, builder, moduleTranslation);
10677 })
10678 .Case([&](omp::InteropUseOp op) {
10679 return convertOmpInteropUseOp(op, builder, moduleTranslation);
10680 })
10681 .Case<omp::YieldOp, omp::TerminatorOp, omp::DeclareMapperOp,
10682 omp::DeclareMapperInfoOp, omp::DeclareReductionOp,
10683 omp::CriticalDeclareOp>([](auto op) {
10684 // `yield` and `terminator` can be just omitted. The block structure
10685 // was created in the region that handles their parent operation.
10686 // `declare_reduction` will be used by reductions and is not
10687 // converted directly, skip it.
10688 // `declare_mapper` and `declare_mapper.info` are handled whenever
10689 // they are referred to through a `map` clause.
10690 // `critical.declare` is only used to declare names of critical
10691 // sections which will be used by `critical` ops and hence can be
10692 // ignored for lowering. The OpenMP IRBuilder will create unique
10693 // name for critical section names.
10694 return success();
10695 })
10696 .Case([&](omp::ThreadprivateOp) {
10697 return convertOmpThreadprivate(*op, builder, moduleTranslation);
10698 })
10699 .Case<omp::TargetDataOp, omp::TargetEnterDataOp,
10700 omp::TargetExitDataOp, omp::TargetUpdateOp>([&](auto op) {
10701 return convertOmpTargetData(op, builder, moduleTranslation);
10702 })
10703 .Case([&](omp::TargetOp) {
10704 return convertOmpTarget(*op, builder, moduleTranslation);
10705 })
10706 .Case([&](omp::DistributeOp) {
10707 return convertOmpDistribute(*op, builder, moduleTranslation);
10708 })
10709 .Case([&](omp::LoopNestOp) {
10710 return convertOmpLoopNest(*op, builder, moduleTranslation);
10711 })
10712 .Case<omp::MapInfoOp, omp::MapBoundsOp, omp::PrivateClauseOp,
10713 omp::AffinityEntryOp, omp::IteratorOp>([&](auto op) {
10714 // No-op, should be handled by relevant owning operations e.g.
10715 // TargetOp, TargetEnterDataOp, TargetExitDataOp, TargetDataOp
10716 // etc. and then discarded
10717 return success();
10718 })
10719 .Case([&](omp::NewCliOp op) {
10720 // Meta-operation: Doesn't do anything by itself, but used to
10721 // identify a loop.
10722 return success();
10723 })
10724 .Case([&](omp::CanonicalLoopOp op) {
10725 return convertOmpCanonicalLoopOp(op, builder, moduleTranslation);
10726 })
10727 .Case([&](omp::UnrollHeuristicOp op) {
10728 // FIXME: Handling omp.unroll_heuristic as an executable requires
10729 // that the generator (e.g. omp.canonical_loop) has been seen first.
10730 // For construct that require all codegen to occur inside a callback
10731 // (e.g. OpenMPIRBilder::createParallel), all codegen of that
10732 // contained region including their transformations must occur at
10733 // the omp.canonical_loop.
10734 return applyUnrollHeuristic(op, builder, moduleTranslation);
10735 })
10736 .Case([&](omp::UnrollFullOp op) {
10737 return applyUnrollFull(op, builder, moduleTranslation);
10738 })
10739 .Case([&](omp::UnrollPartialOp op) {
10740 return applyUnrollPartial(op, builder, moduleTranslation);
10741 })
10742 .Case([&](omp::TileOp op) {
10743 return applyTile(op, builder, moduleTranslation);
10744 })
10745 .Case([&](omp::FuseOp op) {
10746 return applyFuse(op, builder, moduleTranslation);
10747 })
10748 .Case([&](omp::TargetAllocMemOp) {
10749 return convertTargetAllocMemOp(*op, builder, moduleTranslation);
10750 })
10751 .Case([&](omp::TargetFreeMemOp) {
10752 return convertTargetFreeMemOp(*op, builder, moduleTranslation);
10753 })
10754 .Case([&](omp::AllocateDirOp) {
10755 return convertAllocateDirOp(*op, builder, moduleTranslation, *this);
10756 })
10757 .Case([&](omp::AllocateFreeOp) {
10758 return convertAllocateFreeOp(*op, builder, moduleTranslation,
10759 *this);
10760 })
10761 .Case([&](omp::AllocSharedMemOp op) {
10762 return convertAllocSharedMemOp(op, builder, moduleTranslation);
10763 })
10764 .Case([&](omp::FreeSharedMemOp op) {
10765 return convertFreeSharedMemOp(op, builder, moduleTranslation);
10766 })
10767 .Case([&](omp::GroupprivateOp) {
10768 return convertOmpGroupprivate(*op, builder, moduleTranslation);
10769 })
10770 .Default([&](Operation *inst) {
10771 return inst->emitError()
10772 << "not yet implemented: " << inst->getName();
10773 });
10774
10775 if (isOutermostLoopWrapper)
10776 moduleTranslation.stackPop();
10777
10778 return result;
10779}
10780
10782 registry.insert<omp::OpenMPDialect>();
10783 registry.addExtension(+[](MLIRContext *ctx, omp::OpenMPDialect *dialect) {
10784 dialect->addInterfaces<OpenMPDialectLLVMIRTranslationInterface>();
10785 });
10786}
10787
10789 DialectRegistry registry;
10791 context.appendDialectRegistry(registry);
10792}
for(Operation *op :ops)
return success()
if(failed(verifyVectorMemoryOp(getOperation(), memrefType, getVectorType()))) return failure()
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
ArrayAttr()
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 bool isPrivatizeableAttachMap(omp::ClauseMapFlags mapType)
static llvm::Value * getSizeInBytes(DataLayout &dl, const mlir::Type &type, Operation *clauseOp, llvm::Value *basePointer, llvm::Type *baseType, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static llvm::Error initPrivateVars(llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, PrivateVarsInfo &privateVarsInfo, llvm::DenseMap< Value, Value > *mappedPrivateVars=nullptr)
static LogicalResult convertAllocSharedMemOp(omp::AllocSharedMemOp allocMemOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static llvm::CanonicalLoopInfo * findCurrentLoopInfo(LLVM::ModuleTranslation &moduleTranslation)
Find the loop information structure for the loop nest being translated.
static OwningReductionGen makeReductionGen(omp::DeclareReductionOp decl, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Create an OpenMPIRBuilder-compatible reduction generator for the given reduction declaration.
static 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 llvm::Type * getAllocatedLlvmTypeForVariable(Value var, Value baseVar, LLVM::ModuleTranslation &moduleTranslation)
static LogicalResult applyTile(omp::TileOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Apply a #pragma omp tile / !$omp tile transformation using the OpenMPIRBuilder.
static LogicalResult convertAllocateFreeOp(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, const OpenMPDialectLLVMIRTranslationInterface &ompIface)
static llvm::Function * getOmpTargetFree(llvm::IRBuilderBase &builder, llvm::Module *llvmModule)
static LogicalResult convertOmpTaskgroupOp(omp::TaskgroupOp tgOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP taskgroup construct into LLVM IR using OpenMPIRBuilder.
static std::optional< llvm::Value * > getDynamicAllocatedSize(Value var, Value baseVar, LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder, const llvm::DataLayout &dataLayout)
static void collectMapDataFromMapOperands(MapInfoData &mapData, SmallVectorImpl< Value > &mapVars, LLVM::ModuleTranslation &moduleTranslation, DataLayout &dl, llvm::IRBuilderBase &builder, ArrayRef< Value > useDevPtrOperands={}, ArrayRef< Value > useDevAddrOperands={}, ArrayRef< Value > hasDevAddrOperands={})
static void extractAtomicControlFlags(omp::AtomicUpdateOp atomicUpdateOp, bool &isIgnoreDenormalMode, bool &isFineGrainedMemory, bool &isRemoteMemory)
static Operation * genLoop(CodegenEnv &env, OpBuilder &builder, LoopId curr, unsigned numCases, bool needsUniv, ArrayRef< TensorLevel > tidLvls)
Generates a for-loop or a while-loop, depending on whether it implements singleton iteration or co-it...
#define MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(CLASS_NAME)
Definition TypeID.h:331
#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.
void remapAllValuesWith(llvm::Value *oldValue, llvm::Value *newValue)
Remap old value with new value in the MLIR-to-LLVM value map so later translations use the replacemen...
LogicalResult convertOperation(Operation &op, llvm::IRBuilderBase &builder)
Converts the given MLIR operation into LLVM IR using this translator.
llvm::Function * lookupFunction(StringRef name) const
Finds an LLVM IR function by its name.
void mapBlock(Block *mlir, llvm::BasicBlock *llvm)
Stores the mapping between an MLIR block and LLVM IR basic block.
llvm::Module * getLLVMModule()
Returns the LLVM module in which the IR is being constructed.
void stackPop()
Pops the last element from the ModuleTranslation stack.
void forgetMapping(Region &region)
Removes the mapping for blocks contained in the region and values defined in these blocks.
void mapValue(Value mlir, llvm::Value *llvm)
Stores the mapping between an MLIR value and its LLVM IR counterpart.
llvm::CanonicalLoopInfo * lookupOMPLoop(omp::NewCliOp mlir) const
Find the LLVM-IR loop that represents an MLIR loop.
llvm::LLVMContext & getLLVMContext() const
Returns the LLVM context in which the IR is being constructed.
Utility class to translate MLIR LLVM dialect types to LLVM IR.
Definition TypeToLLVM.h:39
unsigned getPreferredAlignment(Type type, const llvm::DataLayout &layout)
Returns the preferred alignment for the type given the data layout.
T findInstanceOf()
Return an instance of the given location type if one is nested under the current location.
Definition Location.h:45
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h: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:738
Value getOperand(unsigned idx)
Definition Operation.h:375
InFlightDiagnostic emitWarning(const Twine &message={})
Emit a warning about this operation, reporting up to any diagnostic handlers that may be listening.
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
unsigned getNumRegions()
Returns the number of regions held by this operation.
Definition Operation.h:726
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:251
unsigned getNumOperands()
Definition Operation.h:371
OperandRange operand_range
Definition Operation.h:396
InFlightDiagnostic emitError(const Twine &message={})
Emit an error about fatal conditions with this operation, reporting up to any diagnostic handlers tha...
OpTy getParentOfType()
Return the closest surrounding parent operation that is of type 'OpTy'.
Definition Operation.h:255
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
std::enable_if_t< llvm::function_traits< std::decay_t< FnT > >::num_args==1, RetT > walk(FnT &&callback)
Walk the operation by calling the callback for each nested operation (including this one),...
Definition Operation.h:849
user_range getUsers()
Returns a range of all users.
Definition Operation.h:925
result_range getResults()
Definition Operation.h:440
MLIRContext * getContext()
Return the context this operation is associated with.
Definition Operation.h:233
InFlightDiagnostic emitOpError(const Twine &message={})
Emit an error with the op name prefixed, like "'dim' op " which is convenient for verifiers.
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:429
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
Block & front()
Definition Region.h:65
BlockArgListType getArguments()
Definition Region.h:94
bool empty()
Definition Region.h:60
unsigned getNumArguments()
Definition Region.h:136
iterator begin()
Definition Region.h:55
Operation * getParentOp()
Return the parent operation this region is attached to.
Definition Region.h: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:91
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:113
bool allocaUsesRequireSharedMem(Value alloc)
Check whether the value representing an allocation, assumed to have been defined in a shared device c...
Definition Utils.cpp:98
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:732
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:310
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:1380
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.