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//===----------------------------------------------------------------------===//
20#include "mlir/IR/Operation.h"
22#include "mlir/Support/LLVM.h"
25
26#include "llvm/ADT/ArrayRef.h"
27#include "llvm/ADT/SmallVector.h"
28#include "llvm/ADT/TypeSwitch.h"
29#include "llvm/Frontend/OpenMP/OMPConstants.h"
30#include "llvm/Frontend/OpenMP/OMPIRBuilder.h"
31#include "llvm/IR/Constants.h"
32#include "llvm/IR/DebugInfoMetadata.h"
33#include "llvm/IR/DerivedTypes.h"
34#include "llvm/IR/IRBuilder.h"
35#include "llvm/IR/MDBuilder.h"
36#include "llvm/IR/ReplaceConstant.h"
37#include "llvm/Support/AMDGPUAddrSpace.h"
38#include "llvm/Support/FileSystem.h"
39#include "llvm/Support/NVPTXAddrSpace.h"
40#include "llvm/Support/VirtualFileSystem.h"
41#include "llvm/TargetParser/Triple.h"
42#include "llvm/Transforms/Utils/ModuleUtils.h"
43
44#include <cstdint>
45#include <iterator>
46#include <numeric>
47#include <optional>
48#include <utility>
49
50using namespace mlir;
51
52namespace {
53static llvm::omp::ScheduleKind
54convertToScheduleKind(std::optional<omp::ClauseScheduleKind> schedKind) {
55 if (!schedKind.has_value())
56 return llvm::omp::OMP_SCHEDULE_Default;
57 switch (schedKind.value()) {
58 case omp::ClauseScheduleKind::Static:
59 return llvm::omp::OMP_SCHEDULE_Static;
60 case omp::ClauseScheduleKind::Dynamic:
61 return llvm::omp::OMP_SCHEDULE_Dynamic;
62 case omp::ClauseScheduleKind::Guided:
63 return llvm::omp::OMP_SCHEDULE_Guided;
64 case omp::ClauseScheduleKind::Auto:
65 return llvm::omp::OMP_SCHEDULE_Auto;
66 case omp::ClauseScheduleKind::Runtime:
67 return llvm::omp::OMP_SCHEDULE_Runtime;
68 case omp::ClauseScheduleKind::Distribute:
69 return llvm::omp::OMP_SCHEDULE_Distribute;
70 }
71 llvm_unreachable("unhandled schedule clause argument");
72}
73
74/// ModuleTranslation stack frame for OpenMP operations. This keeps track of the
75/// insertion points for allocas.
76class OpenMPAllocStackFrame
77 : public StateStackFrameBase<OpenMPAllocStackFrame> {
78public:
80
81 explicit OpenMPAllocStackFrame(
82 llvm::OpenMPIRBuilder::InsertPointTy allocaIP,
83 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks)
84 : allocInsertPoint(allocaIP), deallocBlocks(deallocBlocks) {}
85 llvm::OpenMPIRBuilder::InsertPointTy allocInsertPoint;
86 llvm::SmallVector<llvm::BasicBlock *> deallocBlocks;
87};
88
89/// Stack frame to hold a \see llvm::CanonicalLoopInfo representing the
90/// collapsed canonical loop information corresponding to an \c omp.loop_nest
91/// operation.
92class OpenMPLoopInfoStackFrame
93 : public StateStackFrameBase<OpenMPLoopInfoStackFrame> {
94public:
95 MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(OpenMPLoopInfoStackFrame)
96 llvm::CanonicalLoopInfo *loopInfo = nullptr;
97};
98
99/// Custom error class to signal translation errors that don't need reporting,
100/// since encountering them will have already triggered relevant error messages.
101///
102/// Its purpose is to serve as the glue between MLIR failures represented as
103/// \see LogicalResult instances and \see llvm::Error instances used to
104/// propagate errors through the \see llvm::OpenMPIRBuilder. Generally, when an
105/// error of the first type is raised, a message is emitted directly (the \see
106/// LogicalResult itself does not hold any information). If we need to forward
107/// this error condition as an \see llvm::Error while avoiding triggering some
108/// redundant error reporting later on, we need a custom \see llvm::ErrorInfo
109/// class to just signal this situation has happened.
110///
111/// For example, this class should be used to trigger errors from within
112/// callbacks passed to the \see OpenMPIRBuilder when they were triggered by the
113/// translation of their own regions. This unclutters the error log from
114/// redundant messages.
115class PreviouslyReportedError
116 : public llvm::ErrorInfo<PreviouslyReportedError> {
117public:
118 void log(raw_ostream &) const override {
119 // Do not log anything.
120 }
121
122 std::error_code convertToErrorCode() const override {
123 llvm_unreachable(
124 "PreviouslyReportedError doesn't support ECError conversion");
125 }
126
127 // Used by ErrorInfo::classID.
128 static char ID;
129};
130
131char PreviouslyReportedError::ID = 0;
132
133/*
134 * Custom class for processing linear clause for omp.wsloop
135 * and omp.simd. Linear clause translation requires setup,
136 * initialization, update, and finalization at varying
137 * basic blocks in the IR. This class helps maintain
138 * internal state to allow consistent translation in
139 * each of these stages.
140 */
141
142class LinearClauseProcessor {
143
144private:
145 SmallVector<llvm::Value *> linearPreconditionVars;
146 SmallVector<llvm::Value *> linearLoopBodyTemps;
147 SmallVector<llvm::Value *> linearOrigVal;
148 SmallVector<llvm::Value *> linearSteps;
149 SmallVector<llvm::Type *> linearVarTypes;
150 llvm::BasicBlock *linearFinalizationBB;
151 llvm::BasicBlock *linearExitBB;
152 llvm::BasicBlock *linearLastIterExitBB;
153 Value linearLoopIV;
154
155public:
156 // Register type for the linear variables
157 void registerType(LLVM::ModuleTranslation &moduleTranslation,
158 mlir::Attribute &ty) {
159 linearVarTypes.push_back(moduleTranslation.convertType(
160 mlir::cast<mlir::TypeAttr>(ty).getValue()));
161 }
162
163 // Allocate space for linear variabes
164 void createLinearVar(llvm::IRBuilderBase &builder,
165 LLVM::ModuleTranslation &moduleTranslation,
166 llvm::Value *linearVar, int idx) {
167 linearPreconditionVars.push_back(
168 builder.CreateAlloca(linearVarTypes[idx], nullptr, ".linear_var"));
169 llvm::Value *linearLoopBodyTemp =
170 builder.CreateAlloca(linearVarTypes[idx], nullptr, ".linear_result");
171 linearOrigVal.push_back(linearVar);
172 linearLoopBodyTemps.push_back(linearLoopBodyTemp);
173 }
174
175 // Initialize linear step
176 inline void initLinearStep(LLVM::ModuleTranslation &moduleTranslation,
177 mlir::Value &linearStep) {
178 linearSteps.push_back(moduleTranslation.lookupValue(linearStep));
179 }
180
181 // Emit IR for initialization of linear variables
182 void initLinearVar(llvm::IRBuilderBase &builder,
183 LLVM::ModuleTranslation &moduleTranslation,
184 llvm::BasicBlock *loopPreHeader) {
185 builder.SetInsertPoint(loopPreHeader->getTerminator());
186 for (size_t index = 0; index < linearOrigVal.size(); index++) {
187 llvm::LoadInst *linearVarLoad =
188 builder.CreateLoad(linearVarTypes[index], linearOrigVal[index]);
189 builder.CreateStore(linearVarLoad, linearPreconditionVars[index]);
190 }
191 }
192
193 // Find linear iteration variable and save it for later updates
194 LogicalResult initLinearIV(omp::SimdOp simdOp) {
195 auto loopOp = cast<omp::LoopNestOp>(simdOp.getWrappedLoop());
196 // NOTE iteration variables can only be linear in non-nested loops.
197 if (loopOp.getIVs().size() != 1)
198 return success();
199 // Currently, frontends using `omp.simd` always generate a store from the
200 // `omp.loop_nest`'s IV to the corresponding iteration variable.
201 // We leverage this to find the linear iteration variable.
202 //
203 // TODO Add an attribute to `omp.loop_nest` that explicitly lists the
204 // variables that correspond to the loop induction variables.
205 BlockArgument arg = loopOp.getIVs().front();
206 for (const Operation *user : arg.getUsers()) {
207 if (auto storeOp = dyn_cast<LLVM::StoreOp>(user)) {
208 for (Value linearVar : simdOp.getLinearVars()) {
209 if (linearVar == storeOp.getAddr()) {
210 if (linearLoopIV && linearLoopIV != linearVar)
211 return simdOp.emitError(
212 "Could not determine the linear variable associated with the "
213 "loop nest induction variable");
214 linearLoopIV = linearVar;
215 }
216 }
217 }
218 }
219 return success();
220 }
221
222 // Emit IR for updating Linear variables
223 void updateLinearVar(llvm::IRBuilderBase &builder, llvm::BasicBlock *loopBody,
224 llvm::Value *loopInductionVar) {
225 builder.SetInsertPoint(loopBody->getTerminator());
226 for (size_t index = 0; index < linearPreconditionVars.size(); index++) {
227 llvm::Type *linearVarType = linearVarTypes[index];
228 llvm::Value *iv = loopInductionVar;
229 llvm::Value *step = linearSteps[index];
230
231 if (!iv->getType()->isIntegerTy())
232 llvm_unreachable("OpenMP loop induction variable must be an integer "
233 "type");
234
235 if (linearVarType->isIntegerTy()) {
236 // Integer path: normalize all arithmetic to linearVarType
237 iv = builder.CreateSExtOrTrunc(iv, linearVarType);
238 step = builder.CreateSExtOrTrunc(step, linearVarType);
239
240 llvm::LoadInst *linearVarStart =
241 builder.CreateLoad(linearVarType, linearPreconditionVars[index]);
242 llvm::Value *mulInst = builder.CreateMul(iv, step);
243 llvm::Value *addInst = builder.CreateAdd(linearVarStart, mulInst);
244 builder.CreateStore(addInst, linearLoopBodyTemps[index]);
245 } else if (linearVarType->isFloatingPointTy()) {
246 // Float path: perform multiply in integer, then convert to float
247 step = builder.CreateSExtOrTrunc(step, iv->getType());
248 llvm::Value *mulInst = builder.CreateMul(iv, step);
249
250 llvm::LoadInst *linearVarStart =
251 builder.CreateLoad(linearVarType, linearPreconditionVars[index]);
252 llvm::Value *mulFp = builder.CreateSIToFP(mulInst, linearVarType);
253 llvm::Value *addInst = builder.CreateFAdd(linearVarStart, mulFp);
254 builder.CreateStore(addInst, linearLoopBodyTemps[index]);
255 } else {
256 llvm_unreachable(
257 "Linear variable must be of integer or floating-point type");
258 }
259 }
260 }
261
262 // Emit IR for updating linear iteration variables on loop exit
263 void updateLinearIV(llvm::IRBuilderBase &builder,
264 LLVM::ModuleTranslation &moduleTranslation) {
265 if (!linearLoopIV)
266 return;
267 llvm::Value *linearIV = moduleTranslation.lookupValue(linearLoopIV);
268
269 // Find linearIV's index
270 size_t index;
271 for (index = 0; index < linearOrigVal.size(); index++)
272 if (linearIV == linearOrigVal[index])
273 break;
274 if (index == linearOrigVal.size())
275 return;
276
277 // Add one more step to the linear iteration variable
278 llvm::Type *varType = linearVarTypes[index];
279 llvm::Value *var = linearLoopBodyTemps[index];
280 llvm::Value *step = linearSteps[index];
281 if (!varType->isIntegerTy())
282 llvm_unreachable("Linear iteration variable must be of integer type");
283
284 step = builder.CreateSExtOrTrunc(step, varType);
285 llvm::Value *val = builder.CreateLoad(varType, var);
286 llvm::Value *addInst = builder.CreateAdd(val, step);
287 builder.CreateStore(addInst, var);
288 }
289
290 // Linear variable finalization is conditional on the last logical iteration.
291 // Create BB splits to manage the same.
292 void splitLinearFiniBB(llvm::IRBuilderBase &builder,
293 llvm::BasicBlock *loopExit) {
294 linearFinalizationBB = loopExit->splitBasicBlock(
295 loopExit->getTerminator(), "omp_loop.linear_finalization");
296 linearExitBB = linearFinalizationBB->splitBasicBlock(
297 linearFinalizationBB->getTerminator(), "omp_loop.linear_exit");
298 linearLastIterExitBB = linearFinalizationBB->splitBasicBlock(
299 linearFinalizationBB->getTerminator(), "omp_loop.linear_lastiter_exit");
300 }
301
302 // Finalize the linear vars
303 llvm::OpenMPIRBuilder::InsertPointOrErrorTy
304 finalizeLinearVar(llvm::IRBuilderBase &builder,
305 LLVM::ModuleTranslation &moduleTranslation,
306 llvm::Value *lastIter) {
307 // Emit condition to check whether last logical iteration is being executed
308 builder.SetInsertPoint(linearFinalizationBB->getTerminator());
309 llvm::Value *loopLastIterLoad = builder.CreateLoad(
310 llvm::Type::getInt32Ty(builder.getContext()), lastIter);
311 llvm::Value *isLast =
312 builder.CreateCmp(llvm::CmpInst::ICMP_NE, loopLastIterLoad,
313 llvm::ConstantInt::get(
314 llvm::Type::getInt32Ty(builder.getContext()), 0));
315 // Store the linear variable values to original variables.
316 builder.SetInsertPoint(linearLastIterExitBB->getTerminator());
317 for (size_t index = 0; index < linearOrigVal.size(); index++) {
318 llvm::LoadInst *linearVarTemp =
319 builder.CreateLoad(linearVarTypes[index], linearLoopBodyTemps[index]);
320 builder.CreateStore(linearVarTemp, linearOrigVal[index]);
321 }
322
323 // Create conditional branch such that the linear variable
324 // values are stored to original variables only at the
325 // last logical iteration
326 builder.SetInsertPoint(linearFinalizationBB->getTerminator());
327 builder.CreateCondBr(isLast, linearLastIterExitBB, linearExitBB);
328 linearFinalizationBB->getTerminator()->eraseFromParent();
329 // Emit barrier
330 builder.SetInsertPoint(linearExitBB->getTerminator());
331 return moduleTranslation.getOpenMPBuilder()->createBarrier(
332 builder, llvm::omp::OMPD_barrier);
333 }
334
335 // Emit stores for linear variables. Useful in case of SIMD
336 // construct.
337 void emitStoresForLinearVar(llvm::IRBuilderBase &builder) {
338 for (size_t index = 0; index < linearOrigVal.size(); index++) {
339 llvm::LoadInst *linearVarTemp =
340 builder.CreateLoad(linearVarTypes[index], linearLoopBodyTemps[index]);
341 builder.CreateStore(linearVarTemp, linearOrigVal[index]);
342 }
343 }
344
345 // Rewrite all uses of the original variable, in the basic blocks in the
346 // [startBB, endBB] interval, with the linear variable in-place.
347 void rewriteInPlace(llvm::IRBuilderBase &builder, llvm::BasicBlock *startBB,
348 llvm::BasicBlock *endBB, size_t varIndex) {
349 llvm::SmallVector<llvm::BasicBlock *, 32> worklist;
350 llvm::SmallPtrSet<llvm::BasicBlock *, 32> collectedBBs;
351
352 assert(startBB && endBB && "Invalid startBB/endBB");
353
354 // Collect basic blocks from startBB to endBB.
355 worklist.push_back(startBB);
356 collectedBBs.insert(startBB);
357
358 while (!worklist.empty()) {
359 llvm::BasicBlock *bb = worklist.pop_back_val();
360
361 if (bb == endBB)
362 continue;
363
364 for (llvm::BasicBlock *succ : llvm::successors(bb)) {
365 if (collectedBBs.insert(succ).second)
366 worklist.push_back(succ);
367 }
368 }
369
370 // Rewrite all uses in the collected BBs.
371 llvm::SmallVector<llvm::User *> users(linearOrigVal[varIndex]->users());
372 for (auto *user : users) {
373 if (auto *userInst = dyn_cast<llvm::Instruction>(user)) {
374 if (collectedBBs.contains(userInst->getParent()))
375 user->replaceUsesOfWith(linearOrigVal[varIndex],
376 linearLoopBodyTemps[varIndex]);
377 }
378 }
379 }
380};
381
382} // namespace
383
384/// Looks up from the operation from and returns the PrivateClauseOp with
385/// name symbolName
386static omp::PrivateClauseOp findPrivatizer(Operation *from,
387 SymbolRefAttr symbolName) {
388 omp::PrivateClauseOp privatizer =
390 symbolName);
391 assert(privatizer && "privatizer not found in the symbol table");
392 return privatizer;
393}
394
395/// Check whether translation to LLVM IR for the given operation is currently
396/// supported. If not, descriptive diagnostics will be emitted to let users know
397/// this is a not-yet-implemented feature.
398///
399/// \returns success if no unimplemented features are needed to translate the
400/// given operation.
401static LogicalResult checkImplementationStatus(Operation &op) {
402 auto todo = [&op](StringRef clauseName) {
403 return op.emitError() << "not yet implemented: Unhandled clause "
404 << clauseName << " in " << op.getName()
405 << " operation";
406 };
407
408 auto checkAllocate = [&todo](auto op, LogicalResult &result) {
409 if (!op.getAllocateVars().empty() || !op.getAllocatorVars().empty())
410 result = todo("allocate");
411 };
412 auto checkBare = [&todo](auto op, LogicalResult &result) {
413 if (op.getKernelType() == omp::TargetExecMode::bare)
414 result = todo("ompx_bare");
415 };
416 auto checkDepend = [&todo](auto op, LogicalResult &result) {
417 if (!op.getDependVars().empty() || op.getDependKinds())
418 result = todo("depend");
419 };
420 auto checkHint = [](auto op, LogicalResult &) {
421 if (op.getHint())
422 op.emitWarning("hint clause discarded");
423 };
424 auto checkInReduction = [&todo](auto op, LogicalResult &result) {
425 if (isa<omp::TargetOp, omp::TaskOp, omp::TaskloopContextOp>(
426 op.getOperation())) {
427 if (auto byrefAttr = op.getInReductionByref()) {
428 for (bool isByRef : *byrefAttr) {
429 if (isByRef) {
430 result = todo("in_reduction with byref modifier");
431 return;
432 }
433 }
434 }
435 if (isa<omp::TargetOp>(op.getOperation())) {
436 if (auto inReductionSyms = op.getInReductionSyms()) {
437 for (auto sym :
438 (*inReductionSyms).template getAsRange<SymbolRefAttr>()) {
439 auto decl =
441 op, sym);
442 assert(decl &&
443 "symbol resolution should be guaranteed by the op verifier");
444 if (decl.getInitializerRegion().front().getNumArguments() != 1) {
445 result = todo("in_reduction with two-argument initializer");
446 return;
447 }
448 if (!decl.getCleanupRegion().empty()) {
449 result = todo("in_reduction with cleanup region");
450 return;
451 }
452 }
453 }
454 }
455 } else if (!op.getInReductionVars().empty() || op.getInReductionByref() ||
456 op.getInReductionSyms()) {
457 result = todo("in_reduction");
458 }
459 };
460 auto checkNowait = [&todo](auto op, LogicalResult &result) {
461 if (op.getNowait())
462 result = todo("nowait");
463 };
464 auto checkOrder = [&todo](auto op, LogicalResult &result) {
465 if (op.getOrder() || op.getOrderMod())
466 result = todo("order");
467 };
468 auto checkPrivate = [&todo](auto op, LogicalResult &result) {
469 if (!op.getPrivateVars().empty() || op.getPrivateSyms())
470 result = todo("privatization");
471 };
472 auto checkReduction = [&todo](auto op, LogicalResult &result) {
473 if (isa<omp::TeamsOp>(op))
474 if (!op.getReductionVars().empty() || op.getReductionByref() ||
475 op.getReductionSyms())
476 result = todo("reduction");
477 if (op.getReductionMod() &&
478 op.getReductionMod().value() != omp::ReductionModifier::defaultmod) {
479 omp::ReductionModifier mod = op.getReductionMod().value();
480 // The `task` reduction modifier is supported on the parallel and
481 // worksharing (do/for and sections) constructs. Other modifiers, and the
482 // `task` modifier on other constructs, are not yet implemented.
483 bool taskModifierSupported =
484 mod == omp::ReductionModifier::task &&
485 isa<omp::ParallelOp, omp::WsloopOp, omp::SectionsOp>(op);
486 if (!taskModifierSupported) {
487 result = todo("reduction with modifier");
488 } else if (auto byref = op.getReductionByref()) {
489 // The task reduction modifier lowering only handles non-byref
490 // reductions for now.
491 for (bool isByRef : *byref)
492 if (isByRef) {
493 result = todo("task reduction modifier with by-ref reduction");
494 break;
495 }
496 }
497 }
498 };
499 auto checkTaskReductionByref = [&todo](auto op, LogicalResult &result) {
500 if (auto byrefAttr = op.getTaskReductionByref())
501 for (bool isByRef : *byrefAttr)
502 if (isByRef) {
503 result = todo("task_reduction with byref modifier");
504 return;
505 }
506 };
507 auto checkReductionByref = [&todo](auto op, LogicalResult &result) {
508 if (auto byrefAttr = op.getReductionByref())
509 for (bool isByRef : *byrefAttr)
510 if (isByRef) {
511 result = todo("reduction with byref modifier");
512 return;
513 }
514 };
515 auto checkNumTeams = [&todo](auto op, LogicalResult &result) {
516 if (op.hasNumTeamsMultiDim())
517 result = todo("num_teams with multi-dimensional values");
518 };
519 auto checkNumThreads = [&todo](auto op, LogicalResult &result) {
520 if (op.hasNumThreadsMultiDim())
521 result = todo("num_threads with multi-dimensional values");
522 };
523
524 auto checkThreadLimit = [&todo](auto op, LogicalResult &result) {
525 if (op.hasThreadLimitMultiDim())
526 result = todo("thread_limit with multi-dimensional values");
527 };
528 auto checkMap = [&todo](auto op, LogicalResult &result) {
529 if (!op.getMapIterated().empty())
530 result = todo("map/motion clause with iterator modifier");
531 };
532
533 auto checkDynGroupprivate = [&todo](auto op, LogicalResult &result) {
534 if (op.getDynGroupprivateSize())
535 result = todo("dyn_groupprivate");
536 };
537
538 LogicalResult result = success();
540 .Case([&](omp::DistributeOp op) {
541 checkAllocate(op, result);
542 checkOrder(op, result);
543 })
544 .Case([&](omp::SectionsOp op) {
545 checkAllocate(op, result);
546 checkPrivate(op, result);
547 checkReduction(op, result);
548 })
549 .Case([&](omp::ScopeOp op) {
550 checkAllocate(op, result);
551 checkReduction(op, result);
552 })
553 .Case([&](omp::SingleOp op) {
554 checkAllocate(op, result);
555 checkPrivate(op, result);
556 })
557 .Case([&](omp::TeamsOp op) {
558 checkAllocate(op, result);
559 checkPrivate(op, result);
560 checkNumTeams(op, result);
561 checkThreadLimit(op, result);
562 checkDynGroupprivate(op, result);
563 })
564 .Case([&](omp::TaskOp op) {
565 checkAllocate(op, result);
566 checkInReduction(op, result);
567 })
568 .Case([&](omp::TaskgroupOp op) {
569 checkAllocate(op, result);
570 checkTaskReductionByref(op, result);
571 })
572 .Case([&](omp::TaskwaitOp op) { checkNowait(op, result); })
573 .Case([&](omp::TaskloopContextOp op) {
574 checkAllocate(op, result);
575 checkInReduction(op, result);
576 checkReduction(op, result);
577 checkReductionByref(op, result);
578 })
579 .Case([&](omp::WsloopOp op) {
580 checkAllocate(op, result);
581 checkOrder(op, result);
582 checkReduction(op, result);
583 })
584 .Case([&](omp::ParallelOp op) {
585 checkAllocate(op, result);
586 checkReduction(op, result);
587 checkNumThreads(op, result);
588 })
589 .Case([&](omp::SimdOp op) { checkReduction(op, result); })
590 .Case<omp::AtomicReadOp, omp::AtomicWriteOp, omp::AtomicUpdateOp,
591 omp::AtomicCaptureOp>([&](auto op) { checkHint(op, result); })
592 .Case([&](omp::AtomicCompareOp op) {
593 checkHint(op, result);
594 Region &region = op.getRegion();
595 if (region.empty())
596 return;
597 mlir::Type argType = region.front().getArgument(0).getType();
598 auto structTy = dyn_cast<LLVM::LLVMStructType>(argType);
599 if (!structTy)
600 return;
601 DataLayout dl = DataLayout(op->getParentOfType<ModuleOp>());
602 unsigned totalBits = dl.getTypeSizeInBits(structTy);
603 if (totalBits > 128)
604 result = todo("compare for complex types wider than 128 bits");
605 })
606 .Case<omp::TargetEnterDataOp, omp::TargetExitDataOp>([&](auto op) {
607 checkDepend(op, result);
608 checkMap(op, result);
609 })
610 .Case([&](omp::TargetUpdateOp op) {
611 checkDepend(op, result);
612 checkMap(op, result);
613 })
614 .Case([&](omp::TargetOp op) {
615 checkAllocate(op, result);
616 checkBare(op, result);
617 checkInReduction(op, result);
618 checkMap(op, result);
619 checkThreadLimit(op, result);
620 })
621 .Case([&](omp::TargetDataOp op) { checkMap(op, result); })
622 .Case([&](omp::DeclareMapperInfoOp op) { checkMap(op, result); })
623 .Default([](Operation &) {
624 // Assume all clauses for an operation can be translated unless they are
625 // checked above.
626 });
627 return result;
628}
629
630static LogicalResult handleError(llvm::Error error, Operation &op) {
631 LogicalResult result = success();
632 if (error) {
633 llvm::handleAllErrors(
634 std::move(error),
635 [&](const PreviouslyReportedError &) { result = failure(); },
636 [&](const llvm::ErrorInfoBase &err) {
637 result = op.emitError(err.message());
638 });
639 }
640 return result;
641}
642
643template <typename T>
644static LogicalResult handleError(llvm::Expected<T> &result, Operation &op) {
645 if (!result)
646 return handleError(result.takeError(), op);
647
648 return success();
649}
650
651/// Find the insertion point for allocas given the current insertion point for
652/// normal operations in the builder.
653static llvm::OpenMPIRBuilder::InsertPointTy findAllocInsertPoints(
654 llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation,
655 llvm::SmallVectorImpl<llvm::BasicBlock *> *deallocBlocks = nullptr) {
656 // If there is an allocation insertion point on stack, i.e. we are in a nested
657 // operation and a specific point was provided by some surrounding operation,
658 // use it.
659 llvm::OpenMPIRBuilder::InsertPointTy allocInsertPoint;
660 llvm::ArrayRef<llvm::BasicBlock *> deallocInsertPoints;
661 WalkResult walkResult = moduleTranslation.stackWalk<OpenMPAllocStackFrame>(
662 [&](OpenMPAllocStackFrame &frame) {
663 allocInsertPoint = frame.allocInsertPoint;
664 deallocInsertPoints = frame.deallocBlocks;
665 return WalkResult::interrupt();
666 });
667 // In cases with multiple levels of outlining, the tree walk might find an
668 // insertion point that is inside the original function while the builder
669 // insertion point is inside the outlined function. We need to make sure that
670 // we do not use it in those cases.
671 if (walkResult.wasInterrupted() &&
672 allocInsertPoint.getBlock()->getParent() ==
673 builder.GetInsertBlock()->getParent()) {
674 if (deallocBlocks)
675 deallocBlocks->insert(deallocBlocks->end(), deallocInsertPoints.begin(),
676 deallocInsertPoints.end());
677 return allocInsertPoint;
678 }
679
680 // Otherwise, insert to the entry block of the surrounding function.
681 // If the current IRBuilder InsertPoint is the function's entry, it cannot
682 // also be used for alloca insertion which would result in insertion order
683 // confusion. Create a new BasicBlock for the Builder and use the entry block
684 // for the allocs.
685 // TODO: Create a dedicated alloca BasicBlock at function creation such that
686 // we do not need to move the current InsertPoint here.
687 if (builder.GetInsertBlock() ==
688 &builder.GetInsertBlock()->getParent()->getEntryBlock()) {
689 assert(builder.GetInsertPoint() == builder.GetInsertBlock()->end() &&
690 "Assuming end of basic block");
691 llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(
692 builder.getContext(), "entry", builder.GetInsertBlock()->getParent(),
693 builder.GetInsertBlock()->getNextNode());
694 builder.CreateBr(entryBB);
695 builder.SetInsertPoint(entryBB);
696 }
697
698 // Collect exit blocks, which is where explicit deallocations should happen in
699 // this case.
700 if (deallocBlocks) {
701 for (llvm::BasicBlock &block : *builder.GetInsertBlock()->getParent()) {
702 // TODO: This currently results in no blocks being added to the list when
703 // all exit blocks of the enclosing function have not been lowered before
704 // this is reached.
705 llvm::Instruction *terminator = block.getTerminatorOrNull();
706 if (isa_and_present<llvm::ReturnInst>(terminator))
707 deallocBlocks->emplace_back(&block);
708 }
709 }
710
711 llvm::BasicBlock &funcEntryBlock =
712 builder.GetInsertBlock()->getParent()->getEntryBlock();
713 return llvm::OpenMPIRBuilder::InsertPointTy(
714 &funcEntryBlock, funcEntryBlock.getFirstInsertionPt());
715}
716
717/// Find the loop information structure for the loop nest being translated. It
718/// will return a `null` value unless called from the translation function for
719/// a loop wrapper operation after successfully translating its body.
720static llvm::CanonicalLoopInfo *
722 llvm::CanonicalLoopInfo *loopInfo = nullptr;
723 moduleTranslation.stackWalk<OpenMPLoopInfoStackFrame>(
724 [&](OpenMPLoopInfoStackFrame &frame) {
725 loopInfo = frame.loopInfo;
726 return WalkResult::interrupt();
727 });
728 return loopInfo;
729}
730
731/// Converts the given region that appears within an OpenMP dialect operation to
732/// LLVM IR, creating a branch from the `sourceBlock` to the entry block of the
733/// region, and a branch from any block with an successor-less OpenMP terminator
734/// to `continuationBlock`. Populates `continuationBlockPHIs` with the PHI nodes
735/// of the continuation block if provided.
737 Region &region, StringRef blockName, llvm::IRBuilderBase &builder,
738 LLVM::ModuleTranslation &moduleTranslation,
739 SmallVectorImpl<llvm::PHINode *> *continuationBlockPHIs = nullptr) {
740 bool isLoopWrapper = isa<omp::LoopWrapperInterface>(region.getParentOp());
741
742 llvm::BasicBlock *continuationBlock =
743 splitBB(builder, true, "omp.region.cont");
744 llvm::BasicBlock *sourceBlock = builder.GetInsertBlock();
745
746 llvm::LLVMContext &llvmContext = builder.getContext();
747 for (Block &bb : region) {
748 llvm::BasicBlock *llvmBB = llvm::BasicBlock::Create(
749 llvmContext, blockName, builder.GetInsertBlock()->getParent(),
750 builder.GetInsertBlock()->getNextNode());
751 moduleTranslation.mapBlock(&bb, llvmBB);
752 }
753
754 llvm::Instruction *sourceTerminator = sourceBlock->getTerminator();
755
756 // Terminators (namely YieldOp) may be forwarding values to the region that
757 // need to be available in the continuation block. Collect the types of these
758 // operands in preparation of creating PHI nodes. This is skipped for loop
759 // wrapper operations, for which we know in advance they have no terminators.
760 SmallVector<llvm::Type *> continuationBlockPHITypes;
761 unsigned numYields = 0;
762
763 if (!isLoopWrapper) {
764 bool operandsProcessed = false;
765 for (Block &bb : region.getBlocks()) {
766 if (omp::YieldOp yield = dyn_cast<omp::YieldOp>(bb.getTerminator())) {
767 if (!operandsProcessed) {
768 for (unsigned i = 0, e = yield->getNumOperands(); i < e; ++i) {
769 continuationBlockPHITypes.push_back(
770 moduleTranslation.convertType(yield->getOperand(i).getType()));
771 }
772 operandsProcessed = true;
773 } else {
774 assert(continuationBlockPHITypes.size() == yield->getNumOperands() &&
775 "mismatching number of values yielded from the region");
776 for (unsigned i = 0, e = yield->getNumOperands(); i < e; ++i) {
777 llvm::Type *operandType =
778 moduleTranslation.convertType(yield->getOperand(i).getType());
779 (void)operandType;
780 assert(continuationBlockPHITypes[i] == operandType &&
781 "values of mismatching types yielded from the region");
782 }
783 }
784 numYields++;
785 }
786 }
787 }
788
789 // Insert PHI nodes in the continuation block for any values forwarded by the
790 // terminators in this region.
791 if (!continuationBlockPHITypes.empty())
792 assert(
793 continuationBlockPHIs &&
794 "expected continuation block PHIs if converted regions yield values");
795 if (continuationBlockPHIs) {
796 llvm::IRBuilderBase::InsertPointGuard guard(builder);
797 continuationBlockPHIs->reserve(continuationBlockPHITypes.size());
798 builder.SetInsertPoint(continuationBlock, continuationBlock->begin());
799 for (llvm::Type *ty : continuationBlockPHITypes)
800 continuationBlockPHIs->push_back(builder.CreatePHI(ty, numYields));
801 }
802
803 // Convert blocks one by one in topological order to ensure
804 // defs are converted before uses.
806 for (Block *bb : blocks) {
807 llvm::BasicBlock *llvmBB = moduleTranslation.lookupBlock(bb);
808 // Retarget the branch of the entry block to the entry block of the
809 // converted region (regions are single-entry).
810 if (bb->isEntryBlock()) {
811 assert(sourceTerminator->getNumSuccessors() == 1 &&
812 "provided entry block has multiple successors");
813 assert(sourceTerminator->getSuccessor(0) == continuationBlock &&
814 "ContinuationBlock is not the successor of the entry block");
815 sourceTerminator->setSuccessor(0, llvmBB);
816 }
817
818 llvm::IRBuilderBase::InsertPointGuard guard(builder);
819 if (failed(
820 moduleTranslation.convertBlock(*bb, bb->isEntryBlock(), builder)))
821 return llvm::make_error<PreviouslyReportedError>();
822
823 // Create a direct branch here for loop wrappers to prevent their lack of a
824 // terminator from causing a crash below.
825 if (isLoopWrapper) {
826 builder.CreateBr(continuationBlock);
827 continue;
828 }
829
830 // Special handling for `omp.yield` and `omp.terminator` (we may have more
831 // than one): they return the control to the parent OpenMP dialect operation
832 // so replace them with the branch to the continuation block. We handle this
833 // here to avoid relying inter-function communication through the
834 // ModuleTranslation class to set up the correct insertion point. This is
835 // also consistent with MLIR's idiom of handling special region terminators
836 // in the same code that handles the region-owning operation.
837 Operation *terminator = bb->getTerminator();
838 if (isa<omp::TerminatorOp, omp::YieldOp>(terminator)) {
839 builder.CreateBr(continuationBlock);
840
841 for (unsigned i = 0, e = terminator->getNumOperands(); i < e; ++i)
842 (*continuationBlockPHIs)[i]->addIncoming(
843 moduleTranslation.lookupValue(terminator->getOperand(i)), llvmBB);
844 }
845 }
846 // After all blocks have been traversed and values mapped, connect the PHI
847 // nodes to the results of preceding blocks.
848 LLVM::detail::connectPHINodes(region, moduleTranslation);
849
850 // Remove the blocks and values defined in this region from the mapping since
851 // they are not visible outside of this region. This allows the same region to
852 // be converted several times, that is cloned, without clashes, and slightly
853 // speeds up the lookups.
854 moduleTranslation.forgetMapping(region);
855
856 return continuationBlock;
857}
858
859/// Convert ProcBindKind from MLIR-generated enum to LLVM enum.
860static llvm::omp::ProcBindKind getProcBindKind(omp::ClauseProcBindKind kind) {
861 switch (kind) {
862 case omp::ClauseProcBindKind::Close:
863 return llvm::omp::ProcBindKind::OMP_PROC_BIND_close;
864 case omp::ClauseProcBindKind::Master:
865 return llvm::omp::ProcBindKind::OMP_PROC_BIND_master;
866 case omp::ClauseProcBindKind::Primary:
867 return llvm::omp::ProcBindKind::OMP_PROC_BIND_primary;
868 case omp::ClauseProcBindKind::Spread:
869 return llvm::omp::ProcBindKind::OMP_PROC_BIND_spread;
870 }
871 llvm_unreachable("Unknown ClauseProcBindKind kind");
872}
873
874/// Converts an OpenMP 'masked' operation into LLVM IR using OpenMPIRBuilder.
875static LogicalResult
876convertOmpMasked(Operation &opInst, llvm::IRBuilderBase &builder,
877 LLVM::ModuleTranslation &moduleTranslation) {
878 auto maskedOp = cast<omp::MaskedOp>(opInst);
879 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
880
881 if (failed(checkImplementationStatus(opInst)))
882 return failure();
883
884 auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
886 // MaskedOp has only one region associated with it.
887 auto &region = maskedOp.getRegion();
888 builder.restoreIP(codeGenIP);
889 return convertOmpOpRegions(region, "omp.masked.region", builder,
890 moduleTranslation)
891 .takeError();
892 };
893
894 // TODO: Perform finalization actions for variables. This has to be
895 // called for variables which have destructors/finalizers.
896 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
897
898 llvm::Value *filterVal = nullptr;
899 if (auto filterVar = maskedOp.getFilteredThreadId()) {
900 filterVal = moduleTranslation.lookupValue(filterVar);
901 } else {
902 llvm::LLVMContext &llvmContext = builder.getContext();
903 filterVal =
904 llvm::ConstantInt::get(llvm::Type::getInt32Ty(llvmContext), /*V=*/0);
905 }
906 assert(filterVal != nullptr);
907 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
908 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
909 moduleTranslation.getOpenMPBuilder()->createMasked(ompLoc, bodyGenCB,
910 finiCB, filterVal);
911
912 if (failed(handleError(afterIP, opInst)))
913 return failure();
914
915 builder.restoreIP(*afterIP);
916 return success();
917}
918
919/// Converts an OpenMP 'master' operation into LLVM IR using OpenMPIRBuilder.
920static LogicalResult
921convertOmpMaster(Operation &opInst, llvm::IRBuilderBase &builder,
922 LLVM::ModuleTranslation &moduleTranslation) {
923 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
924 auto masterOp = cast<omp::MasterOp>(opInst);
925
926 if (failed(checkImplementationStatus(opInst)))
927 return failure();
928
929 auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
931 // MasterOp has only one region associated with it.
932 auto &region = masterOp.getRegion();
933 builder.restoreIP(codeGenIP);
934 return convertOmpOpRegions(region, "omp.master.region", builder,
935 moduleTranslation)
936 .takeError();
937 };
938
939 // TODO: Perform finalization actions for variables. This has to be
940 // called for variables which have destructors/finalizers.
941 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
942
943 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
944 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
945 moduleTranslation.getOpenMPBuilder()->createMaster(ompLoc, bodyGenCB,
946 finiCB);
947
948 if (failed(handleError(afterIP, opInst)))
949 return failure();
950
951 builder.restoreIP(*afterIP);
952 return success();
953}
954
955/// Converts an OpenMP 'critical' operation into LLVM IR using OpenMPIRBuilder.
956static LogicalResult
957convertOmpCritical(Operation &opInst, llvm::IRBuilderBase &builder,
958 LLVM::ModuleTranslation &moduleTranslation) {
959 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
960 auto criticalOp = cast<omp::CriticalOp>(opInst);
961
962 if (failed(checkImplementationStatus(opInst)))
963 return failure();
964
965 auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
967 // CriticalOp has only one region associated with it.
968 auto &region = cast<omp::CriticalOp>(opInst).getRegion();
969 builder.restoreIP(codeGenIP);
970 return convertOmpOpRegions(region, "omp.critical.region", builder,
971 moduleTranslation)
972 .takeError();
973 };
974
975 // TODO: Perform finalization actions for variables. This has to be
976 // called for variables which have destructors/finalizers.
977 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
978
979 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
980 llvm::LLVMContext &llvmContext = moduleTranslation.getLLVMContext();
981 llvm::Constant *hint = nullptr;
982
983 // If it has a name, it probably has a hint too.
984 if (criticalOp.getNameAttr()) {
985 // The verifiers in OpenMP Dialect guarentee that all the pointers are
986 // non-null
987 auto symbolRef = cast<SymbolRefAttr>(criticalOp.getNameAttr());
988 auto criticalDeclareOp =
990 symbolRef);
991 hint =
992 llvm::ConstantInt::get(llvm::Type::getInt32Ty(llvmContext),
993 static_cast<int>(criticalDeclareOp.getHint()));
994 }
995 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
996 moduleTranslation.getOpenMPBuilder()->createCritical(
997 ompLoc, bodyGenCB, finiCB, criticalOp.getName().value_or(""), hint);
998
999 if (failed(handleError(afterIP, opInst)))
1000 return failure();
1001
1002 builder.restoreIP(*afterIP);
1003 return success();
1004}
1005
1006/// A util to collect info needed to convert delayed privatizers from MLIR to
1007/// LLVM.
1009 template <typename OP>
1011 : blockArgs(
1012 cast<omp::BlockArgOpenMPOpInterface>(*op).getPrivateBlockArgs()) {
1013 mlirVars.reserve(blockArgs.size());
1014 llvmVars.reserve(blockArgs.size());
1015 collectPrivatizationDecls<OP>(op);
1016
1017 for (mlir::Value privateVar : op.getPrivateVars())
1018 mlirVars.push_back(privateVar);
1019 }
1020
1025
1026private:
1027 /// Populates `privatizations` with privatization declarations used for the
1028 /// given op.
1029 template <class OP>
1030 void collectPrivatizationDecls(OP op) {
1031 std::optional<ArrayAttr> attr = op.getPrivateSyms();
1032 if (!attr)
1033 return;
1034
1035 privatizers.reserve(privatizers.size() + attr->size());
1036 for (auto symbolRef : attr->getAsRange<SymbolRefAttr>()) {
1037 privatizers.push_back(findPrivatizer(op, symbolRef));
1038 }
1039 }
1040};
1041
1042/// Populates `reductions` with reduction declarations used in the given op.
1043template <typename T>
1044static void
1047 std::optional<ArrayAttr> attr = op.getReductionSyms();
1048 if (!attr)
1049 return;
1050
1051 reductions.reserve(reductions.size() + op.getNumReductionVars());
1052 for (auto symbolRef : attr->getAsRange<SymbolRefAttr>()) {
1053 reductions.push_back(
1055 op, symbolRef));
1056 }
1057}
1058
1059/// Look up and validate the declare_reduction ops referenced by a
1060/// reduction-like clause on the omp.taskloop.context translation path. Only
1061/// the non-byref, single-init-arg, no-cleanup form is supported in this
1062/// initial cut; richer shapes are rejected here with a diagnostic. \p syms
1063/// is the clause's symbol list (e.g. `getReductionSyms()` or
1064/// `getInReductionSyms()`), \p opName is the textual op name used in
1065/// diagnostics, and \p clauseName distinguishes "reduction" from
1066/// "in_reduction" in those diagnostics.
1068 Operation *contextOp, std::optional<ArrayAttr> syms, StringRef opName,
1069 StringRef clauseName, SmallVectorImpl<omp::DeclareReductionOp> &out) {
1070 if (!syms)
1071 return success();
1072 out.reserve(out.size() + syms->size());
1073 for (auto sym : syms->getAsRange<SymbolRefAttr>()) {
1075 contextOp, sym);
1076 if (!decl)
1077 return contextOp->emitError()
1078 << "failed to resolve " << clauseName
1079 << " declare_reduction symbol " << sym.getRootReference() << " in "
1080 << opName;
1081 if (decl.getInitializerRegion().front().getNumArguments() != 1)
1082 return contextOp->emitError()
1083 << "not yet implemented: " << clauseName
1084 << " with two-argument initializer in " << opName;
1085 if (!decl.getCleanupRegion().empty())
1086 return contextOp->emitError() << "not yet implemented: " << clauseName
1087 << " with cleanup region in " << opName;
1088 if (decl.getReductionRegion().empty())
1089 return contextOp->emitError()
1090 << clauseName << " declare_reduction is missing a combiner region";
1091 out.push_back(decl);
1092 }
1093 return success();
1094}
1095
1096/// Translates the blocks contained in the given region and appends them to at
1097/// the current insertion point of `builder`. The operations of the entry block
1098/// are appended to the current insertion block. If set, `continuationBlockArgs`
1099/// is populated with translated values that correspond to the values
1100/// omp.yield'ed from the region.
1101static LogicalResult inlineConvertOmpRegions(
1102 Region &region, StringRef blockName, llvm::IRBuilderBase &builder,
1103 LLVM::ModuleTranslation &moduleTranslation,
1104 SmallVectorImpl<llvm::Value *> *continuationBlockArgs = nullptr) {
1105 if (region.empty())
1106 return success();
1107
1108 // Special case for single-block regions that don't create additional blocks:
1109 // insert operations without creating additional blocks.
1110 if (region.hasOneBlock()) {
1111 llvm::Instruction *potentialTerminator =
1112 builder.GetInsertBlock()->empty() ? nullptr
1113 : &builder.GetInsertBlock()->back();
1114
1115 if (potentialTerminator && potentialTerminator->isTerminator())
1116 potentialTerminator->removeFromParent();
1117 moduleTranslation.mapBlock(&region.front(), builder.GetInsertBlock());
1118
1119 if (failed(moduleTranslation.convertBlock(
1120 region.front(), /*ignoreArguments=*/true, builder)))
1121 return failure();
1122
1123 // The continuation arguments are simply the translated terminator operands.
1124 if (continuationBlockArgs)
1125 llvm::append_range(
1126 *continuationBlockArgs,
1127 moduleTranslation.lookupValues(region.front().back().getOperands()));
1128
1129 // Drop the mapping that is no longer necessary so that the same region can
1130 // be processed multiple times.
1131 moduleTranslation.forgetMapping(region);
1132
1133 if (potentialTerminator && potentialTerminator->isTerminator()) {
1134 llvm::BasicBlock *block = builder.GetInsertBlock();
1135 if (block->empty()) {
1136 // this can happen for really simple reduction init regions e.g.
1137 // %0 = llvm.mlir.constant(0 : i32) : i32
1138 // omp.yield(%0 : i32)
1139 // because the llvm.mlir.constant (MLIR op) isn't converted into any
1140 // llvm op
1141 potentialTerminator->insertInto(block, block->begin());
1142 } else {
1143 potentialTerminator->insertAfter(&block->back());
1144 }
1145 }
1146
1147 return success();
1148 }
1149
1151 llvm::Expected<llvm::BasicBlock *> continuationBlock =
1152 convertOmpOpRegions(region, blockName, builder, moduleTranslation, &phis);
1153
1154 if (failed(handleError(continuationBlock, *region.getParentOp())))
1155 return failure();
1156
1157 if (continuationBlockArgs)
1158 llvm::append_range(*continuationBlockArgs, phis);
1159 builder.SetInsertPoint(*continuationBlock,
1160 (*continuationBlock)->getFirstInsertionPt());
1161 return success();
1162}
1163
1164namespace {
1165/// Owning equivalents of OpenMPIRBuilder::(Atomic)ReductionGen that are used to
1166/// store lambdas with capture.
1167using OwningReductionGen =
1168 std::function<llvm::OpenMPIRBuilder::InsertPointOrErrorTy(
1169 llvm::OpenMPIRBuilder::InsertPointTy, llvm::Value *, llvm::Value *,
1170 llvm::Value *&)>;
1171using OwningAtomicReductionGen =
1172 std::function<llvm::OpenMPIRBuilder::InsertPointOrErrorTy(
1173 llvm::OpenMPIRBuilder::InsertPointTy, llvm::Type *, llvm::Value *,
1174 llvm::Value *)>;
1175using OwningDataPtrPtrReductionGen =
1176 std::function<llvm::OpenMPIRBuilder::InsertPointOrErrorTy(
1177 llvm::OpenMPIRBuilder::InsertPointTy, llvm::Value *, llvm::Value *&)>;
1178} // namespace
1179
1180/// Create an OpenMPIRBuilder-compatible reduction generator for the given
1181/// reduction declaration. The generator uses `builder` but ignores its
1182/// insertion point.
1183static OwningReductionGen
1184makeReductionGen(omp::DeclareReductionOp decl, llvm::IRBuilderBase &builder,
1185 LLVM::ModuleTranslation &moduleTranslation) {
1186 // The lambda is mutable because we need access to non-const methods of decl
1187 // (which aren't actually mutating it), and we must capture decl by-value to
1188 // avoid the dangling reference after the parent function returns.
1189 OwningReductionGen gen =
1190 [&, decl](llvm::OpenMPIRBuilder::InsertPointTy insertPoint,
1191 llvm::Value *lhs, llvm::Value *rhs,
1192 llvm::Value *&result) mutable
1193 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
1194 moduleTranslation.mapValue(decl.getReductionLhsArg(), lhs);
1195 moduleTranslation.mapValue(decl.getReductionRhsArg(), rhs);
1196 builder.restoreIP(insertPoint);
1198 if (failed(inlineConvertOmpRegions(decl.getReductionRegion(),
1199 "omp.reduction.nonatomic.body", builder,
1200 moduleTranslation, &phis)))
1201 return llvm::createStringError(
1202 "failed to inline `combiner` region of `omp.declare_reduction`");
1203 result = llvm::getSingleElement(phis);
1204 return builder.saveIP();
1205 };
1206 return gen;
1207}
1208
1209/// Create an OpenMPIRBuilder-compatible atomic reduction generator for the
1210/// given reduction declaration. The generator uses `builder` but ignores its
1211/// insertion point. Returns null if there is no atomic region available in the
1212/// reduction declaration.
1213static OwningAtomicReductionGen
1214makeAtomicReductionGen(omp::DeclareReductionOp decl,
1215 llvm::IRBuilderBase &builder,
1216 LLVM::ModuleTranslation &moduleTranslation) {
1217 if (decl.getAtomicReductionRegion().empty())
1218 return OwningAtomicReductionGen();
1219
1220 // The lambda is mutable because we need access to non-const methods of decl
1221 // (which aren't actually mutating it), and we must capture decl by-value to
1222 // avoid the dangling reference after the parent function returns.
1223 OwningAtomicReductionGen atomicGen =
1224 [&, decl](llvm::OpenMPIRBuilder::InsertPointTy insertPoint, llvm::Type *,
1225 llvm::Value *lhs, llvm::Value *rhs) mutable
1226 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
1227 moduleTranslation.mapValue(decl.getAtomicReductionLhsArg(), lhs);
1228 moduleTranslation.mapValue(decl.getAtomicReductionRhsArg(), rhs);
1229 builder.restoreIP(insertPoint);
1231 if (failed(inlineConvertOmpRegions(decl.getAtomicReductionRegion(),
1232 "omp.reduction.atomic.body", builder,
1233 moduleTranslation, &phis)))
1234 return llvm::createStringError(
1235 "failed to inline `atomic` region of `omp.declare_reduction`");
1236 assert(phis.empty());
1237 return builder.saveIP();
1238 };
1239 return atomicGen;
1240}
1241
1242/// Create an OpenMPIRBuilder-compatible `data_ptr_ptr` reduction generator for
1243/// the given reduction declaration. The generator uses `builder` but ignores
1244/// its insertion point. Returns null if there is no `data_ptr_ptr` region
1245/// available in the reduction declaration.
1246static OwningDataPtrPtrReductionGen
1247makeRefDataPtrGen(omp::DeclareReductionOp decl, llvm::IRBuilderBase &builder,
1248 LLVM::ModuleTranslation &moduleTranslation, bool isByRef) {
1249 if (!isByRef || decl.getDataPtrPtrRegion().empty())
1250 return OwningDataPtrPtrReductionGen();
1251
1252 OwningDataPtrPtrReductionGen refDataPtrGen =
1253 [&, decl](llvm::OpenMPIRBuilder::InsertPointTy insertPoint,
1254 llvm::Value *byRefVal, llvm::Value *&result) mutable
1255 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
1256 moduleTranslation.mapValue(decl.getDataPtrPtrRegionArg(), byRefVal);
1257 builder.restoreIP(insertPoint);
1259 if (failed(inlineConvertOmpRegions(decl.getDataPtrPtrRegion(),
1260 "omp.data_ptr_ptr.body", builder,
1261 moduleTranslation, &phis)))
1262 return llvm::createStringError(
1263 "failed to inline `data_ptr_ptr` region of `omp.declare_reduction`");
1264 result = llvm::getSingleElement(phis);
1265 return builder.saveIP();
1266 };
1267
1268 return refDataPtrGen;
1269}
1270
1271/// Converts an OpenMP 'ordered' operation into LLVM IR using OpenMPIRBuilder.
1272static LogicalResult
1273convertOmpOrdered(Operation &opInst, llvm::IRBuilderBase &builder,
1274 LLVM::ModuleTranslation &moduleTranslation) {
1275 auto orderedOp = cast<omp::OrderedOp>(opInst);
1276
1277 if (failed(checkImplementationStatus(opInst)))
1278 return failure();
1279
1280 omp::ClauseDepend dependType = *orderedOp.getDoacrossDependType();
1281 bool isDependSource = dependType == omp::ClauseDepend::dependsource;
1282 unsigned numLoops = *orderedOp.getDoacrossNumLoops();
1283 SmallVector<llvm::Value *> vecValues =
1284 moduleTranslation.lookupValues(orderedOp.getDoacrossDependVars());
1285
1286 size_t indexVecValues = 0;
1287 while (indexVecValues < vecValues.size()) {
1288 SmallVector<llvm::Value *> storeValues;
1289 storeValues.reserve(numLoops);
1290 for (unsigned i = 0; i < numLoops; i++) {
1291 storeValues.push_back(vecValues[indexVecValues]);
1292 indexVecValues++;
1293 }
1294 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
1295 findAllocInsertPoints(builder, moduleTranslation);
1296 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
1297 builder.restoreIP(moduleTranslation.getOpenMPBuilder()->createOrderedDepend(
1298 ompLoc, allocaIP, numLoops, storeValues, ".cnt.addr", isDependSource));
1299 }
1300 return success();
1301}
1302
1303/// Converts an OpenMP 'ordered_region' operation into LLVM IR using
1304/// OpenMPIRBuilder.
1305static LogicalResult
1306convertOmpOrderedRegion(Operation &opInst, llvm::IRBuilderBase &builder,
1307 LLVM::ModuleTranslation &moduleTranslation) {
1308 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
1309 auto orderedRegionOp = cast<omp::OrderedRegionOp>(opInst);
1310
1311 if (failed(checkImplementationStatus(opInst)))
1312 return failure();
1313
1314 auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
1315 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) {
1316 // OrderedOp has only one region associated with it.
1317 auto &region = cast<omp::OrderedRegionOp>(opInst).getRegion();
1318 builder.restoreIP(codeGenIP);
1319 return convertOmpOpRegions(region, "omp.ordered.region", builder,
1320 moduleTranslation)
1321 .takeError();
1322 };
1323
1324 // TODO: Perform finalization actions for variables. This has to be
1325 // called for variables which have destructors/finalizers.
1326 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
1327
1328 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
1329 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
1330 moduleTranslation.getOpenMPBuilder()->createOrderedThreadsSimd(
1331 ompLoc, bodyGenCB, finiCB, !orderedRegionOp.getParLevelSimd());
1332
1333 if (failed(handleError(afterIP, opInst)))
1334 return failure();
1335
1336 builder.restoreIP(*afterIP);
1337 return success();
1338}
1339
1340namespace {
1341/// Contains the arguments for an LLVM store operation
1342struct DeferredStore {
1343 DeferredStore(llvm::Value *value, llvm::Value *address)
1344 : value(value), address(address) {}
1345
1346 llvm::Value *value;
1347 llvm::Value *address;
1348};
1349} // namespace
1350
1351/// Allocate space for privatized reduction variables.
1352/// `deferredStores` contains information to create store operations which needs
1353/// to be inserted after all allocas
1354template <typename T>
1355static LogicalResult
1357 llvm::IRBuilderBase &builder,
1358 LLVM::ModuleTranslation &moduleTranslation,
1359 const llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
1361 SmallVectorImpl<llvm::Value *> &privateReductionVariables,
1362 DenseMap<Value, llvm::Value *> &reductionVariableMap,
1363 SmallVectorImpl<DeferredStore> &deferredStores,
1364 llvm::ArrayRef<bool> isByRefs) {
1365 llvm::IRBuilderBase::InsertPointGuard guard(builder);
1366 builder.SetInsertPoint(allocaIP.getBlock()->getTerminator());
1367
1368 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
1369 bool useDeviceSharedMem = omp::opInSharedDeviceContext(*op);
1370
1371 // delay creating stores until after all allocas
1372 deferredStores.reserve(op.getNumReductionVars());
1373
1374 for (std::size_t i = 0; i < op.getNumReductionVars(); ++i) {
1375 Region &allocRegion = reductionDecls[i].getAllocRegion();
1376 if (isByRefs[i]) {
1377 if (allocRegion.empty())
1378 continue;
1379
1381 if (failed(inlineConvertOmpRegions(allocRegion, "omp.reduction.alloc",
1382 builder, moduleTranslation, &phis)))
1383 return op.emitError(
1384 "failed to inline `alloc` region of `omp.declare_reduction`");
1385
1386 assert(phis.size() == 1 && "expected one allocation to be yielded");
1387 builder.SetInsertPoint(allocaIP.getBlock()->getTerminator());
1388
1389 // Allocate reduction variable (which is a pointer to the real reduction
1390 // variable allocated in the inlined region)
1391 llvm::Type *ptrTy = builder.getPtrTy();
1392 llvm::Type *varTy =
1393 moduleTranslation.convertType(reductionDecls[i].getType());
1394 llvm::Value *var;
1395 if (useDeviceSharedMem) {
1396 var = ompBuilder->createOMPAllocShared(builder, varTy);
1397 } else {
1398 var = builder.CreateAlloca(varTy);
1399 var = builder.CreatePointerBitCastOrAddrSpaceCast(var, ptrTy);
1400 }
1401
1402 llvm::Value *castPhi =
1403 builder.CreatePointerBitCastOrAddrSpaceCast(phis[0], ptrTy);
1404
1405 deferredStores.emplace_back(castPhi, var);
1406
1407 privateReductionVariables[i] = var;
1408 moduleTranslation.mapValue(reductionArgs[i], castPhi);
1409 reductionVariableMap.try_emplace(op.getReductionVars()[i], castPhi);
1410 } else {
1411 assert(allocRegion.empty() &&
1412 "allocaction is implicit for by-val reduction");
1413
1414 llvm::Type *ptrTy = builder.getPtrTy();
1415 llvm::Type *varTy =
1416 moduleTranslation.convertType(reductionDecls[i].getType());
1417 llvm::Value *var;
1418 if (useDeviceSharedMem) {
1419 var = ompBuilder->createOMPAllocShared(builder, varTy);
1420 } else {
1421 var = builder.CreateAlloca(varTy);
1422 var = builder.CreatePointerBitCastOrAddrSpaceCast(var, ptrTy);
1423 }
1424
1425 moduleTranslation.mapValue(reductionArgs[i], var);
1426 privateReductionVariables[i] = var;
1427 reductionVariableMap.try_emplace(op.getReductionVars()[i], var);
1428 }
1429 }
1430
1431 return success();
1432}
1433
1434/// Map input arguments to reduction initialization region
1435template <typename T>
1436static void
1438 llvm::IRBuilderBase &builder,
1440 DenseMap<Value, llvm::Value *> &reductionVariableMap,
1441 unsigned i) {
1442 // map input argument to the initialization region
1443 mlir::omp::DeclareReductionOp &reduction = reductionDecls[i];
1444 Region &initializerRegion = reduction.getInitializerRegion();
1445 Block &entry = initializerRegion.front();
1446
1447 mlir::Value mlirSource = loop.getReductionVars()[i];
1448 llvm::Value *llvmSource = moduleTranslation.lookupValue(mlirSource);
1449 llvm::Value *origVal = llvmSource;
1450 // If a non-pointer value is expected, load the value from the source pointer.
1451 if (!isa<LLVM::LLVMPointerType>(
1452 reduction.getInitializerMoldArg().getType()) &&
1453 isa<LLVM::LLVMPointerType>(mlirSource.getType())) {
1454 origVal =
1455 builder.CreateLoad(moduleTranslation.convertType(
1456 reduction.getInitializerMoldArg().getType()),
1457 llvmSource, "omp_orig");
1458 }
1459 moduleTranslation.mapValue(reduction.getInitializerMoldArg(), origVal);
1460
1461 if (entry.getNumArguments() > 1) {
1462 llvm::Value *allocation =
1463 reductionVariableMap.lookup(loop.getReductionVars()[i]);
1464 moduleTranslation.mapValue(reduction.getInitializerAllocArg(), allocation);
1465 }
1466}
1467
1468static void
1469setInsertPointForPossiblyEmptyBlock(llvm::IRBuilderBase &builder,
1470 llvm::BasicBlock *block = nullptr) {
1471 if (block == nullptr)
1472 block = builder.GetInsertBlock();
1473
1474 if (!block->hasTerminator())
1475 builder.SetInsertPoint(block);
1476 else
1477 builder.SetInsertPoint(block->getTerminator());
1478}
1479
1480/// Inline reductions' `init` regions. This functions assumes that the
1481/// `builder`'s insertion point is where the user wants the `init` regions to be
1482/// inlined; i.e. it does not try to find a proper insertion location for the
1483/// `init` regions. It also leaves the `builder's insertions point in a state
1484/// where the user can continue the code-gen directly afterwards.
1485template <typename OP>
1486static LogicalResult
1487initReductionVars(OP op, ArrayRef<BlockArgument> reductionArgs,
1488 llvm::IRBuilderBase &builder,
1489 LLVM::ModuleTranslation &moduleTranslation,
1490 llvm::BasicBlock *latestAllocaBlock,
1492 SmallVectorImpl<llvm::Value *> &privateReductionVariables,
1493 DenseMap<Value, llvm::Value *> &reductionVariableMap,
1494 llvm::ArrayRef<bool> isByRef,
1495 SmallVectorImpl<DeferredStore> &deferredStores) {
1496 if (op.getNumReductionVars() == 0)
1497 return success();
1498
1499 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
1500 bool useDeviceSharedMem = omp::opInSharedDeviceContext(*op);
1501
1502 llvm::BasicBlock *initBlock = splitBB(builder, true, "omp.reduction.init");
1503 auto allocaIP = llvm::IRBuilderBase::InsertPoint(
1504 latestAllocaBlock, latestAllocaBlock->getTerminator()->getIterator());
1505 builder.restoreIP(allocaIP);
1506 SmallVector<llvm::Value *> byRefVars(op.getNumReductionVars());
1507
1508 for (unsigned i = 0; i < op.getNumReductionVars(); ++i) {
1509 if (isByRef[i]) {
1510 if (!reductionDecls[i].getAllocRegion().empty())
1511 continue;
1512
1513 // TODO: remove after all users of by-ref are updated to use the alloc
1514 // region: Allocate reduction variable (which is a pointer to the real
1515 // reduciton variable allocated in the inlined region)
1516 llvm::Type *varTy =
1517 moduleTranslation.convertType(reductionDecls[i].getType());
1518 if (useDeviceSharedMem)
1519 byRefVars[i] = ompBuilder->createOMPAllocShared(builder, varTy);
1520 else
1521 byRefVars[i] = builder.CreateAlloca(varTy);
1522 }
1523 }
1524
1525 setInsertPointForPossiblyEmptyBlock(builder, initBlock);
1526
1527 // store result of the alloc region to the allocated pointer to the real
1528 // reduction variable
1529 for (auto [data, addr] : deferredStores)
1530 builder.CreateStore(data, addr);
1531
1532 // Before the loop, store the initial values of reductions into reduction
1533 // variables. Although this could be done after allocas, we don't want to mess
1534 // up with the alloca insertion point.
1535 for (unsigned i = 0; i < op.getNumReductionVars(); ++i) {
1537
1538 // map block argument to initializer region
1539 mapInitializationArgs(op, moduleTranslation, builder, reductionDecls,
1540 reductionVariableMap, i);
1541
1542 // TODO In some cases (specially on the GPU), the init regions may
1543 // contains stack alloctaions. If the region is inlined in a loop, this is
1544 // problematic. Instead of just inlining the region, handle allocations by
1545 // hoisting fixed length allocations to the function entry and using
1546 // stacksave and restore for variable length ones.
1547 if (failed(inlineConvertOmpRegions(reductionDecls[i].getInitializerRegion(),
1548 "omp.reduction.neutral", builder,
1549 moduleTranslation, &phis)))
1550 return failure();
1551
1552 assert(phis.size() == 1 && "expected one value to be yielded from the "
1553 "reduction neutral element declaration region");
1554
1556
1557 if (isByRef[i]) {
1558 if (!reductionDecls[i].getAllocRegion().empty())
1559 // done in allocReductionVars
1560 continue;
1561
1562 // TODO: this path can be removed once all users of by-ref are updated to
1563 // use an alloc region
1564
1565 // Store the result of the inlined region to the allocated reduction var
1566 // ptr
1567 builder.CreateStore(phis[0], byRefVars[i]);
1568
1569 privateReductionVariables[i] = byRefVars[i];
1570 moduleTranslation.mapValue(reductionArgs[i], phis[0]);
1571 reductionVariableMap.try_emplace(op.getReductionVars()[i], phis[0]);
1572 } else {
1573 // for by-ref case the store is inside of the reduction region
1574 builder.CreateStore(phis[0], privateReductionVariables[i]);
1575 // the rest was handled in allocByValReductionVars
1576 }
1577
1578 // forget the mapping for the initializer region because we might need a
1579 // different mapping if this reduction declaration is re-used for a
1580 // different variable
1581 moduleTranslation.forgetMapping(reductionDecls[i].getInitializerRegion());
1582 }
1583
1584 return success();
1585}
1586
1587/// Collect reduction info
1588template <typename T>
1589static void collectReductionInfo(
1590 T loop, llvm::IRBuilderBase &builder,
1591 LLVM::ModuleTranslation &moduleTranslation,
1594 SmallVectorImpl<OwningAtomicReductionGen> &owningAtomicReductionGens,
1596 const ArrayRef<llvm::Value *> privateReductionVariables,
1598 ArrayRef<bool> isByRef) {
1599 unsigned numReductions = loop.getNumReductionVars();
1600
1601 for (unsigned i = 0; i < numReductions; ++i) {
1602 owningReductionGens.push_back(
1603 makeReductionGen(reductionDecls[i], builder, moduleTranslation));
1604 owningAtomicReductionGens.push_back(
1605 makeAtomicReductionGen(reductionDecls[i], builder, moduleTranslation));
1607 reductionDecls[i], builder, moduleTranslation, isByRef[i]));
1608 }
1609
1610 // Collect the reduction information.
1611 reductionInfos.reserve(numReductions);
1612 for (unsigned i = 0; i < numReductions; ++i) {
1613 llvm::OpenMPIRBuilder::ReductionGenAtomicCBTy atomicGen = nullptr;
1614 if (owningAtomicReductionGens[i])
1615 atomicGen = owningAtomicReductionGens[i];
1616 llvm::Value *variable =
1617 moduleTranslation.lookupValue(loop.getReductionVars()[i]);
1618 mlir::Type allocatedType;
1619 reductionDecls[i].getAllocRegion().walk([&](mlir::Operation *op) {
1620 if (auto alloca = mlir::dyn_cast<LLVM::AllocaOp>(op)) {
1621 allocatedType = alloca.getElemType();
1623 }
1624
1626 });
1627
1628 reductionInfos.push_back(
1629 {moduleTranslation.convertType(reductionDecls[i].getType()), variable,
1630 privateReductionVariables[i],
1631 /*EvaluationKind=*/llvm::OpenMPIRBuilder::EvalKind::Scalar,
1633 /*ReductionGenClang=*/nullptr, atomicGen,
1635 allocatedType ? moduleTranslation.convertType(allocatedType) : nullptr,
1636 reductionDecls[i].getByrefElementType()
1637 ? moduleTranslation.convertType(
1638 *reductionDecls[i].getByrefElementType())
1639 : nullptr});
1640 }
1641}
1642
1643/// handling of DeclareReductionOp's cleanup region
1644static LogicalResult
1646 llvm::ArrayRef<llvm::Value *> privateVariables,
1647 LLVM::ModuleTranslation &moduleTranslation,
1648 llvm::IRBuilderBase &builder, StringRef regionName,
1649 bool shouldLoadCleanupRegionArg = true) {
1650 for (auto [i, cleanupRegion] : llvm::enumerate(cleanupRegions)) {
1651 if (cleanupRegion->empty())
1652 continue;
1653
1654 // map the argument to the cleanup region
1655 Block &entry = cleanupRegion->front();
1656
1657 llvm::Instruction *potentialTerminator =
1658 builder.GetInsertBlock()->empty() ? nullptr
1659 : &builder.GetInsertBlock()->back();
1660 if (potentialTerminator && potentialTerminator->isTerminator())
1661 builder.SetInsertPoint(potentialTerminator);
1662 llvm::Value *privateVarValue =
1663 shouldLoadCleanupRegionArg
1664 ? builder.CreateLoad(
1665 moduleTranslation.convertType(entry.getArgument(0).getType()),
1666 privateVariables[i])
1667 : privateVariables[i];
1668
1669 moduleTranslation.mapValue(entry.getArgument(0), privateVarValue);
1670
1671 if (failed(inlineConvertOmpRegions(*cleanupRegion, regionName, builder,
1672 moduleTranslation)))
1673 return failure();
1674
1675 // clear block argument mapping in case it needs to be re-created with a
1676 // different source for another use of the same reduction decl
1677 moduleTranslation.forgetMapping(*cleanupRegion);
1678 }
1679 return success();
1680}
1681
1682// TODO: not used by ParallelOp
1683template <class OP>
1684static LogicalResult createReductionsAndCleanup(
1685 OP op, llvm::IRBuilderBase &builder,
1686 LLVM::ModuleTranslation &moduleTranslation,
1687 llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
1689 ArrayRef<llvm::Value *> privateReductionVariables, ArrayRef<bool> isByRef,
1690 bool isNowait = false, bool isTeamsReduction = false) {
1691 // Process the reductions if required.
1692 if (op.getNumReductionVars() == 0)
1693 return success();
1694
1696 SmallVector<OwningAtomicReductionGen> owningAtomicReductionGens;
1697 SmallVector<OwningDataPtrPtrReductionGen> owningReductionGenRefDataPtrGens;
1699
1700 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
1701
1702 // Create the reduction generators. We need to own them here because
1703 // ReductionInfo only accepts references to the generators.
1704 collectReductionInfo(op, builder, moduleTranslation, reductionDecls,
1705 owningReductionGens, owningAtomicReductionGens,
1706 owningReductionGenRefDataPtrGens,
1707 privateReductionVariables, reductionInfos, isByRef);
1708
1709 // The call to createReductions below expects the block to have a
1710 // terminator. Create an unreachable instruction to serve as terminator
1711 // and remove it later.
1712 llvm::UnreachableInst *tempTerminator = builder.CreateUnreachable();
1713 builder.SetInsertPoint(tempTerminator);
1714 llvm::OpenMPIRBuilder::InsertPointOrErrorTy contInsertPoint =
1715 ompBuilder->createReductions(builder, allocaIP, reductionInfos, isByRef,
1716 isNowait, isTeamsReduction);
1717
1718 if (failed(handleError(contInsertPoint, *op)))
1719 return failure();
1720
1721 if (!contInsertPoint->getBlock())
1722 return op->emitOpError() << "failed to convert reductions";
1723
1724 llvm::OpenMPIRBuilder::InsertPointTy afterIP = *contInsertPoint;
1725 if (!isTeamsReduction) {
1726 llvm::OpenMPIRBuilder::InsertPointOrErrorTy barrierIP =
1727 ompBuilder->createBarrier(*contInsertPoint, llvm::omp::OMPD_for);
1728
1729 if (failed(handleError(barrierIP, *op)))
1730 return failure();
1731 afterIP = *barrierIP;
1732 }
1733
1734 tempTerminator->eraseFromParent();
1735 builder.restoreIP(afterIP);
1736
1737 // after the construct, deallocate private reduction variables
1738 SmallVector<Region *> reductionRegions;
1739 llvm::transform(reductionDecls, std::back_inserter(reductionRegions),
1740 [](omp::DeclareReductionOp reductionDecl) {
1741 return &reductionDecl.getCleanupRegion();
1742 });
1743 LogicalResult result = inlineOmpRegionCleanup(
1744 reductionRegions, privateReductionVariables, moduleTranslation, builder,
1745 "omp.reduction.cleanup");
1746
1747 bool useDeviceSharedMem = omp::opInSharedDeviceContext(*op);
1748 if (useDeviceSharedMem) {
1749 for (auto [var, reductionDecl] :
1750 llvm::zip_equal(privateReductionVariables, reductionDecls))
1751 ompBuilder->createOMPFreeShared(
1752 builder, var, moduleTranslation.convertType(reductionDecl.getType()));
1753 }
1754
1755 return result;
1756}
1757
1758static ArrayRef<bool> getIsByRef(std::optional<ArrayRef<bool>> attr) {
1759 if (!attr)
1760 return {};
1761 return *attr;
1762}
1763
1764// TODO: not used by omp.parallel
1765template <typename OP>
1767 OP op, ArrayRef<BlockArgument> reductionArgs, llvm::IRBuilderBase &builder,
1768 LLVM::ModuleTranslation &moduleTranslation,
1769 llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
1771 SmallVectorImpl<llvm::Value *> &privateReductionVariables,
1772 DenseMap<Value, llvm::Value *> &reductionVariableMap,
1773 llvm::ArrayRef<bool> isByRef) {
1774 if (op.getNumReductionVars() == 0)
1775 return success();
1776
1777 SmallVector<DeferredStore> deferredStores;
1778
1779 if (failed(allocReductionVars(op, reductionArgs, builder, moduleTranslation,
1780 allocaIP, reductionDecls,
1781 privateReductionVariables, reductionVariableMap,
1782 deferredStores, isByRef)))
1783 return failure();
1784
1785 return initReductionVars(op, reductionArgs, builder, moduleTranslation,
1786 allocaIP.getBlock(), reductionDecls,
1787 privateReductionVariables, reductionVariableMap,
1788 isByRef, deferredStores);
1789}
1790
1791/// Return the llvm::Value * corresponding to the `privateVar` that
1792/// is being privatized. It isn't always as simple as looking up
1793/// moduleTranslation with privateVar. For instance, in case of
1794/// an allocatable, the descriptor for the allocatable is privatized.
1795/// This descriptor is mapped using an MapInfoOp. So, this function
1796/// will return a pointer to the llvm::Value corresponding to the
1797/// block argument for the mapped descriptor.
1798static llvm::Value *
1799findAssociatedValue(Value privateVar, llvm::IRBuilderBase &builder,
1800 LLVM::ModuleTranslation &moduleTranslation,
1801 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1802 if (mappedPrivateVars == nullptr || !mappedPrivateVars->contains(privateVar))
1803 return moduleTranslation.lookupValue(privateVar);
1804
1805 Value blockArg = (*mappedPrivateVars)[privateVar];
1806 Type privVarType = privateVar.getType();
1807 Type blockArgType = blockArg.getType();
1808 assert(isa<LLVM::LLVMPointerType>(blockArgType) &&
1809 "A block argument corresponding to a mapped var should have "
1810 "!llvm.ptr type");
1811
1812 if (privVarType == blockArgType)
1813 return moduleTranslation.lookupValue(blockArg);
1814
1815 // This typically happens when the privatized type is lowered from
1816 // boxchar<KIND> and gets lowered to !llvm.struct<(ptr, i64)>. That is the
1817 // struct/pair is passed by value. But, mapped values are passed only as
1818 // pointers, so before we privatize, we must load the pointer.
1819 if (!isa<LLVM::LLVMPointerType>(privVarType))
1820 return builder.CreateLoad(moduleTranslation.convertType(privVarType),
1821 moduleTranslation.lookupValue(blockArg));
1822
1823 return moduleTranslation.lookupValue(privateVar);
1824}
1825
1826// Privatizer region arguments may be by-value even when the available LLVM
1827// value is storage for that value, e.g. lowered Fortran boxchar descriptors in
1828// task context structs. Materialize the value expected by the region argument
1829// while preserving the existing pointer mapping for pointer arguments.
1830static llvm::Value *
1831materializeRegionArgValue(llvm::IRBuilderBase &builder,
1832 LLVM::ModuleTranslation &moduleTranslation,
1833 BlockArgument regionArg, llvm::Value *value) {
1834 if (!regionArg)
1835 return value;
1836
1837 llvm::Type *regionArgType =
1838 moduleTranslation.convertType(regionArg.getType());
1839 if (regionArgType->isPointerTy() || !value->getType()->isPointerTy())
1840 return value;
1841
1842 return builder.CreateLoad(regionArgType, value);
1843}
1844
1845/// Initialize a single (first)private variable. You probably want to use
1846/// allocateAndInitPrivateVars instead of this.
1847/// This returns the private variable which has been initialized. This
1848/// variable should be mapped before constructing the body of the Op.
1850initPrivateVar(llvm::IRBuilderBase &builder,
1851 LLVM::ModuleTranslation &moduleTranslation,
1852 omp::PrivateClauseOp &privDecl, llvm::Value *nonPrivateVar,
1853 BlockArgument &blockArg, llvm::Value *llvmPrivateVar,
1854 llvm::BasicBlock *privInitBlock,
1855 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1856 Region &initRegion = privDecl.getInitRegion();
1857 if (initRegion.empty())
1858 return llvmPrivateVar;
1859
1860 assert(nonPrivateVar);
1861 moduleTranslation.mapValue(privDecl.getInitMoldArg(), nonPrivateVar);
1862 moduleTranslation.mapValue(privDecl.getInitPrivateArg(), llvmPrivateVar);
1863
1864 // in-place convert the private initialization region
1866 if (failed(inlineConvertOmpRegions(initRegion, "omp.private.init", builder,
1867 moduleTranslation, &phis)))
1868 return llvm::createStringError(
1869 "failed to inline `init` region of `omp.private`");
1870
1871 assert(phis.size() == 1 && "expected one allocation to be yielded");
1872
1873 // clear init region block argument mapping in case it needs to be
1874 // re-created with a different source for another use of the same
1875 // reduction decl
1876 moduleTranslation.forgetMapping(initRegion);
1877
1878 // Prefer the value yielded from the init region to the allocated private
1879 // variable in case the region is operating on arguments by-value (e.g.
1880 // Fortran character boxes).
1881 return phis[0];
1882}
1883
1884/// Version of initPrivateVar which looks up the nonPrivateVar from mlirPrivVar.
1886 llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation,
1887 omp::PrivateClauseOp &privDecl, Value mlirPrivVar, BlockArgument &blockArg,
1888 llvm::Value *llvmPrivateVar, llvm::BasicBlock *privInitBlock,
1889 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1890 return initPrivateVar(
1891 builder, moduleTranslation, privDecl,
1892 findAssociatedValue(mlirPrivVar, builder, moduleTranslation,
1893 mappedPrivateVars),
1894 blockArg, llvmPrivateVar, privInitBlock, mappedPrivateVars);
1895}
1896
1897static llvm::Error
1898initPrivateVars(llvm::IRBuilderBase &builder,
1899 LLVM::ModuleTranslation &moduleTranslation,
1900 PrivateVarsInfo &privateVarsInfo,
1901 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1902 if (privateVarsInfo.blockArgs.empty())
1903 return llvm::Error::success();
1904
1905 llvm::BasicBlock *privInitBlock = splitBB(builder, true, "omp.private.init");
1906 setInsertPointForPossiblyEmptyBlock(builder, privInitBlock);
1907
1908 for (auto [idx, zip] : llvm::enumerate(llvm::zip_equal(
1909 privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
1910 privateVarsInfo.blockArgs, privateVarsInfo.llvmVars))) {
1911 auto [privDecl, mlirPrivVar, blockArg, llvmPrivateVar] = zip;
1913 builder, moduleTranslation, privDecl, mlirPrivVar, blockArg,
1914 llvmPrivateVar, privInitBlock, mappedPrivateVars);
1915
1916 if (!privVarOrErr)
1917 return privVarOrErr.takeError();
1918
1919 llvmPrivateVar = privVarOrErr.get();
1920 moduleTranslation.mapValue(blockArg, llvmPrivateVar);
1921
1923 }
1924
1925 return llvm::Error::success();
1926}
1927
1928/// Allocate and initialize delayed private variables. Returns the basic block
1929/// which comes after all of these allocations. llvm::Value * for each of these
1930/// private variables are populated in llvmPrivateVars.
1931template <typename T>
1933allocatePrivateVars(T op, llvm::IRBuilderBase &builder,
1934 LLVM::ModuleTranslation &moduleTranslation,
1935 PrivateVarsInfo &privateVarsInfo,
1936 const llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
1937 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1938 // Allocate private vars
1939 llvm::Instruction *allocaTerminator = allocaIP.getBlock()->getTerminator();
1940 splitBB(llvm::OpenMPIRBuilder::InsertPointTy(allocaIP.getBlock(),
1941 allocaTerminator->getIterator()),
1942 true, allocaTerminator->getStableDebugLoc(),
1943 "omp.region.after_alloca");
1944
1945 llvm::IRBuilderBase::InsertPointGuard guard(builder);
1946 // Update the allocaTerminator since the alloca block was split above.
1947 allocaTerminator = allocaIP.getBlock()->getTerminator();
1948 builder.SetInsertPoint(allocaTerminator);
1949 // The new terminator is an uncondition branch created by the splitBB above.
1950 assert(allocaTerminator->getNumSuccessors() == 1 &&
1951 "This is an unconditional branch created by splitBB");
1952
1953 llvm::DataLayout dataLayout = builder.GetInsertBlock()->getDataLayout();
1954 llvm::BasicBlock *afterAllocas = allocaTerminator->getSuccessor(0);
1955
1956 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
1957 bool mightUseDeviceSharedMem = omp::opInSharedDeviceContext(*op);
1958 unsigned int allocaAS =
1959 moduleTranslation.getLLVMModule()->getDataLayout().getAllocaAddrSpace();
1960 unsigned int defaultAS = moduleTranslation.getLLVMModule()
1961 ->getDataLayout()
1962 .getProgramAddressSpace();
1963
1964 for (auto [privDecl, mlirPrivVar, blockArg] :
1965 llvm::zip_equal(privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
1966 privateVarsInfo.blockArgs)) {
1967 llvm::Type *llvmAllocType =
1968 moduleTranslation.convertType(privDecl.getType());
1969 builder.SetInsertPoint(allocaIP.getBlock()->getTerminator());
1970 llvm::Value *llvmPrivateVar = nullptr;
1971 if (mightUseDeviceSharedMem && omp::allocaUsesRequireSharedMem(blockArg)) {
1972 llvmPrivateVar = ompBuilder->createOMPAllocShared(builder, llvmAllocType);
1973 } else {
1974 llvmPrivateVar = builder.CreateAlloca(
1975 llvmAllocType, /*ArraySize=*/nullptr, "omp.private.alloc");
1976 if (allocaAS != defaultAS)
1977 llvmPrivateVar = builder.CreateAddrSpaceCast(
1978 llvmPrivateVar, builder.getPtrTy(defaultAS));
1979 }
1980
1981 privateVarsInfo.llvmVars.push_back(llvmPrivateVar);
1982 }
1983
1984 return afterAllocas;
1985}
1986
1987/// This can't always be determined statically, but when we can, it is good to
1988/// avoid generating compiler-added barriers which will deadlock the program.
1990 for (mlir::Operation *parent = op->getParentOp(); parent != nullptr;
1991 parent = parent->getParentOp()) {
1992 if (mlir::isa<omp::SingleOp, omp::CriticalOp>(parent))
1993 return true;
1994
1995 // e.g.
1996 // omp.single {
1997 // omp.parallel {
1998 // op
1999 // }
2000 // }
2001 if (mlir::isa<omp::ParallelOp>(parent))
2002 return false;
2003 }
2004 return false;
2005}
2006
2007static LogicalResult copyFirstPrivateVars(
2008 mlir::Operation *op, llvm::IRBuilderBase &builder,
2009 LLVM::ModuleTranslation &moduleTranslation,
2011 ArrayRef<llvm::Value *> llvmPrivateVars,
2012 SmallVectorImpl<omp::PrivateClauseOp> &privateDecls, bool insertBarrier,
2013 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
2014 // Apply copy region for firstprivate.
2015 bool needsFirstprivate =
2016 llvm::any_of(privateDecls, [](omp::PrivateClauseOp &privOp) {
2017 return privOp.getDataSharingType() ==
2018 omp::DataSharingClauseType::FirstPrivate;
2019 });
2020
2021 if (!needsFirstprivate)
2022 return success();
2023
2024 llvm::BasicBlock *copyBlock =
2025 splitBB(builder, /*CreateBranch=*/true, "omp.private.copy");
2026 setInsertPointForPossiblyEmptyBlock(builder, copyBlock);
2027
2028 for (auto [decl, moldVar, llvmVar] :
2029 llvm::zip_equal(privateDecls, moldVars, llvmPrivateVars)) {
2030 if (decl.getDataSharingType() != omp::DataSharingClauseType::FirstPrivate)
2031 continue;
2032
2033 // copyRegion implements `lhs = rhs`
2034 Region &copyRegion = decl.getCopyRegion();
2035
2036 llvm::Value *copyMoldVar = materializeRegionArgValue(
2037 builder, moduleTranslation, decl.getCopyMoldArg(), moldVar);
2038 llvm::Value *copyPrivateVar = materializeRegionArgValue(
2039 builder, moduleTranslation, decl.getCopyPrivateArg(), llvmVar);
2040
2041 moduleTranslation.mapValue(decl.getCopyMoldArg(), copyMoldVar);
2042
2043 // map copyRegion lhs arg
2044 moduleTranslation.mapValue(decl.getCopyPrivateArg(), copyPrivateVar);
2045
2046 // in-place convert copy region
2047 if (failed(inlineConvertOmpRegions(copyRegion, "omp.private.copy", builder,
2048 moduleTranslation)))
2049 return decl.emitError("failed to inline `copy` region of `omp.private`");
2050
2052
2053 // ignore unused value yielded from copy region
2054
2055 // clear copy region block argument mapping in case it needs to be
2056 // re-created with different sources for reuse of the same reduction
2057 // decl
2058 moduleTranslation.forgetMapping(copyRegion);
2059 }
2060
2061 if (insertBarrier && !opIsInSingleThread(op)) {
2062 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
2063 llvm::OpenMPIRBuilder::InsertPointOrErrorTy res =
2064 ompBuilder->createBarrier(builder, llvm::omp::OMPD_barrier);
2065 if (failed(handleError(res, *op)))
2066 return failure();
2067 }
2068
2069 return success();
2070}
2071
2072static LogicalResult copyFirstPrivateVars(
2073 mlir::Operation *op, llvm::IRBuilderBase &builder,
2074 LLVM::ModuleTranslation &moduleTranslation,
2075 SmallVectorImpl<mlir::Value> &mlirPrivateVars,
2076 ArrayRef<llvm::Value *> llvmPrivateVars,
2077 SmallVectorImpl<omp::PrivateClauseOp> &privateDecls, bool insertBarrier,
2078 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
2079 llvm::SmallVector<llvm::Value *> moldVars(mlirPrivateVars.size());
2080 llvm::transform(mlirPrivateVars, moldVars.begin(), [&](mlir::Value mlirVar) {
2081 // map copyRegion rhs arg
2082 llvm::Value *moldVar = findAssociatedValue(
2083 mlirVar, builder, moduleTranslation, mappedPrivateVars);
2084 assert(moldVar);
2085 return moldVar;
2086 });
2087 return copyFirstPrivateVars(op, builder, moduleTranslation, moldVars,
2088 llvmPrivateVars, privateDecls, insertBarrier,
2089 mappedPrivateVars);
2090}
2091
2092template <typename T>
2093static LogicalResult
2094cleanupPrivateVars(T op, llvm::IRBuilderBase &builder,
2095 LLVM::ModuleTranslation &moduleTranslation, Location loc,
2096 PrivateVarsInfo &privateVarsInfo) {
2097 // private variable deallocation
2098 SmallVector<Region *> privateCleanupRegions;
2099 llvm::transform(privateVarsInfo.privatizers,
2100 std::back_inserter(privateCleanupRegions),
2101 [](omp::PrivateClauseOp privatizer) {
2102 return &privatizer.getDeallocRegion();
2103 });
2104
2105 if (failed(inlineOmpRegionCleanup(privateCleanupRegions,
2106 privateVarsInfo.llvmVars, moduleTranslation,
2107 builder, "omp.private.dealloc",
2108 /*shouldLoadCleanupRegionArg=*/false)))
2109 return mlir::emitError(loc, "failed to inline `dealloc` region of an "
2110 "`omp.private` op in");
2111
2112 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
2113 bool mightUseDeviceSharedMem = omp::opInSharedDeviceContext(*op);
2114 for (auto [privDecl, llvmPrivVar, blockArg] :
2115 llvm::zip_equal(privateVarsInfo.privatizers, privateVarsInfo.llvmVars,
2116 privateVarsInfo.blockArgs)) {
2117 if (mightUseDeviceSharedMem && omp::allocaUsesRequireSharedMem(blockArg)) {
2118 ompBuilder->createOMPFreeShared(
2119 builder, llvmPrivVar,
2120 moduleTranslation.convertType(privDecl.getType()));
2121 }
2122 }
2123
2124 return success();
2125}
2126
2127/// Returns true if the construct contains omp.cancel or omp.cancellation_point
2129 // omp.cancel and omp.cancellation_point must be "closely nested" so they will
2130 // be visible and not inside of function calls. This is enforced by the
2131 // verifier.
2132 return op
2133 ->walk([](Operation *child) {
2134 if (mlir::isa<omp::CancelOp, omp::CancellationPointOp>(child))
2135 return WalkResult::interrupt();
2136 return WalkResult::advance();
2137 })
2138 .wasInterrupted();
2139}
2140
2141// Forward declarations for the task-reduction helpers defined alongside the
2142// omp.taskgroup lowering further down in this file. These are shared by the
2143// `reduction(task, ...)` modifier lowering on the parallel/worksharing
2144// constructs and by the omp.taskgroup / omp.taskloop.context task_reduction
2145// lowering. When \p isModifier is set, `__kmpc_taskred_modifier_init` is
2146// emitted (opening a task-reduction scope) instead of `__kmpc_taskred_init`,
2147// with \p isWorksharing selecting the runtime `is_ws` argument.
2148static llvm::Value *emitTaskReductionInitCall(
2150 ArrayRef<llvm::Value *> origPtrs, StringRef helperNamePrefix,
2151 llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder::InsertPointTy allocaIP,
2152 LLVM::ModuleTranslation &moduleTranslation, bool isModifier = false,
2153 bool isWorksharing = false);
2154static void
2155emitTaskReductionModifierFini(bool isWorksharing, llvm::IRBuilderBase &builder,
2156 LLVM::ModuleTranslation &moduleTranslation);
2157
2158static LogicalResult
2159convertOmpSections(Operation &opInst, llvm::IRBuilderBase &builder,
2160 LLVM::ModuleTranslation &moduleTranslation) {
2161 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2162 using StorableBodyGenCallbackTy =
2163 llvm::OpenMPIRBuilder::StorableBodyGenCallbackTy;
2164
2165 auto sectionsOp = cast<omp::SectionsOp>(opInst);
2166
2167 if (failed(checkImplementationStatus(opInst)))
2168 return failure();
2169
2170 llvm::ArrayRef<bool> isByRef = getIsByRef(sectionsOp.getReductionByref());
2171 assert(isByRef.size() == sectionsOp.getNumReductionVars());
2172
2174 collectReductionDecls(sectionsOp, reductionDecls);
2175 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
2176 findAllocInsertPoints(builder, moduleTranslation);
2177
2178 SmallVector<llvm::Value *> privateReductionVariables(
2179 sectionsOp.getNumReductionVars());
2180 DenseMap<Value, llvm::Value *> reductionVariableMap;
2181
2182 MutableArrayRef<BlockArgument> reductionArgs =
2183 cast<omp::BlockArgOpenMPOpInterface>(opInst).getReductionBlockArgs();
2184
2186 sectionsOp, reductionArgs, builder, moduleTranslation, allocaIP,
2187 reductionDecls, privateReductionVariables, reductionVariableMap,
2188 isByRef)))
2189 return failure();
2190
2191 bool isTaskReductionMod =
2192 sectionsOp.getReductionMod() == omp::ReductionModifier::task &&
2193 sectionsOp.getNumReductionVars() > 0;
2194
2196
2197 for (Operation &op : *sectionsOp.getRegion().begin()) {
2198 auto sectionOp = dyn_cast<omp::SectionOp>(op);
2199 if (!sectionOp) // omp.terminator
2200 continue;
2201
2202 Region &region = sectionOp.getRegion();
2203 auto sectionCB = [&sectionsOp, &region, &builder, &moduleTranslation](
2204 InsertPointTy allocaIP, InsertPointTy codeGenIP,
2205 ArrayRef<llvm::BasicBlock *> deallocBlocks) {
2206 builder.restoreIP(codeGenIP);
2207
2208 // map the omp.section reduction block argument to the omp.sections block
2209 // arguments
2210 // TODO: this assumes that the only block arguments are reduction
2211 // variables
2212 assert(region.getNumArguments() ==
2213 sectionsOp.getRegion().getNumArguments());
2214 for (auto [sectionsArg, sectionArg] : llvm::zip_equal(
2215 sectionsOp.getRegion().getArguments(), region.getArguments())) {
2216 llvm::Value *llvmVal = moduleTranslation.lookupValue(sectionsArg);
2217 assert(llvmVal);
2218 moduleTranslation.mapValue(sectionArg, llvmVal);
2219 }
2220
2221 return convertOmpOpRegions(region, "omp.section.region", builder,
2222 moduleTranslation)
2223 .takeError();
2224 };
2225 sectionCBs.push_back(sectionCB);
2226 }
2227
2228 // No sections within omp.sections operation - skip generation. This situation
2229 // is only possible if there is only a terminator operation inside the
2230 // sections operation
2231 if (sectionCBs.empty())
2232 return success();
2233
2234 // For `reduction(task, ...)` open a task-reduction scope for the worksharing
2235 // region. Participating explicit tasks accumulate into the per-thread private
2236 // copies, which the worksharing reduction then combines across threads. This
2237 // is emitted only after the empty-sections early return above, so it stays
2238 // balanced with the matching fini emitted after the sections region.
2239 if (isTaskReductionMod &&
2240 !emitTaskReductionInitCall(reductionDecls, privateReductionVariables,
2241 "__omp_taskred_mod_", builder, allocaIP,
2242 moduleTranslation, /*isModifier=*/true,
2243 /*isWorksharing=*/true))
2244 return sectionsOp.emitError(
2245 "failed to emit task reduction modifier initialization");
2246
2247 assert(isa<omp::SectionOp>(*sectionsOp.getRegion().op_begin()));
2248
2249 // TODO: Perform appropriate actions according to the data-sharing
2250 // attribute (shared, private, firstprivate, ...) of variables.
2251 // Currently defaults to shared.
2252 auto privCB = [&](InsertPointTy, InsertPointTy codeGenIP, llvm::Value &,
2253 llvm::Value &vPtr, llvm::Value *&replacementValue)
2254 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
2255 replacementValue = &vPtr;
2256 return codeGenIP;
2257 };
2258
2259 // TODO: Perform finalization actions for variables. This has to be
2260 // called for variables which have destructors/finalizers.
2261 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
2262
2263 allocaIP = findAllocInsertPoints(builder, moduleTranslation);
2264 bool isCancellable = constructIsCancellable(sectionsOp);
2265 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2266 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2267 moduleTranslation.getOpenMPBuilder()->createSections(
2268 ompLoc, allocaIP, sectionCBs, privCB, finiCB, isCancellable,
2269 sectionsOp.getNowait());
2270
2271 if (failed(handleError(afterIP, opInst)))
2272 return failure();
2273
2274 builder.restoreIP(*afterIP);
2275
2276 // Close the task-reduction scope before combining the worksharing copies.
2277 if (isTaskReductionMod)
2278 emitTaskReductionModifierFini(/*isWorksharing=*/true, builder,
2279 moduleTranslation);
2280
2281 // Process the reductions if required.
2283 sectionsOp, builder, moduleTranslation, allocaIP, reductionDecls,
2284 privateReductionVariables, isByRef, sectionsOp.getNowait());
2285}
2286
2287/// Converts an OpenMP scope construct into LLVM IR.
2288static LogicalResult
2289convertOmpScope(omp::ScopeOp &scopeOp, llvm::IRBuilderBase &builder,
2290 LLVM::ModuleTranslation &moduleTranslation) {
2291 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2292 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
2293
2294 if (failed(checkImplementationStatus(*scopeOp)))
2295 return failure();
2296
2297 llvm::ArrayRef<bool> isByRef = getIsByRef(scopeOp.getReductionByref());
2298 assert(isByRef.size() == scopeOp.getNumReductionVars());
2299
2300 PrivateVarsInfo privateVarsInfo(scopeOp);
2301
2303 collectReductionDecls(scopeOp, reductionDecls);
2304 InsertPointTy allocaIP = findAllocInsertPoints(builder, moduleTranslation);
2305
2306 SmallVector<llvm::Value *> privateReductionVariables(
2307 scopeOp.getNumReductionVars());
2308 DenseMap<Value, llvm::Value *> reductionVariableMap;
2309
2310 MutableArrayRef<BlockArgument> reductionArgs =
2311 cast<omp::BlockArgOpenMPOpInterface>(*scopeOp).getReductionBlockArgs();
2312
2313 // Allocate private vars before the scope body
2315 scopeOp, builder, moduleTranslation, privateVarsInfo, allocaIP);
2316 if (failed(handleError(afterAllocas, *scopeOp)))
2317 return failure();
2318
2320 scopeOp, reductionArgs, builder, moduleTranslation, allocaIP,
2321 reductionDecls, privateReductionVariables, reductionVariableMap,
2322 isByRef)))
2323 return failure();
2324
2325 auto bodyCB =
2326 [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
2327 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
2328 builder.restoreIP(codeGenIP);
2329
2330 if (handleError(
2331 initPrivateVars(builder, moduleTranslation, privateVarsInfo),
2332 *scopeOp)
2333 .failed())
2334 return llvm::make_error<PreviouslyReportedError>();
2335
2336 if (failed(copyFirstPrivateVars(
2337 scopeOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
2338 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
2339 scopeOp.getPrivateNeedsBarrier())))
2340 return llvm::make_error<PreviouslyReportedError>();
2341
2342 return convertOmpOpRegions(scopeOp.getRegion(), "omp.scope.region", builder,
2343 moduleTranslation)
2344 .takeError();
2345 };
2346
2347 auto finiCB = [&](InsertPointTy codeGenIP) -> llvm::Error {
2348 InsertPointTy oldIP = builder.saveIP();
2349 builder.restoreIP(codeGenIP);
2350 if (failed(cleanupPrivateVars(scopeOp, builder, moduleTranslation,
2351 scopeOp.getLoc(), privateVarsInfo)))
2352 return llvm::make_error<PreviouslyReportedError>();
2353 builder.restoreIP(oldIP);
2354 return llvm::Error::success();
2355 };
2356
2357 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2358 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2359 ompBuilder->createScope(ompLoc, bodyCB, finiCB, scopeOp.getNowait());
2360
2361 if (failed(handleError(afterIP, *scopeOp)))
2362 return failure();
2363
2364 builder.restoreIP(*afterIP);
2365
2366 // Process the reductions if required.
2368 scopeOp, builder, moduleTranslation, allocaIP, reductionDecls,
2369 privateReductionVariables, isByRef, scopeOp.getNowait(),
2370 /*isTeamsReduction=*/false);
2371}
2372
2373/// Converts an OpenMP single construct into LLVM IR using OpenMPIRBuilder.
2374static LogicalResult
2375convertOmpSingle(omp::SingleOp &singleOp, llvm::IRBuilderBase &builder,
2376 LLVM::ModuleTranslation &moduleTranslation) {
2377 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2378 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2379
2380 if (failed(checkImplementationStatus(*singleOp)))
2381 return failure();
2382
2383 auto bodyCB = [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
2384 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) {
2385 builder.restoreIP(codegenIP);
2386 return convertOmpOpRegions(singleOp.getRegion(), "omp.single.region",
2387 builder, moduleTranslation)
2388 .takeError();
2389 };
2390 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
2391
2392 // Handle copyprivate
2393 Operation::operand_range cpVars = singleOp.getCopyprivateVars();
2394 std::optional<ArrayAttr> cpFuncs = singleOp.getCopyprivateSyms();
2397 for (size_t i = 0, e = cpVars.size(); i < e; ++i) {
2398 llvmCPVars.push_back(moduleTranslation.lookupValue(cpVars[i]));
2400 singleOp, cast<SymbolRefAttr>((*cpFuncs)[i]));
2401 llvmCPFuncs.push_back(
2402 moduleTranslation.lookupFunction(llvmFuncOp.getName()));
2403 }
2404
2405 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2406 moduleTranslation.getOpenMPBuilder()->createSingle(
2407 ompLoc, bodyCB, finiCB, singleOp.getNowait(), llvmCPVars,
2408 llvmCPFuncs);
2409
2410 if (failed(handleError(afterIP, *singleOp)))
2411 return failure();
2412
2413 builder.restoreIP(*afterIP);
2414 return success();
2415}
2416
2417static omp::DistributeOp
2419 // Early return if we found more than one distribute op or if we can't find
2420 // any distribute op in the teams region.
2421 omp::DistributeOp distOp;
2422 WalkResult walk = teamsOp.getRegion().walk([&](omp::DistributeOp op) {
2423 if (distOp)
2424 return WalkResult::interrupt();
2425 distOp = op;
2426 return WalkResult::skip();
2427 });
2428 if (walk.wasInterrupted() || !distOp)
2429 return {};
2430
2431 auto iface =
2432 llvm::cast<mlir::omp::BlockArgOpenMPOpInterface>(teamsOp.getOperation());
2433 // Check that all uses of the reduction block arg has the same distribute op
2434 // parent.
2436 for (auto ra : iface.getReductionBlockArgs())
2437 for (auto &use : ra.getUses()) {
2438 auto *useOp = use.getOwner();
2439 // Ignore debug uses.
2440 if (mlir::isa<LLVM::DbgDeclareOp, LLVM::DbgValueOp>(useOp)) {
2441 debugUses.push_back(useOp);
2442 continue;
2443 }
2444 if (!distOp->isProperAncestor(useOp))
2445 return {};
2446 }
2447
2448 // If we are going to use distribute reduction then remove any debug uses of
2449 // the reduction parameters in teamsOp. Otherwise they will be left without
2450 // any mapped value in moduleTranslation and will eventually error out.
2451 for (auto *use : debugUses)
2452 use->erase();
2453 return distOp;
2454}
2455
2456// Convert an OpenMP Teams construct to LLVM IR using OpenMPIRBuilder
2457static LogicalResult
2458convertOmpTeams(omp::TeamsOp op, llvm::IRBuilderBase &builder,
2459 LLVM::ModuleTranslation &moduleTranslation) {
2460 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2461 if (failed(checkImplementationStatus(*op)))
2462 return failure();
2463
2464 DenseMap<Value, llvm::Value *> reductionVariableMap;
2465 unsigned numReductionVars = op.getNumReductionVars();
2467 SmallVector<llvm::Value *> privateReductionVariables(numReductionVars);
2468 llvm::ArrayRef<bool> isByRef;
2469 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
2470 findAllocInsertPoints(builder, moduleTranslation);
2471
2472 // Only do teams reduction if there is no distribute op that captures the
2473 // reduction instead.
2474 bool doTeamsReduction = !getDistributeCapturingTeamsReduction(op);
2475 if (doTeamsReduction) {
2476 isByRef = getIsByRef(op.getReductionByref());
2477
2478 assert(isByRef.size() == op.getNumReductionVars());
2479
2480 MutableArrayRef<BlockArgument> reductionArgs =
2481 llvm::cast<omp::BlockArgOpenMPOpInterface>(*op).getReductionBlockArgs();
2482
2483 collectReductionDecls(op, reductionDecls);
2484
2486 op, reductionArgs, builder, moduleTranslation, allocaIP,
2487 reductionDecls, privateReductionVariables, reductionVariableMap,
2488 isByRef)))
2489 return failure();
2490 }
2491
2492 auto bodyCB = [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
2493 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) {
2495 moduleTranslation, allocaIP, deallocBlocks);
2496 builder.restoreIP(codegenIP);
2497 return convertOmpOpRegions(op.getRegion(), "omp.teams.region", builder,
2498 moduleTranslation)
2499 .takeError();
2500 };
2501
2502 llvm::Value *numTeamsLower = nullptr;
2503 if (Value numTeamsLowerVar = op.getNumTeamsLower())
2504 numTeamsLower = moduleTranslation.lookupValue(numTeamsLowerVar);
2505
2506 llvm::Value *numTeamsUpper = nullptr;
2507 if (!op.getNumTeamsUpperVars().empty())
2508 numTeamsUpper = moduleTranslation.lookupValue(op.getNumTeams(0));
2509
2510 llvm::Value *threadLimit = nullptr;
2511 if (!op.getThreadLimitVars().empty())
2512 threadLimit = moduleTranslation.lookupValue(op.getThreadLimit(0));
2513
2514 llvm::Value *ifExpr = nullptr;
2515 if (Value ifVar = op.getIfExpr())
2516 ifExpr = moduleTranslation.lookupValue(ifVar);
2517
2518 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2519 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2520 moduleTranslation.getOpenMPBuilder()->createTeams(
2521 ompLoc, bodyCB, numTeamsLower, numTeamsUpper, threadLimit, ifExpr);
2522
2523 if (failed(handleError(afterIP, *op)))
2524 return failure();
2525
2526 builder.restoreIP(*afterIP);
2527 if (doTeamsReduction) {
2528 // Process the reductions if required.
2530 op, builder, moduleTranslation, allocaIP, reductionDecls,
2531 privateReductionVariables, isByRef,
2532 /*isNoWait*/ false, /*isTeamsReduction*/ true);
2533 }
2534 return success();
2535}
2536
2537static llvm::omp::RTLDependenceKindTy
2538convertDependKind(mlir::omp::ClauseTaskDepend kind) {
2539 switch (kind) {
2540 case mlir::omp::ClauseTaskDepend::taskdependin:
2541 return llvm::omp::RTLDependenceKindTy::DepIn;
2542 // The OpenMP runtime requires that the codegen for 'depend' clause for
2543 // 'out' dependency kind must be the same as codegen for 'depend' clause
2544 // with 'inout' dependency.
2545 case mlir::omp::ClauseTaskDepend::taskdependout:
2546 case mlir::omp::ClauseTaskDepend::taskdependinout:
2547 return llvm::omp::RTLDependenceKindTy::DepInOut;
2548 case mlir::omp::ClauseTaskDepend::taskdependmutexinoutset:
2549 return llvm::omp::RTLDependenceKindTy::DepMutexInOutSet;
2550 case mlir::omp::ClauseTaskDepend::taskdependinoutset:
2551 return llvm::omp::RTLDependenceKindTy::DepInOutSet;
2552 }
2553 llvm_unreachable("unhandled depend kind");
2554}
2555
2557 std::optional<ArrayAttr> dependKinds, OperandRange dependVars,
2558 LLVM::ModuleTranslation &moduleTranslation,
2560 if (dependVars.empty())
2561 return;
2562 for (auto dep : llvm::zip(dependVars, dependKinds->getValue())) {
2563 auto kind =
2564 cast<mlir::omp::ClauseTaskDependAttr>(std::get<1>(dep)).getValue();
2565 llvm::omp::RTLDependenceKindTy type = convertDependKind(kind);
2566 llvm::Value *depVal = moduleTranslation.lookupValue(std::get<0>(dep));
2567 llvm::OpenMPIRBuilder::DependData dd(type, depVal->getType(), depVal);
2568 dds.emplace_back(dd);
2569 }
2570}
2571
2572/// Shared implementation of a callback which adds a termiator for the new block
2573/// created for the branch taken when an openmp construct is cancelled. The
2574/// terminator is saved in \p cancelTerminators. This callback is invoked only
2575/// if there is cancellation inside of the taskgroup body.
2576/// The terminator will need to be fixed to branch to the correct block to
2577/// cleanup the construct.
2579 SmallVectorImpl<llvm::UncondBrInst *> &cancelTerminators,
2580 llvm::IRBuilderBase &llvmBuilder, llvm::OpenMPIRBuilder &ompBuilder,
2581 mlir::Operation *op, llvm::omp::Directive cancelDirective) {
2582 auto finiCB = [&](llvm::OpenMPIRBuilder::InsertPointTy ip) -> llvm::Error {
2583 llvm::IRBuilderBase::InsertPointGuard guard(llvmBuilder);
2584
2585 // ip is currently in the block branched to if cancellation occurred.
2586 // We need to create a branch to terminate that block.
2587 llvmBuilder.restoreIP(ip);
2588
2589 // We must still clean up the construct after cancelling it, so we need to
2590 // branch to the block that finalizes the taskgroup.
2591 // That block has not been created yet so use this block as a dummy for now
2592 // and fix this after creating the operation.
2593 cancelTerminators.push_back(llvmBuilder.CreateBr(ip.getBlock()));
2594 return llvm::Error::success();
2595 };
2596 // We have to add the cleanup to the OpenMPIRBuilder before the body gets
2597 // created in case the body contains omp.cancel (which will then expect to be
2598 // able to find this cleanup callback).
2599 ompBuilder.pushFinalizationCB(
2600 {finiCB, cancelDirective, constructIsCancellable(op)});
2601}
2602
2603/// If we cancelled the construct, we should branch to the finalization block of
2604/// that construct. OMPIRBuilder structures the CFG such that the cleanup block
2605/// is immediately before the continuation block. Now this finalization has
2606/// been created we can fix the branch.
2607static void
2609 llvm::OpenMPIRBuilder &ompBuilder,
2610 const llvm::OpenMPIRBuilder::InsertPointTy &afterIP) {
2611 ompBuilder.popFinalizationCB();
2612 llvm::BasicBlock *constructFini = afterIP.getBlock()->getSinglePredecessor();
2613 for (llvm::UncondBrInst *cancelBranch : cancelTerminators)
2614 cancelBranch->setSuccessor(constructFini);
2615}
2616
2617namespace {
2618/// TaskContextStructManager takes care of creating and freeing a structure
2619/// containing information needed by the task body to execute.
2620class TaskContextStructManager {
2621public:
2622 TaskContextStructManager(llvm::IRBuilderBase &builder,
2623 LLVM::ModuleTranslation &moduleTranslation,
2624 MutableArrayRef<omp::PrivateClauseOp> privateDecls)
2625 : builder{builder}, moduleTranslation{moduleTranslation},
2626 privateDecls{privateDecls} {}
2627
2628 /// Creates a heap allocated struct containing space for each private
2629 /// variable. Invariant: privateVarTypes, privateDecls, and the elements of
2630 /// the structure should all have the same order (although privateDecls which
2631 /// do not read from the mold argument are skipped).
2632 void generateTaskContextStruct();
2633
2634 /// Create GEPs to access each member of the structure representing a private
2635 /// variable, adding them to llvmPrivateVars. Null values are added where
2636 /// private decls were skipped so that the ordering continues to match the
2637 /// private decls.
2638 void createGEPsToPrivateVars();
2639
2640 /// Given the address of the structure, return a GEP for each private variable
2641 /// in the structure. Null values are added where private decls were skipped
2642 /// so that the ordering continues to match the private decls.
2643 /// Must be called after generateTaskContextStruct().
2644 SmallVector<llvm::Value *>
2645 createGEPsToPrivateVars(llvm::Value *altStructPtr) const;
2646
2647 /// De-allocate the task context structure.
2648 void freeStructPtr();
2649
2650 MutableArrayRef<llvm::Value *> getLLVMPrivateVarGEPs() {
2651 return llvmPrivateVarGEPs;
2652 }
2653
2654 llvm::Value *getStructPtr() { return structPtr; }
2655
2656private:
2657 llvm::IRBuilderBase &builder;
2658 LLVM::ModuleTranslation &moduleTranslation;
2659 MutableArrayRef<omp::PrivateClauseOp> privateDecls;
2660
2661 /// The type of each member of the structure, in order.
2662 SmallVector<llvm::Type *> privateVarTypes;
2663
2664 /// LLVM values for each private variable, or null if that private variable is
2665 /// not included in the task context structure
2666 SmallVector<llvm::Value *> llvmPrivateVarGEPs;
2667
2668 /// A pointer to the structure containing context for this task.
2669 llvm::Value *structPtr = nullptr;
2670 /// The type of the structure
2671 llvm::Type *structTy = nullptr;
2672};
2673
2674/// IteratorInfo extracts and prepares loop bounds information from an
2675/// mlir::omp::IteratorOp for lowering to LLVM IR.
2676///
2677/// It computes the per-dimension trip counts and the total linearized trip
2678/// count, casted to i64. These are used to build a canonical loop and to
2679/// reconstruct the physical induction variables inside the loop body.
2680class IteratorInfo {
2681private:
2682 llvm::SmallVector<llvm::Value *> lowerBounds;
2683 llvm::SmallVector<llvm::Value *> upperBounds;
2684 llvm::SmallVector<llvm::Value *> steps;
2685 llvm::SmallVector<llvm::Value *> trips;
2686 unsigned dims;
2687 llvm::Value *totalTrips;
2688
2689 llvm::Value *lookUpAsI64(mlir::Value val, const LLVM::ModuleTranslation &mt,
2690 llvm::IRBuilderBase &builder) {
2691 llvm::Value *v = mt.lookupValue(val);
2692 if (!v)
2693 return nullptr;
2694 if (v->getType()->isIntegerTy(64))
2695 return v;
2696 if (v->getType()->isIntegerTy())
2697 return builder.CreateSExtOrTrunc(v, builder.getInt64Ty());
2698 return nullptr;
2699 }
2700
2701public:
2702 IteratorInfo(mlir::omp::IteratorOp itersOp,
2703 mlir::LLVM::ModuleTranslation &moduleTranslation,
2704 llvm::IRBuilderBase &builder) {
2705 dims = itersOp.getLoopLowerBounds().size();
2706 lowerBounds.resize(dims);
2707 upperBounds.resize(dims);
2708 steps.resize(dims);
2709 trips.resize(dims);
2710
2711 for (unsigned d = 0; d < dims; ++d) {
2712 llvm::Value *lb = lookUpAsI64(itersOp.getLoopLowerBounds()[d],
2713 moduleTranslation, builder);
2714 llvm::Value *ub = lookUpAsI64(itersOp.getLoopUpperBounds()[d],
2715 moduleTranslation, builder);
2716 llvm::Value *st =
2717 lookUpAsI64(itersOp.getLoopSteps()[d], moduleTranslation, builder);
2718 assert(lb && ub && st &&
2719 "Expect lowerBounds, upperBounds, and steps in IteratorOp");
2720 assert((!llvm::isa<llvm::ConstantInt>(st) ||
2721 !llvm::cast<llvm::ConstantInt>(st)->isZero()) &&
2722 "Expect non-zero step in IteratorOp");
2723
2724 lowerBounds[d] = lb;
2725 upperBounds[d] = ub;
2726 steps[d] = st;
2727
2728 // trips = ((ub - lb) / step) + 1 (inclusive ub, assume positive step)
2729 llvm::Value *diff = builder.CreateSub(ub, lb);
2730 llvm::Value *div = builder.CreateSDiv(diff, st);
2731 trips[d] = builder.CreateAdd(
2732 div, llvm::ConstantInt::get(builder.getInt64Ty(), 1));
2733 }
2734
2735 totalTrips = llvm::ConstantInt::get(builder.getInt64Ty(), 1);
2736 for (unsigned d = 0; d < dims; ++d)
2737 totalTrips = builder.CreateMul(totalTrips, trips[d]);
2738 }
2739
2740 unsigned getDims() const { return dims; }
2741 llvm::ArrayRef<llvm::Value *> getLowerBounds() const { return lowerBounds; }
2742 llvm::ArrayRef<llvm::Value *> getUpperBounds() const { return upperBounds; }
2743 llvm::ArrayRef<llvm::Value *> getSteps() const { return steps; }
2744 llvm::ArrayRef<llvm::Value *> getTrips() const { return trips; }
2745 llvm::Value *getTotalTrips() const { return totalTrips; }
2746};
2747
2748} // namespace
2749
2750void TaskContextStructManager::generateTaskContextStruct() {
2751 if (privateDecls.empty())
2752 return;
2753 privateVarTypes.reserve(privateDecls.size());
2754
2755 for (omp::PrivateClauseOp &privOp : privateDecls) {
2756 // Skip private variables which can safely be allocated and initialised
2757 // inside of the task
2758 if (!privOp.readsFromMold())
2759 continue;
2760 Type mlirType = privOp.getType();
2761 privateVarTypes.push_back(moduleTranslation.convertType(mlirType));
2762 }
2763
2764 if (privateVarTypes.empty())
2765 return;
2766
2767 structTy = llvm::StructType::get(moduleTranslation.getLLVMContext(),
2768 privateVarTypes);
2769
2770 llvm::DataLayout dataLayout =
2771 builder.GetInsertBlock()->getModule()->getDataLayout();
2772 llvm::Type *intPtrTy = builder.getIntPtrTy(dataLayout);
2773 llvm::Constant *allocSize = llvm::ConstantExpr::getSizeOf(structTy);
2774
2775 // Heap allocate the structure
2776 structPtr = builder.CreateMalloc(intPtrTy, structTy, allocSize,
2777 /*ArraySize=*/nullptr, /*MallocF=*/nullptr,
2778 "omp.task.context_ptr");
2779}
2780
2781SmallVector<llvm::Value *> TaskContextStructManager::createGEPsToPrivateVars(
2782 llvm::Value *altStructPtr) const {
2783 SmallVector<llvm::Value *> ret;
2784
2785 // Create GEPs for each struct member
2786 ret.reserve(privateDecls.size());
2787 llvm::Value *zero = builder.getInt32(0);
2788 unsigned i = 0;
2789 for (auto privDecl : privateDecls) {
2790 if (!privDecl.readsFromMold()) {
2791 // Handle this inside of the task so we don't pass unnessecary vars in
2792 ret.push_back(nullptr);
2793 continue;
2794 }
2795 llvm::Value *iVal = builder.getInt32(i);
2796 llvm::Value *gep = builder.CreateGEP(structTy, altStructPtr, {zero, iVal});
2797 ret.push_back(gep);
2798 i += 1;
2799 }
2800 return ret;
2801}
2802
2803void TaskContextStructManager::createGEPsToPrivateVars() {
2804 if (!structPtr)
2805 assert(privateVarTypes.empty());
2806 // Still need to run createGEPsToPrivateVars to populate llvmPrivateVarGEPs
2807 // with null values for skipped private decls
2808
2809 llvmPrivateVarGEPs = createGEPsToPrivateVars(structPtr);
2810}
2811
2812void TaskContextStructManager::freeStructPtr() {
2813 if (!structPtr)
2814 return;
2815
2816 llvm::IRBuilderBase::InsertPointGuard guard{builder};
2817 // Ensure we don't put the call to free() after the terminator
2818 builder.SetInsertPoint(builder.GetInsertBlock()->getTerminator());
2819 builder.CreateFree(structPtr);
2820}
2821
2822static void storeAffinityEntry(llvm::IRBuilderBase &builder,
2823 llvm::OpenMPIRBuilder &ompBuilder,
2824 llvm::Value *affinityList, llvm::Value *index,
2825 llvm::Value *addr, llvm::Value *len) {
2826 llvm::StructType *kmpTaskAffinityInfoTy =
2827 ompBuilder.getKmpTaskAffinityInfoTy();
2828 llvm::Value *entry = builder.CreateInBoundsGEP(
2829 kmpTaskAffinityInfoTy, affinityList, index, "omp.affinity.entry");
2830
2831 addr = builder.CreatePtrToInt(addr, kmpTaskAffinityInfoTy->getElementType(0));
2832 len = builder.CreateIntCast(len, kmpTaskAffinityInfoTy->getElementType(1),
2833 /*isSigned=*/false);
2834 llvm::Value *flags = builder.getInt32(0);
2835
2836 builder.CreateStore(addr,
2837 builder.CreateStructGEP(kmpTaskAffinityInfoTy, entry, 0));
2838 builder.CreateStore(len,
2839 builder.CreateStructGEP(kmpTaskAffinityInfoTy, entry, 1));
2840 builder.CreateStore(flags,
2841 builder.CreateStructGEP(kmpTaskAffinityInfoTy, entry, 2));
2842}
2843
2845 llvm::IRBuilderBase &builder,
2846 LLVM::ModuleTranslation &moduleTranslation,
2847 llvm::Value *affinityList) {
2848 for (auto [i, affinityVar] : llvm::enumerate(affinityVars)) {
2849 auto entryOp = affinityVar.getDefiningOp<mlir::omp::AffinityEntryOp>();
2850 assert(entryOp && "affinity item must be omp.affinity_entry");
2851
2852 llvm::Value *addr = moduleTranslation.lookupValue(entryOp.getAddr());
2853 llvm::Value *len = moduleTranslation.lookupValue(entryOp.getLen());
2854 assert(addr && len && "expect affinity addr and len to be non-null");
2855 storeAffinityEntry(builder, *moduleTranslation.getOpenMPBuilder(),
2856 affinityList, builder.getInt64(i), addr, len);
2857 }
2858}
2859
2860static mlir::LogicalResult
2861convertIteratorRegion(llvm::Value *linearIV, IteratorInfo &iterInfo,
2862 mlir::Block &iteratorRegionBlock,
2863 llvm::IRBuilderBase &builder,
2864 LLVM::ModuleTranslation &moduleTranslation) {
2865 llvm::Value *tmp = linearIV;
2866 for (int d = (int)iterInfo.getDims() - 1; d >= 0; --d) {
2867 llvm::Value *trip = iterInfo.getTrips()[d];
2868 // idx_d = tmp % trip_d
2869 llvm::Value *idx = builder.CreateURem(tmp, trip);
2870 // tmp = tmp / trip_d
2871 tmp = builder.CreateUDiv(tmp, trip);
2872
2873 // physIV_d = lb_d + idx_d * step_d
2874 llvm::Value *physIV = builder.CreateAdd(
2875 iterInfo.getLowerBounds()[d],
2876 builder.CreateMul(idx, iterInfo.getSteps()[d]), "omp.it.phys_iv");
2877
2878 moduleTranslation.mapValue(iteratorRegionBlock.getArgument(d), physIV);
2879 }
2880
2881 // Translate the iterator region into the loop body.
2882 moduleTranslation.mapBlock(&iteratorRegionBlock, builder.GetInsertBlock());
2883 if (mlir::failed(moduleTranslation.convertBlock(iteratorRegionBlock,
2884 /*ignoreArguments=*/true,
2885 builder))) {
2886 return mlir::failure();
2887 }
2888 return mlir::success();
2889}
2890
2892 llvm::function_ref<void(llvm::Value *linearIV, mlir::omp::YieldOp yield)>;
2893
2894static mlir::LogicalResult
2895fillIteratorLoop(mlir::omp::IteratorOp itersOp, llvm::IRBuilderBase &builder,
2896 mlir::LLVM::ModuleTranslation &moduleTranslation,
2897 IteratorInfo &iterInfo, llvm::StringRef loopName,
2898 IteratorStoreEntryTy genStoreEntry) {
2899 mlir::Region &itersRegion = itersOp.getRegion();
2900 mlir::Block &iteratorRegionBlock = itersRegion.front();
2901
2902 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
2903
2904 auto bodyGen = [&](llvm::OpenMPIRBuilder::InsertPointTy bodyIP,
2905 llvm::Value *linearIV) -> llvm::Error {
2906 llvm::IRBuilderBase::InsertPointGuard guard(builder);
2907 builder.restoreIP(bodyIP);
2908
2909 if (failed(convertIteratorRegion(linearIV, iterInfo, iteratorRegionBlock,
2910 builder, moduleTranslation))) {
2911 return llvm::make_error<llvm::StringError>(
2912 "failed to convert iterator region", llvm::inconvertibleErrorCode());
2913 }
2914
2915 auto yield =
2916 mlir::dyn_cast<mlir::omp::YieldOp>(iteratorRegionBlock.getTerminator());
2917 assert(yield && yield.getResults().size() == 1 &&
2918 "expect omp.yield in iterator region to have one result");
2919
2920 genStoreEntry(linearIV, yield);
2921
2922 // Iterator-region block/value mappings are temporary for this conversion,
2923 // clear them to avoid stale entries in ModuleTranslation.
2924 moduleTranslation.forgetMapping(itersRegion);
2925
2926 return llvm::Error::success();
2927 };
2928
2929 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2930 moduleTranslation.getOpenMPBuilder()->createIteratorLoop(
2931 loc, iterInfo.getTotalTrips(), bodyGen, loopName);
2932 if (failed(handleError(afterIP, *itersOp)))
2933 return failure();
2934
2935 builder.restoreIP(*afterIP);
2936
2937 return mlir::success();
2938}
2939
2940static mlir::LogicalResult
2941buildAffinityData(mlir::omp::TaskOp &taskOp, llvm::IRBuilderBase &builder,
2942 mlir::LLVM::ModuleTranslation &moduleTranslation,
2943 llvm::OpenMPIRBuilder::AffinityData &ad) {
2944
2945 if (taskOp.getAffinityVars().empty() && taskOp.getIterated().empty()) {
2946 ad.Count = nullptr;
2947 ad.Info = nullptr;
2948 return mlir::success();
2949 }
2950
2952 llvm::StructType *kmpTaskAffinityInfoTy =
2953 moduleTranslation.getOpenMPBuilder()->getKmpTaskAffinityInfoTy();
2954
2955 auto allocateAffinityList = [&](llvm::Value *count) -> llvm::Value * {
2956 llvm::IRBuilderBase::InsertPointGuard guard(builder);
2957 if (llvm::isa<llvm::Constant>(count) || llvm::isa<llvm::Argument>(count))
2958 builder.restoreIP(findAllocInsertPoints(builder, moduleTranslation));
2959 return builder.CreateAlloca(kmpTaskAffinityInfoTy, count,
2960 "omp.affinity_list");
2961 };
2962
2963 auto createAffinity =
2964 [&](llvm::Value *count,
2965 llvm::Value *info) -> llvm::OpenMPIRBuilder::AffinityData {
2966 llvm::OpenMPIRBuilder::AffinityData ad{};
2967 ad.Count = builder.CreateTrunc(count, builder.getInt32Ty());
2968 ad.Info =
2969 builder.CreatePointerBitCastOrAddrSpaceCast(info, builder.getPtrTy(0));
2970 return ad;
2971 };
2972
2973 if (!taskOp.getAffinityVars().empty()) {
2974 llvm::Value *count = llvm::ConstantInt::get(
2975 builder.getInt64Ty(), taskOp.getAffinityVars().size());
2976 llvm::Value *list = allocateAffinityList(count);
2977 fillAffinityLocators(taskOp.getAffinityVars(), builder, moduleTranslation,
2978 list);
2979 ads.emplace_back(createAffinity(count, list));
2980 }
2981
2982 if (!taskOp.getIterated().empty()) {
2983 for (auto [i, iter] : llvm::enumerate(taskOp.getIterated())) {
2984 auto itersOp = iter.getDefiningOp<omp::IteratorOp>();
2985 assert(itersOp && "iterated value must be defined by omp.iterator");
2986 IteratorInfo iterInfo(itersOp, moduleTranslation, builder);
2987 llvm::Value *affList = allocateAffinityList(iterInfo.getTotalTrips());
2988 if (failed(fillIteratorLoop(
2989 itersOp, builder, moduleTranslation, iterInfo, "iterator",
2990 [&](llvm::Value *linearIV, mlir::omp::YieldOp yield) {
2991 auto entryOp = yield.getResults()[0]
2992 .getDefiningOp<mlir::omp::AffinityEntryOp>();
2993 assert(entryOp && "expect yield produce an affinity entry");
2994 llvm::Value *addr =
2995 moduleTranslation.lookupValue(entryOp.getAddr());
2996 llvm::Value *len =
2997 moduleTranslation.lookupValue(entryOp.getLen());
2998 storeAffinityEntry(builder,
2999 *moduleTranslation.getOpenMPBuilder(),
3000 affList, linearIV, addr, len);
3001 })))
3002 return llvm::failure();
3003 ads.emplace_back(createAffinity(iterInfo.getTotalTrips(), affList));
3004 }
3005 }
3006
3007 llvm::Value *totalAffinityCount = builder.getInt32(0);
3008 for (const auto &affinity : ads)
3009 totalAffinityCount = builder.CreateAdd(
3010 totalAffinityCount,
3011 builder.CreateIntCast(affinity.Count, builder.getInt32Ty(),
3012 /*isSigned=*/false));
3013
3014 llvm::Value *affinityInfo = ads.front().Info;
3015 if (ads.size() > 1) {
3016 llvm::StructType *kmpTaskAffinityInfoTy =
3017 moduleTranslation.getOpenMPBuilder()->getKmpTaskAffinityInfoTy();
3018 llvm::Value *affinityInfoElemSize = builder.getInt64(
3019 moduleTranslation.getLLVMModule()->getDataLayout().getTypeAllocSize(
3020 kmpTaskAffinityInfoTy));
3021
3022 llvm::Value *packedAffinityInfo = allocateAffinityList(totalAffinityCount);
3023 llvm::Value *packedAffinityInfoOffset = builder.getInt32(0);
3024 for (const auto &affinity : ads) {
3025 llvm::Value *affinityCount = builder.CreateIntCast(
3026 affinity.Count, builder.getInt32Ty(), /*isSigned=*/false);
3027 llvm::Value *affinityCountInt64 = builder.CreateIntCast(
3028 affinityCount, builder.getInt64Ty(), /*isSigned=*/false);
3029 llvm::Value *affinityInfoSize =
3030 builder.CreateMul(affinityCountInt64, affinityInfoElemSize);
3031
3032 llvm::Value *packedAffinityInfoIndex = builder.CreateIntCast(
3033 packedAffinityInfoOffset, kmpTaskAffinityInfoTy->getElementType(0),
3034 /*isSigned=*/false);
3035 packedAffinityInfoIndex = builder.CreateInBoundsGEP(
3036 kmpTaskAffinityInfoTy, packedAffinityInfo, packedAffinityInfoIndex);
3037
3038 builder.CreateMemCpy(
3039 packedAffinityInfoIndex, llvm::Align(1),
3040 builder.CreatePointerBitCastOrAddrSpaceCast(
3041 affinity.Info, builder.getPtrTy(packedAffinityInfoIndex->getType()
3042 ->getPointerAddressSpace())),
3043 llvm::Align(1), affinityInfoSize);
3044
3045 packedAffinityInfoOffset =
3046 builder.CreateAdd(packedAffinityInfoOffset, affinityCount);
3047 }
3048
3049 affinityInfo = packedAffinityInfo;
3050 }
3051
3052 ad.Count = totalAffinityCount;
3053 ad.Info = affinityInfo;
3054
3055 return mlir::success();
3056}
3057
3058// Allocates a single kmp_dep_info array sized to hold both locator
3059// (non-iterated) and iterated entries, fills the locator entries first, then
3060// runs an iterator loop for each iterator modifier object.
3061static mlir::LogicalResult
3062buildDependData(OperandRange dependVars, std::optional<ArrayAttr> dependKinds,
3063 OperandRange dependIterated,
3064 std::optional<ArrayAttr> dependIteratedKinds,
3065 llvm::IRBuilderBase &builder,
3066 mlir::LLVM::ModuleTranslation &moduleTranslation,
3067 llvm::OpenMPIRBuilder::DependenciesInfo &taskDeps) {
3068 if (dependIterated.empty()) {
3069 buildDependDataLocator(dependKinds, dependVars, moduleTranslation,
3070 taskDeps.Deps);
3071 return mlir::success();
3072 }
3073
3074 llvm::OpenMPIRBuilder &ompBuilder = *moduleTranslation.getOpenMPBuilder();
3075 llvm::Type *dependInfoTy = ompBuilder.DependInfo;
3076 unsigned numLocator = dependVars.size();
3077
3078 // Compute total count: locator deps + sum of iterator trip counts.
3079 llvm::Value *totalCount =
3080 llvm::ConstantInt::get(builder.getInt64Ty(), numLocator);
3081
3083 for (auto iter : dependIterated) {
3084 auto itersOp = iter.getDefiningOp<mlir::omp::IteratorOp>();
3085 assert(itersOp && "depend_iterated value must be defined by omp.iterator");
3086 iterInfos.emplace_back(itersOp, moduleTranslation, builder);
3087 totalCount =
3088 builder.CreateAdd(totalCount, iterInfos.back().getTotalTrips());
3089 }
3090
3091 // Heap-allocate the kmp_depend_info array so we don't risk
3092 // dynamic-sized alloca outside the entry block (e.g. inside loops).
3093 llvm::Constant *allocSize = llvm::ConstantExpr::getSizeOf(dependInfoTy);
3094 llvm::Value *depArray =
3095 builder.CreateMalloc(ompBuilder.SizeTy, dependInfoTy, allocSize,
3096 totalCount, /*MallocF=*/nullptr, ".dep.arr.addr");
3097
3098 // Fill non-iterated entries at indices [0, numLocator).
3099 if (numLocator > 0) {
3101 buildDependDataLocator(dependKinds, dependVars, moduleTranslation, dds);
3102 for (auto [i, dd] : llvm::enumerate(dds)) {
3103 llvm::Value *idx = llvm::ConstantInt::get(builder.getInt64Ty(), i);
3104 llvm::Value *entry =
3105 builder.CreateInBoundsGEP(dependInfoTy, depArray, idx);
3106 ompBuilder.emitTaskDependency(builder, entry, dd);
3107 }
3108 }
3109
3110 // Fill iterated entries starting at index numLocator.
3111 llvm::Value *offset =
3112 llvm::ConstantInt::get(builder.getInt64Ty(), numLocator);
3113 for (auto [i, iterInfo] : llvm::enumerate(iterInfos)) {
3114 auto kindAttr = cast<mlir::omp::ClauseTaskDependAttr>(
3115 dependIteratedKinds->getValue()[i]);
3116 llvm::omp::RTLDependenceKindTy rtlKind =
3117 convertDependKind(kindAttr.getValue());
3118
3119 auto itersOp = dependIterated[i].getDefiningOp<mlir::omp::IteratorOp>();
3120 if (failed(fillIteratorLoop(
3121 itersOp, builder, moduleTranslation, iterInfo, "dep_iterator",
3122 [&](llvm::Value *linearIV, mlir::omp::YieldOp yield) {
3123 llvm::Value *addr =
3124 moduleTranslation.lookupValue(yield.getResults()[0]);
3125 llvm::Value *idx = builder.CreateAdd(offset, linearIV);
3126 llvm::Value *entry =
3127 builder.CreateInBoundsGEP(dependInfoTy, depArray, idx);
3128 ompBuilder.emitTaskDependency(
3129 builder, entry,
3130 llvm::OpenMPIRBuilder::DependData{rtlKind, addr->getType(),
3131 addr});
3132 })))
3133 return mlir::failure();
3134
3135 // Advance offset by the trip count of this iterator.
3136 offset = builder.CreateAdd(offset, iterInfo.getTotalTrips());
3137 }
3138
3139 taskDeps.DepArray = depArray;
3140 taskDeps.NumDeps = builder.CreateTrunc(totalCount, builder.getInt32Ty());
3141 return mlir::success();
3142}
3143
3144/// Converts an OpenMP task construct into LLVM IR using OpenMPIRBuilder.
3145static LogicalResult
3146convertOmpTaskOp(omp::TaskOp taskOp, llvm::IRBuilderBase &builder,
3147 LLVM::ModuleTranslation &moduleTranslation) {
3148 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
3149 if (failed(checkImplementationStatus(*taskOp)))
3150 return failure();
3151
3152 PrivateVarsInfo privateVarsInfo(taskOp);
3153 TaskContextStructManager taskStructMgr{builder, moduleTranslation,
3154 privateVarsInfo.privatizers};
3155
3156 // Allocate and copy private variables before creating the task. This avoids
3157 // accessing invalid memory if (after this scope ends) the private variables
3158 // are initialized from host variables or if the variables are copied into
3159 // from host variables (firstprivate). The insertion point is just before
3160 // where the code for creating and scheduling the task will go. That puts this
3161 // code outside of the outlined task region, which is what we want because
3162 // this way the initialization and copy regions are executed immediately while
3163 // the host variable data are still live.
3165 InsertPointTy allocaIP =
3166 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
3167
3168 // Not using splitBB() because that requires the current block to have a
3169 // terminator.
3170 assert(builder.GetInsertPoint() == builder.GetInsertBlock()->end());
3171 llvm::BasicBlock *taskStartBlock = llvm::BasicBlock::Create(
3172 builder.getContext(), "omp.task.start",
3173 /*Parent=*/builder.GetInsertBlock()->getParent());
3174 llvm::Instruction *branchToTaskStartBlock = builder.CreateBr(taskStartBlock);
3175 builder.SetInsertPoint(branchToTaskStartBlock);
3176
3177 // Now do this again to make the initialization and copy blocks
3178 llvm::BasicBlock *copyBlock =
3179 splitBB(builder, /*CreateBranch=*/true, "omp.private.copy");
3180 llvm::BasicBlock *initBlock =
3181 splitBB(builder, /*CreateBranch=*/true, "omp.private.init");
3182
3183 // Now the control flow graph should look like
3184 // starter_block:
3185 // <---- where we started when convertOmpTaskOp was called
3186 // br %omp.private.init
3187 // omp.private.init:
3188 // br %omp.private.copy
3189 // omp.private.copy:
3190 // br %omp.task.start
3191 // omp.task.start:
3192 // <---- where we want the insertion point to be when we call createTask()
3193
3194 // Save the alloca insertion point on ModuleTranslation stack for use in
3195 // nested regions.
3197 moduleTranslation, allocaIP, deallocBlocks);
3198
3199 // Allocate and initialize private variables
3200 builder.SetInsertPoint(initBlock->getTerminator());
3201
3202 // Create task variable structure
3203 taskStructMgr.generateTaskContextStruct();
3204 // GEPs so that we can initialize the variables. Don't use these GEPs inside
3205 // of the body otherwise it will be the GEP not the struct which is fowarded
3206 // to the outlined function. GEPs forwarded in this way are passed in a
3207 // stack-allocated (by OpenMPIRBuilder) structure which is not safe for tasks
3208 // which may not be executed until after the current stack frame goes out of
3209 // scope.
3210 taskStructMgr.createGEPsToPrivateVars();
3211
3212 for (auto [privDecl, mlirPrivVar, blockArg, llvmPrivateVarAlloc] :
3213 llvm::zip_equal(privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
3214 privateVarsInfo.blockArgs,
3215 taskStructMgr.getLLVMPrivateVarGEPs())) {
3216 // To be handled inside the task.
3217 if (!privDecl.readsFromMold())
3218 continue;
3219 assert(llvmPrivateVarAlloc &&
3220 "reads from mold so shouldn't have been skipped");
3221
3222 llvm::Expected<llvm::Value *> privateVarOrErr =
3223 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
3224 blockArg, llvmPrivateVarAlloc, initBlock);
3225 if (!privateVarOrErr)
3226 return handleError(privateVarOrErr, *taskOp.getOperation());
3227
3229
3230 // TODO: this is a bit of a hack for Fortran character boxes.
3231 // Character boxes are passed by value into the init region and then the
3232 // initialized character box is yielded by value. Here we need to store the
3233 // yielded value into the private allocation, and load the private
3234 // allocation to match the type expected by region block arguments.
3235 [[maybe_unused]] llvm::Value *llvmPrivateVar = llvmPrivateVarAlloc;
3236 if ((privateVarOrErr.get() != llvmPrivateVarAlloc) &&
3237 !mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3238 builder.CreateStore(privateVarOrErr.get(), llvmPrivateVarAlloc);
3239 // Load it so we have the value pointed to by the GEP
3240 llvmPrivateVar = builder.CreateLoad(privateVarOrErr.get()->getType(),
3241 llvmPrivateVarAlloc);
3242 }
3243 assert(llvmPrivateVar->getType() ==
3244 moduleTranslation.convertType(blockArg.getType()));
3245
3246 // Mapping blockArg -> llvmPrivateVarAlloc is done inside the body callback
3247 // so that OpenMPIRBuilder doesn't try to pass each GEP address through a
3248 // stack allocated structure.
3249 }
3250
3251 // firstprivate copy region
3252 setInsertPointForPossiblyEmptyBlock(builder, copyBlock);
3253 if (failed(copyFirstPrivateVars(
3254 taskOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
3255 taskStructMgr.getLLVMPrivateVarGEPs(), privateVarsInfo.privatizers,
3256 taskOp.getPrivateNeedsBarrier())))
3257 return llvm::failure();
3258
3259 llvm::OpenMPIRBuilder::AffinityData ad;
3260 if (failed(buildAffinityData(taskOp, builder, moduleTranslation, ad)))
3261 return llvm::failure();
3262
3263 // Resolve and validate in_reduction declarations. Byref in_reduction has
3264 // already been rejected by checkImplementationStatus; the helper rejects the
3265 // remaining richer declare_reduction shapes (two-argument initializer,
3266 // cleanup region, missing combiner). This is pure MLIR symbol-table work and
3267 // emits no IR. The matching task_reduction descriptor is registered by an
3268 // enclosing taskgroup; here we only look the per-task storage up at runtime.
3271 taskOp.getOperation(), taskOp.getInReductionSyms(), "omp.task",
3272 "in_reduction", inRedDecls)))
3273 return failure();
3274 SmallVector<llvm::Value *> inRedOrigPtrs;
3275 inRedOrigPtrs.reserve(inRedDecls.size());
3276 for (Value v : taskOp.getInReductionVars())
3277 inRedOrigPtrs.push_back(moduleTranslation.lookupValue(v));
3278
3279 // Set up for call to createTask()
3280 builder.SetInsertPoint(taskStartBlock);
3281
3282 auto bodyCB =
3283 [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
3284 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
3285 // Save the alloca insertion point on ModuleTranslation stack for use in
3286 // nested regions.
3288 moduleTranslation, allocaIP, deallocBlocks);
3289
3290 // translate the body of the task:
3291 builder.restoreIP(codegenIP);
3292
3293 llvm::BasicBlock *privInitBlock = nullptr;
3294 privateVarsInfo.llvmVars.resize(privateVarsInfo.blockArgs.size());
3295 for (auto [i, zip] : llvm::enumerate(llvm::zip_equal(
3296 privateVarsInfo.blockArgs, privateVarsInfo.privatizers,
3297 privateVarsInfo.mlirVars))) {
3298 auto [blockArg, privDecl, mlirPrivVar] = zip;
3299 // This is handled before the task executes
3300 if (privDecl.readsFromMold())
3301 continue;
3302
3303 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3304 llvm::Type *llvmAllocType =
3305 moduleTranslation.convertType(privDecl.getType());
3306 builder.SetInsertPoint(allocaIP.getBlock()->getTerminator());
3307 llvm::Value *llvmPrivateVar = builder.CreateAlloca(
3308 llvmAllocType, /*ArraySize=*/nullptr, "omp.private.alloc");
3309
3310 llvm::Expected<llvm::Value *> privateVarOrError =
3311 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
3312 blockArg, llvmPrivateVar, privInitBlock);
3313 if (!privateVarOrError)
3314 return privateVarOrError.takeError();
3315 moduleTranslation.mapValue(blockArg, privateVarOrError.get());
3316 privateVarsInfo.llvmVars[i] = privateVarOrError.get();
3317 }
3318
3319 taskStructMgr.createGEPsToPrivateVars();
3320 for (auto [i, llvmPrivVar] :
3321 llvm::enumerate(taskStructMgr.getLLVMPrivateVarGEPs())) {
3322 if (!llvmPrivVar) {
3323 assert(privateVarsInfo.llvmVars[i] &&
3324 "This is added in the loop above");
3325 continue;
3326 }
3327 privateVarsInfo.llvmVars[i] = llvmPrivVar;
3328 }
3329
3330 // Find and map the addresses of each variable within the task context
3331 // structure
3332 for (auto [blockArg, llvmPrivateVar, privateDecl] :
3333 llvm::zip_equal(privateVarsInfo.blockArgs, privateVarsInfo.llvmVars,
3334 privateVarsInfo.privatizers)) {
3335 // This was handled above.
3336 if (!privateDecl.readsFromMold())
3337 continue;
3338 // Fix broken pass-by-value case for Fortran character boxes
3339 if (!mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3340 llvmPrivateVar = builder.CreateLoad(
3341 moduleTranslation.convertType(blockArg.getType()), llvmPrivateVar);
3342 }
3343 assert(llvmPrivateVar->getType() ==
3344 moduleTranslation.convertType(blockArg.getType()));
3345 moduleTranslation.mapValue(blockArg, llvmPrivateVar);
3346 }
3347
3348 // Map in_reduction block arguments to the per-task private storage returned
3349 // by __kmpc_task_reduction_get_th_data. This call must be emitted inside
3350 // the to-be-outlined task body so that it returns the *executing* thread's
3351 // gtid (not the encountering thread's). The descriptor is NULL: the runtime
3352 // walks up enclosing taskgroups to find the matching task_reduction
3353 // registration for `origPtr`. The original pointers are auto-captured into
3354 // the task shareds aggregate by CodeExtractor during
3355 // OpenMPIRBuilder::finalize.
3356 if (!inRedDecls.empty()) {
3357 auto iface = cast<omp::BlockArgOpenMPOpInterface>(taskOp.getOperation());
3358 llvm::OpenMPIRBuilder &ompB = *moduleTranslation.getOpenMPBuilder();
3359 llvm::Module *m = moduleTranslation.getLLVMModule();
3360 llvm::LLVMContext &llvmCtx = m->getContext();
3361 llvm::OpenMPIRBuilder::LocationDescription bodyLoc(builder);
3362 uint32_t srcLocSize;
3363 llvm::Constant *srcLocStr =
3364 ompB.getOrCreateSrcLocStr(bodyLoc, srcLocSize);
3365 llvm::Value *bodyIdent = ompB.getOrCreateIdent(srcLocStr, srcLocSize);
3366 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the gtid
3367 // call lands inside the to-be-outlined task body.
3368 ompB.updateToLocation(bodyLoc);
3369 llvm::Value *bodyGtid = ompB.getOrCreateThreadID(bodyIdent);
3370 llvm::FunctionCallee getThData = ompB.getOrCreateRuntimeFunction(
3371 *m, llvm::omp::OMPRTL___kmpc_task_reduction_get_th_data);
3372 llvm::Type *ptrTy = llvm::PointerType::getUnqual(llvmCtx);
3373 llvm::Value *nullDesc = llvm::ConstantPointerNull::get(ptrTy);
3374 ArrayRef<BlockArgument> inRedBlockArgs = iface.getInReductionBlockArgs();
3375 for (auto [blockArg, origPtr] :
3376 llvm::zip_equal(inRedBlockArgs, inRedOrigPtrs)) {
3377 // __kmpc_task_reduction_get_th_data takes and returns a generic,
3378 // default-address-space `ptr`. Normalize a non-default-address-space
3379 // original pointer to the generic address space before the call, and
3380 // cast the returned private pointer back to the block argument's
3381 // address space when it differs (mirrors the taskloop reduction
3382 // remapping in convertOmpTaskloopContextOp).
3383 llvm::Value *lookupPtr = origPtr;
3384 if (auto *origPtrTy =
3385 llvm::dyn_cast<llvm::PointerType>(lookupPtr->getType());
3386 origPtrTy && origPtrTy->getAddressSpace() != 0)
3387 lookupPtr = builder.CreateAddrSpaceCast(lookupPtr, ptrTy);
3388 llvm::Value *priv = builder.CreateCall(
3389 getThData, {bodyGtid, nullDesc, lookupPtr}, "omp.inred.priv");
3390 if (auto *argPtrTy = llvm::dyn_cast<llvm::PointerType>(
3391 moduleTranslation.convertType(blockArg.getType()));
3392 argPtrTy && argPtrTy->getAddressSpace() != 0)
3393 priv = builder.CreateAddrSpaceCast(priv, argPtrTy);
3394 moduleTranslation.mapValue(blockArg, priv);
3395 }
3396 }
3397
3398 auto continuationBlockOrError = convertOmpOpRegions(
3399 taskOp.getRegion(), "omp.task.region", builder, moduleTranslation);
3400 if (failed(handleError(continuationBlockOrError, *taskOp)))
3401 return llvm::make_error<PreviouslyReportedError>();
3402
3403 builder.SetInsertPoint(continuationBlockOrError.get()->getTerminator());
3404
3405 if (failed(cleanupPrivateVars(taskOp, builder, moduleTranslation,
3406 taskOp.getLoc(), privateVarsInfo)))
3407 return llvm::make_error<PreviouslyReportedError>();
3408
3409 // Free heap allocated task context structure at the end of the task.
3410 taskStructMgr.freeStructPtr();
3411
3412 return llvm::Error::success();
3413 };
3414
3415 llvm::OpenMPIRBuilder &ompBuilder = *moduleTranslation.getOpenMPBuilder();
3416 SmallVector<llvm::UncondBrInst *> cancelTerminators;
3417 // The directive to match here is OMPD_taskgroup because it is the taskgroup
3418 // which is canceled. This is handled here because it is the task's cleanup
3419 // block which should be branched to.
3420 pushCancelFinalizationCB(cancelTerminators, builder, ompBuilder, taskOp,
3421 llvm::omp::Directive::OMPD_taskgroup);
3422
3423 llvm::OpenMPIRBuilder::DependenciesInfo dependencies;
3424 if (failed(buildDependData(taskOp.getDependVars(), taskOp.getDependKinds(),
3425 taskOp.getDependIterated(),
3426 taskOp.getDependIteratedKinds(), builder,
3427 moduleTranslation, dependencies)))
3428 return failure();
3429
3430 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
3431 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
3432 moduleTranslation.getOpenMPBuilder()->createTask(
3433 ompLoc, allocaIP, deallocBlocks, bodyCB, !taskOp.getUntied(),
3434 moduleTranslation.lookupValue(taskOp.getFinal()),
3435 moduleTranslation.lookupValue(taskOp.getIfExpr()), dependencies, ad,
3436 taskOp.getMergeable(),
3437 moduleTranslation.lookupValue(taskOp.getEventHandle()),
3438 moduleTranslation.lookupValue(taskOp.getPriority()));
3439
3440 if (failed(handleError(afterIP, *taskOp)))
3441 return failure();
3442
3443 // Set the correct branch target for task cancellation
3444 popCancelFinalizationCB(cancelTerminators, ompBuilder, afterIP.get());
3445
3446 builder.restoreIP(*afterIP);
3447
3448 if (dependencies.DepArray)
3449 builder.CreateFree(dependencies.DepArray);
3450
3451 return success();
3452}
3453
3454/// The correct entry point is convertOmpTaskloopContextOp. This gets called
3455/// whilst lowering the body of the taskloop context (i.e. the task function).
3456static LogicalResult
3457convertOmpTaskloopWrapperOp(omp::TaskloopWrapperOp loopWrapperOp,
3458 llvm::IRBuilderBase &builder,
3459 LLVM::ModuleTranslation &moduleTranslation) {
3460 mlir::Operation &opInst = *loopWrapperOp.getOperation();
3461 if (failed(checkImplementationStatus(opInst)))
3462 return failure();
3463
3464 // Recurse into the loop body.
3465 auto continuationBlockOrError = convertOmpOpRegions(
3466 loopWrapperOp.getRegion(), "omp.taskloop.wrapper.region", builder,
3467 moduleTranslation);
3468
3469 if (failed(handleError(continuationBlockOrError, opInst)))
3470 return failure();
3471
3472 builder.SetInsertPoint(continuationBlockOrError.get());
3473 return success();
3474}
3475
3476/// Look up the given value in the mapping, and if it's not there, translate its
3477/// defining operation at the current builder insertion point. Only pure,
3478/// regionless operations are supported because the same operation will later be
3479/// translated again when the taskloop body itself is lowered.
3480static llvm::Expected<llvm::Value *>
3482 LLVM::ModuleTranslation &moduleTranslation,
3483 llvm::IRBuilderBase &builder) {
3484 if (llvm::Value *mapped = moduleTranslation.lookupValue(value))
3485 return mapped;
3486
3487 Operation *defOp = value.getDefiningOp();
3488 if (!defOp)
3489 return llvm::make_error<llvm::StringError>(
3490 "value is a block argument and is not mapped",
3491 llvm::inconvertibleErrorCode());
3492 if (defOp->getNumRegions() != 0 || !isPure(defOp))
3493 return llvm::make_error<llvm::StringError>(
3494 "unsupported op defining taskloop loop bound",
3495 llvm::inconvertibleErrorCode());
3496
3497 SmallVector<Value> mappingsToRemove;
3498 mappingsToRemove.reserve(defOp->getNumOperands() + defOp->getNumResults());
3499 for (Value operand : defOp->getOperands()) {
3500 if (moduleTranslation.lookupValue(operand))
3501 continue;
3502
3503 llvm::Expected<llvm::Value *> operandOrError =
3504 lookupOrTranslatePureValue(operand, moduleTranslation, builder);
3505 if (!operandOrError)
3506 return operandOrError.takeError();
3507 moduleTranslation.mapValue(operand, *operandOrError);
3508 mappingsToRemove.push_back(operand);
3509 }
3510
3511 if (failed(moduleTranslation.convertOperation(*defOp, builder)))
3512 return llvm::make_error<llvm::StringError>(
3513 "failed to convert op defining taskloop loop bound",
3514 llvm::inconvertibleErrorCode());
3515
3516 llvm::Value *result = moduleTranslation.lookupValue(value);
3517 assert(result && "expected conversion of loop bound op to produce a value");
3518
3519 for (Value resultValue : defOp->getResults()) {
3520 if (moduleTranslation.lookupValue(resultValue))
3521 mappingsToRemove.push_back(resultValue);
3522 }
3523 for (Value mappedValue : mappingsToRemove)
3524 moduleTranslation.forgetMapping(mappedValue);
3525
3526 return result;
3527}
3528
3529static llvm::Error
3530computeTaskloopBounds(omp::LoopNestOp loopOp, llvm::IRBuilderBase &builder,
3531 LLVM::ModuleTranslation &moduleTranslation,
3532 llvm::Value *&lbVal, llvm::Value *&ubVal,
3533 llvm::Value *&stepVal) {
3534 Operation::operand_range lowerBounds = loopOp.getLoopLowerBounds();
3535 Operation::operand_range upperBounds = loopOp.getLoopUpperBounds();
3536 Operation::operand_range steps = loopOp.getLoopSteps();
3537
3538 llvm::Expected<llvm::Value *> firstLbOrErr =
3539 lookupOrTranslatePureValue(lowerBounds[0], moduleTranslation, builder);
3540 if (!firstLbOrErr)
3541 return firstLbOrErr.takeError();
3542
3543 llvm::Type *boundType = (*firstLbOrErr)->getType();
3544 ubVal = builder.getIntN(boundType->getIntegerBitWidth(), 1);
3545 if (loopOp.getCollapseNumLoops() > 1) {
3546 // In cases where Collapse is used with Taskloop, the upper bound of the
3547 // iteration space needs to be recalculated to cater for the collapsed loop.
3548 // The Collapsed Loop UpperBound is the product of all collapsed
3549 // loop's tripcount.
3550 // The LowerBound for collapsed loops is always 1. When the loops are
3551 // collapsed, it will reset the bounds and introduce processing to ensure
3552 // the index's are presented as expected. As this happens after creating
3553 // Taskloop, these bounds need predicting. Example:
3554 // !$omp taskloop collapse(2)
3555 // do i = 1, 10
3556 // do j = 1, 5
3557 // ..
3558 // end do
3559 // end do
3560 // This loop above has a total of 50 iterations, so the lb will be 1, and
3561 // the ub will be 50. collapseLoops in OMPIRBuilder then handles ensuring
3562 // that i and j are properly presented when used in the loop.
3563 for (uint64_t i = 0; i < loopOp.getCollapseNumLoops(); i++) {
3565 i == 0 ? std::move(firstLbOrErr)
3566 : lookupOrTranslatePureValue(lowerBounds[i], moduleTranslation,
3567 builder);
3568 if (!lbOrErr)
3569 return lbOrErr.takeError();
3571 upperBounds[i], moduleTranslation, builder);
3572 if (!ubOrErr)
3573 return ubOrErr.takeError();
3575 lookupOrTranslatePureValue(steps[i], moduleTranslation, builder);
3576 if (!stepOrErr)
3577 return stepOrErr.takeError();
3578
3579 llvm::Value *loopLb = *lbOrErr;
3580 llvm::Value *loopUb = *ubOrErr;
3581 llvm::Value *loopStep = *stepOrErr;
3582 // In some cases, such as where the ub is less than the lb so the loop
3583 // steps down, the calculation for the loopTripCount is swapped. To ensure
3584 // the correct value is found, calculate both UB - LB and LB - UB then
3585 // select which value to use depending on how the loop has been
3586 // configured.
3587 llvm::Value *loopLbMinusOne = builder.CreateSub(
3588 loopLb, builder.getIntN(boundType->getIntegerBitWidth(), 1));
3589 llvm::Value *loopUbMinusOne = builder.CreateSub(
3590 loopUb, builder.getIntN(boundType->getIntegerBitWidth(), 1));
3591 llvm::Value *boundsCmp = builder.CreateICmpSLT(loopLb, loopUb);
3592 llvm::Value *ubMinusLb = builder.CreateSub(loopUb, loopLbMinusOne);
3593 llvm::Value *lbMinusUb = builder.CreateSub(loopLb, loopUbMinusOne);
3594 llvm::Value *loopTripCount =
3595 builder.CreateSelect(boundsCmp, ubMinusLb, lbMinusUb);
3596 loopTripCount = builder.CreateBinaryIntrinsic(
3597 llvm::Intrinsic::abs, loopTripCount, builder.getFalse());
3598 // For loops that have a step value not equal to 1, we need to adjust the
3599 // trip count to ensure the correct number of iterations for the loop is
3600 // captured.
3601 llvm::Value *loopTripCountDivStep =
3602 builder.CreateSDiv(loopTripCount, loopStep);
3603 loopTripCountDivStep = builder.CreateBinaryIntrinsic(
3604 llvm::Intrinsic::abs, loopTripCountDivStep, builder.getFalse());
3605 llvm::Value *loopTripCountRem =
3606 builder.CreateSRem(loopTripCount, loopStep);
3607 loopTripCountRem = builder.CreateBinaryIntrinsic(
3608 llvm::Intrinsic::abs, loopTripCountRem, builder.getFalse());
3609 llvm::Value *needsRoundUp = builder.CreateICmpNE(
3610 loopTripCountRem,
3611 builder.getIntN(loopTripCountRem->getType()->getIntegerBitWidth(),
3612 0));
3613 loopTripCount =
3614 builder.CreateAdd(loopTripCountDivStep,
3615 builder.CreateZExtOrTrunc(
3616 needsRoundUp, loopTripCountDivStep->getType()));
3617 ubVal = builder.CreateMul(ubVal, loopTripCount);
3618 }
3619 lbVal = builder.getIntN(boundType->getIntegerBitWidth(), 1);
3620 stepVal = builder.getIntN(boundType->getIntegerBitWidth(), 1);
3621 } else {
3623 lookupOrTranslatePureValue(upperBounds[0], moduleTranslation, builder);
3624 if (!ubOrErr)
3625 return ubOrErr.takeError();
3627 lookupOrTranslatePureValue(steps[0], moduleTranslation, builder);
3628 if (!stepOrErr)
3629 return stepOrErr.takeError();
3630 lbVal = *firstLbOrErr;
3631 ubVal = *ubOrErr;
3632 stepVal = *stepOrErr;
3633 }
3634
3635 assert(lbVal != nullptr && "Expected value for lbVal");
3636 assert(ubVal != nullptr && "Expected value for ubVal");
3637 assert(stepVal != nullptr && "Expected value for stepVal");
3638 return llvm::Error::success();
3639}
3640
3641// Converts an OpenMP taskloop construct into LLVM IR using OpenMPIRBuilder.
3642static LogicalResult
3643convertOmpTaskloopContextOp(omp::TaskloopContextOp contextOp,
3644 llvm::IRBuilderBase &builder,
3645 LLVM::ModuleTranslation &moduleTranslation) {
3646 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
3647 mlir::Operation &opInst = *contextOp.getOperation();
3648 omp::TaskloopWrapperOp loopWrapperOp = contextOp.getLoopOp();
3649 if (failed(checkImplementationStatus(opInst)))
3650 return failure();
3651
3652 // It stores the pointer of allocated firstprivate copies,
3653 // which can be used later for freeing the allocated space.
3654 SmallVector<llvm::Value *> llvmFirstPrivateVars;
3655 PrivateVarsInfo privateVarsInfo(contextOp);
3656 TaskContextStructManager taskStructMgr{builder, moduleTranslation,
3657 privateVarsInfo.privatizers};
3658
3660 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
3661 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
3662
3663 assert(builder.GetInsertPoint() == builder.GetInsertBlock()->end());
3664 llvm::BasicBlock *taskloopStartBlock = llvm::BasicBlock::Create(
3665 builder.getContext(), "omp.taskloop.wrapper.start",
3666 /*Parent=*/builder.GetInsertBlock()->getParent());
3667 llvm::Instruction *branchToTaskloopStartBlock =
3668 builder.CreateBr(taskloopStartBlock);
3669 builder.SetInsertPoint(branchToTaskloopStartBlock);
3670
3671 llvm::BasicBlock *copyBlock =
3672 splitBB(builder, /*CreateBranch=*/true, "omp.private.copy");
3673 llvm::BasicBlock *initBlock =
3674 splitBB(builder, /*CreateBranch=*/true, "omp.private.init");
3675
3677 moduleTranslation, allocaIP, deallocBlocks);
3678
3679 // Allocate and initialize private variables
3680 builder.SetInsertPoint(initBlock->getTerminator());
3681
3682 // TODO: don't allocate if the loop has zero iterations.
3683 taskStructMgr.generateTaskContextStruct();
3684 taskStructMgr.createGEPsToPrivateVars();
3685
3686 llvmFirstPrivateVars.resize(privateVarsInfo.blockArgs.size());
3687
3688 for (auto [i, zip] : llvm::enumerate(llvm::zip_equal(
3689 privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
3690 privateVarsInfo.blockArgs, taskStructMgr.getLLVMPrivateVarGEPs()))) {
3691 auto [privDecl, mlirPrivVar, blockArg, llvmPrivateVarAlloc] = zip;
3692 // To be handled inside the taskloop.
3693 if (!privDecl.readsFromMold())
3694 continue;
3695 assert(llvmPrivateVarAlloc &&
3696 "reads from mold so shouldn't have been skipped");
3697
3698 llvm::Expected<llvm::Value *> privateVarOrErr =
3699 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
3700 blockArg, llvmPrivateVarAlloc, initBlock);
3701 if (!privateVarOrErr)
3702 return handleError(privateVarOrErr, *contextOp.getOperation());
3703
3704 llvmFirstPrivateVars[i] = privateVarOrErr.get();
3705
3706 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3707 builder.SetInsertPoint(builder.GetInsertBlock()->getTerminator());
3708
3709 [[maybe_unused]] llvm::Value *llvmPrivateVar = llvmPrivateVarAlloc;
3710 if ((privateVarOrErr.get() != llvmPrivateVarAlloc) &&
3711 !mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3712 builder.CreateStore(privateVarOrErr.get(), llvmPrivateVarAlloc);
3713 // Load it so we have the value pointed to by the GEP
3714 llvmPrivateVar = builder.CreateLoad(privateVarOrErr.get()->getType(),
3715 llvmPrivateVarAlloc);
3716 }
3717 assert(llvmPrivateVar->getType() ==
3718 moduleTranslation.convertType(blockArg.getType()));
3719 }
3720
3721 // firstprivate copy region
3722 setInsertPointForPossiblyEmptyBlock(builder, copyBlock);
3723 if (failed(copyFirstPrivateVars(
3724 contextOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
3725 taskStructMgr.getLLVMPrivateVarGEPs(), privateVarsInfo.privatizers,
3726 contextOp.getPrivateNeedsBarrier())))
3727 return llvm::failure();
3728
3729 // Resolve and validate reduction / in_reduction declarations up front.
3730 // This is pure MLIR symbol-table work and does not emit IR, so do it
3731 // before moving the builder to the taskloop start block. Richer
3732 // declare_reduction shapes (byref) have been rejected already by
3733 // checkImplementationStatus; the rest (two-argument initializer, cleanup
3734 // region, missing combiner) are rejected by the helper.
3737 contextOp.getOperation(), contextOp.getReductionSyms(),
3738 "omp.taskloop.context", "reduction", redDecls)))
3739 return failure();
3742 contextOp.getOperation(), contextOp.getInReductionSyms(),
3743 "omp.taskloop.context", "in_reduction", inRedDecls)))
3744 return failure();
3745
3746 // The op verifier rejects nogroup + reduction, so no check is needed here.
3747
3748 SmallVector<llvm::Value *> redOrigPtrs;
3749 redOrigPtrs.reserve(redDecls.size());
3750 for (Value v : contextOp.getReductionVars())
3751 redOrigPtrs.push_back(moduleTranslation.lookupValue(v));
3752 SmallVector<llvm::Value *> inRedOrigPtrs;
3753 inRedOrigPtrs.reserve(inRedDecls.size());
3754 for (Value v : contextOp.getInReductionVars())
3755 inRedOrigPtrs.push_back(moduleTranslation.lookupValue(v));
3756
3757 // Set up insertion point for emitting the implicit-taskgroup reduction
3758 // setup (if any) and for the subsequent call to createTaskloop().
3759 builder.SetInsertPoint(taskloopStartBlock);
3760
3761 llvm::OpenMPIRBuilder &ompBuilderRef = *moduleTranslation.getOpenMPBuilder();
3762 llvm::Module *module = moduleTranslation.getLLVMModule();
3763
3764 // If we have task_reduction items, we must emit our own implicit
3765 // __kmpc_taskgroup so that the descriptor returned by __kmpc_taskred_init
3766 // is associated with that taskgroup. We then force NoGroup=true so that
3767 // OpenMPIRBuilder::createTaskloop does not emit a second taskgroup.
3768 bool implicitTaskgroup = !redDecls.empty();
3769 llvm::Value *redDesc = nullptr;
3770 if (implicitTaskgroup) {
3771 llvm::OpenMPIRBuilder::LocationDescription redLoc(builder);
3772 uint32_t srcLocSize;
3773 llvm::Constant *srcLocStr =
3774 ompBuilderRef.getOrCreateSrcLocStr(redLoc, srcLocSize);
3775 llvm::Value *ident = ompBuilderRef.getOrCreateIdent(srcLocStr, srcLocSize);
3776 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the
3777 // gtid call lands at our insertion point.
3778 ompBuilderRef.updateToLocation(redLoc);
3779 llvm::Value *outerGtid = ompBuilderRef.getOrCreateThreadID(ident);
3780 llvm::FunctionCallee taskgroupFn = ompBuilderRef.getOrCreateRuntimeFunction(
3781 *module, llvm::omp::OMPRTL___kmpc_taskgroup);
3782 builder.CreateCall(taskgroupFn, {ident, outerGtid});
3783
3784 redDesc = emitTaskReductionInitCall(redDecls, redOrigPtrs,
3785 "__omp_taskloop_taskred_", builder,
3786 allocaIP, moduleTranslation);
3787 if (!redDesc)
3788 return failure();
3789 }
3790
3791 auto loopOp = cast<omp::LoopNestOp>(loopWrapperOp.getWrappedLoop());
3792 llvm::Value *lbVal = nullptr;
3793 llvm::Value *ubVal = nullptr;
3794 llvm::Value *stepVal = nullptr;
3795 if (llvm::Error err = computeTaskloopBounds(
3796 loopOp, builder, moduleTranslation, lbVal, ubVal, stepVal))
3797 return handleError(std::move(err), opInst);
3798
3799 auto bodyCB =
3800 [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
3801 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
3802 // Save the alloca insertion point on ModuleTranslation stack for use in
3803 // nested regions.
3805 moduleTranslation, allocaIP, deallocBlocks);
3806
3807 // translate the body of the taskloop:
3808 builder.restoreIP(codegenIP);
3809
3810 llvm::BasicBlock *privInitBlock = nullptr;
3811 privateVarsInfo.llvmVars.resize(privateVarsInfo.blockArgs.size());
3812 for (auto [i, zip] : llvm::enumerate(llvm::zip_equal(
3813 privateVarsInfo.blockArgs, privateVarsInfo.privatizers,
3814 privateVarsInfo.mlirVars))) {
3815 auto [blockArg, privDecl, mlirPrivVar] = zip;
3816 // This is handled before the task executes
3817 if (privDecl.readsFromMold())
3818 continue;
3819
3820 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3821 llvm::Type *llvmAllocType =
3822 moduleTranslation.convertType(privDecl.getType());
3823 builder.SetInsertPoint(allocaIP.getBlock()->getTerminator());
3824 llvm::Value *llvmPrivateVar = builder.CreateAlloca(
3825 llvmAllocType, /*ArraySize=*/nullptr, "omp.private.alloc");
3826
3827 llvm::Expected<llvm::Value *> privateVarOrError =
3828 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
3829 blockArg, llvmPrivateVar, privInitBlock);
3830 if (!privateVarOrError)
3831 return privateVarOrError.takeError();
3832 moduleTranslation.mapValue(blockArg, privateVarOrError.get());
3833 privateVarsInfo.llvmVars[i] = privateVarOrError.get();
3834 }
3835
3836 taskStructMgr.createGEPsToPrivateVars();
3837 for (auto [i, llvmPrivVar] :
3838 llvm::enumerate(taskStructMgr.getLLVMPrivateVarGEPs())) {
3839 if (!llvmPrivVar) {
3840 assert(privateVarsInfo.llvmVars[i] &&
3841 "This is added in the loop above");
3842 continue;
3843 }
3844 privateVarsInfo.llvmVars[i] = llvmPrivVar;
3845 }
3846
3847 // Find and map the addresses of each variable within the taskloop context
3848 // structure
3849 for (auto [blockArg, llvmPrivateVar, privateDecl] :
3850 llvm::zip_equal(privateVarsInfo.blockArgs, privateVarsInfo.llvmVars,
3851 privateVarsInfo.privatizers)) {
3852 // This was handled above.
3853 if (!privateDecl.readsFromMold())
3854 continue;
3855 // Fix broken pass-by-value case for Fortran character boxes
3856 if (!mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3857 llvmPrivateVar = builder.CreateLoad(
3858 moduleTranslation.convertType(blockArg.getType()), llvmPrivateVar);
3859 }
3860 assert(llvmPrivateVar->getType() ==
3861 moduleTranslation.convertType(blockArg.getType()));
3862 moduleTranslation.mapValue(blockArg, llvmPrivateVar);
3863 }
3864
3865 // Map reduction and in_reduction block arguments to the per-task private
3866 // storage returned by __kmpc_task_reduction_get_th_data. This call must
3867 // be emitted inside the to-be-outlined task body so that it returns the
3868 // *executing* thread's gtid (not the encountering thread's). The
3869 // taskgroup descriptor `redDesc` is computed in the outer scope and is
3870 // auto-captured into the task shareds aggregate by CodeExtractor during
3871 // OpenMPIRBuilder::finalize. For in_reduction the descriptor is NULL:
3872 // the runtime walks up enclosing taskgroups to find the matching
3873 // task_reduction registration for `origPtr`.
3874 if (!redDecls.empty() || !inRedDecls.empty()) {
3875 auto iface =
3876 cast<omp::BlockArgOpenMPOpInterface>(contextOp.getOperation());
3877 llvm::OpenMPIRBuilder &ompB = *moduleTranslation.getOpenMPBuilder();
3878 llvm::Module *m = moduleTranslation.getLLVMModule();
3879 llvm::LLVMContext &llvmCtx = m->getContext();
3880 llvm::OpenMPIRBuilder::LocationDescription bodyLoc(builder);
3881 uint32_t srcLocSize;
3882 llvm::Constant *srcLocStr =
3883 ompB.getOrCreateSrcLocStr(bodyLoc, srcLocSize);
3884 llvm::Value *bodyIdent = ompB.getOrCreateIdent(srcLocStr, srcLocSize);
3885 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the
3886 // gtid call lands inside the to-be-outlined task body.
3887 ompB.updateToLocation(bodyLoc);
3888 llvm::Value *bodyGtid = ompB.getOrCreateThreadID(bodyIdent);
3889 llvm::FunctionCallee getThData = ompB.getOrCreateRuntimeFunction(
3890 *m, llvm::omp::OMPRTL___kmpc_task_reduction_get_th_data);
3891 llvm::Type *ptrTy = llvm::PointerType::getUnqual(llvmCtx);
3892
3893 // Emit one __kmpc_task_reduction_get_th_data lookup for a reduction /
3894 // in_reduction item and map its block argument to the per-task private
3895 // storage the runtime returns. The runtime entry point takes (and
3896 // returns) a generic, default-address-space `ptr`, so normalize a
3897 // non-default-address-space original pointer to the generic address
3898 // space before the call (mirroring the descriptor setup in
3899 // emitTaskReductionInitCall), and cast the returned private pointer back
3900 // to the block argument's address space when that differs.
3901 auto remapReductionArg = [&](BlockArgument blockArg, llvm::Value *desc,
3902 llvm::Value *origPtr,
3903 const llvm::Twine &name) {
3904 if (auto *origPtrTy =
3905 llvm::dyn_cast<llvm::PointerType>(origPtr->getType());
3906 origPtrTy && origPtrTy->getAddressSpace() != 0)
3907 origPtr = builder.CreateAddrSpaceCast(origPtr, ptrTy);
3908 llvm::Value *priv =
3909 builder.CreateCall(getThData, {bodyGtid, desc, origPtr}, name);
3910 if (auto *argPtrTy = llvm::dyn_cast<llvm::PointerType>(
3911 moduleTranslation.convertType(blockArg.getType()));
3912 argPtrTy && argPtrTy->getAddressSpace() != 0)
3913 priv = builder.CreateAddrSpaceCast(priv, argPtrTy);
3914 moduleTranslation.mapValue(blockArg, priv);
3915 };
3916
3917 ArrayRef<BlockArgument> redBlockArgs = iface.getReductionBlockArgs();
3918 for (auto [blockArg, origPtr] :
3919 llvm::zip_equal(redBlockArgs, redOrigPtrs))
3920 remapReductionArg(blockArg, redDesc, origPtr, "omp.taskred.priv");
3921 ArrayRef<BlockArgument> inRedBlockArgs = iface.getInReductionBlockArgs();
3922 llvm::Value *nullDesc = llvm::ConstantPointerNull::get(ptrTy);
3923 for (auto [blockArg, origPtr] :
3924 llvm::zip_equal(inRedBlockArgs, inRedOrigPtrs))
3925 remapReductionArg(blockArg, nullDesc, origPtr, "omp.inred.priv");
3926 }
3927
3928 // Lower the contents of the taskloop context region: this is the body of
3929 // the generated task, not the loop.
3930 auto continuationBlockOrError = convertOmpOpRegions(
3931 contextOp.getRegion(), "omp.taskloop.context.region", builder,
3932 moduleTranslation);
3933
3934 if (failed(handleError(continuationBlockOrError, opInst)))
3935 return llvm::make_error<PreviouslyReportedError>();
3936
3937 builder.SetInsertPoint(continuationBlockOrError.get()->getTerminator());
3938
3939 // This is freeing the private variables as mapped inside of the task: these
3940 // will be per-task private copies possibly after task duplication. This is
3941 // handled transparently by how these are passed to the structure passed
3942 // into the outlined function. When the task is duplicated, that structure
3943 // is duplicated too.
3944 if (failed(cleanupPrivateVars(contextOp, builder, moduleTranslation,
3945 contextOp.getLoc(), privateVarsInfo)))
3946 return llvm::make_error<PreviouslyReportedError>();
3947 // Similarly, the task context structure freed inside the task is the
3948 // per-task copy after task duplication.
3949 taskStructMgr.freeStructPtr();
3950
3951 return llvm::Error::success();
3952 };
3953
3954 // Taskloop divides into an appropriate number of tasks by repeatedly
3955 // duplicating the original task. Each time this is done, the task context
3956 // structure must be duplicated too.
3957 auto taskDupCB = [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
3958 llvm::Value *destPtr, llvm::Value *srcPtr)
3960 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3961 builder.restoreIP(codegenIP);
3962
3963 llvm::Type *ptrTy =
3964 builder.getPtrTy(srcPtr->getType()->getPointerAddressSpace());
3965 llvm::Value *src =
3966 builder.CreateLoad(ptrTy, srcPtr, "omp.taskloop.context.src");
3967
3968 TaskContextStructManager &srcStructMgr = taskStructMgr;
3969 TaskContextStructManager destStructMgr(builder, moduleTranslation,
3970 privateVarsInfo.privatizers);
3971 destStructMgr.generateTaskContextStruct();
3972 llvm::Value *dest = destStructMgr.getStructPtr();
3973 dest->setName("omp.taskloop.context.dest");
3974 builder.CreateStore(dest, destPtr);
3975
3977 srcStructMgr.createGEPsToPrivateVars(src);
3979 destStructMgr.createGEPsToPrivateVars(dest);
3980
3981 // Inline init regions.
3982 for (auto [privDecl, mold, blockArg, llvmPrivateVarAlloc] :
3983 llvm::zip_equal(privateVarsInfo.privatizers, srcGEPs,
3984 privateVarsInfo.blockArgs, destGEPs)) {
3985 // To be handled inside task body.
3986 if (!privDecl.readsFromMold())
3987 continue;
3988 assert(llvmPrivateVarAlloc &&
3989 "reads from mold so shouldn't have been skipped");
3990
3991 llvm::Value *moldArg = materializeRegionArgValue(
3992 builder, moduleTranslation, privDecl.getInitMoldArg(), mold);
3994 builder, moduleTranslation, privDecl, moldArg, blockArg,
3995 llvmPrivateVarAlloc, builder.GetInsertBlock());
3996 if (!privateVarOrErr)
3997 return privateVarOrErr.takeError();
3998
4000
4001 // TODO: this is a bit of a hack for Fortran character boxes.
4002 // Character boxes are passed by value into the init region and then the
4003 // initialized character box is yielded by value. Here we need to store
4004 // the yielded value into the private allocation, and load the private
4005 // allocation to match the type expected by region block arguments.
4006 [[maybe_unused]] llvm::Value *llvmPrivateVar = llvmPrivateVarAlloc;
4007 if ((privateVarOrErr.get() != llvmPrivateVarAlloc) &&
4008 !mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
4009 builder.CreateStore(privateVarOrErr.get(), llvmPrivateVarAlloc);
4010 // Load it so we have the value pointed to by the GEP
4011 llvmPrivateVar = builder.CreateLoad(privateVarOrErr.get()->getType(),
4012 llvmPrivateVarAlloc);
4013 }
4014 assert(llvmPrivateVar->getType() ==
4015 moduleTranslation.convertType(blockArg.getType()));
4016
4017 // Mapping blockArg -> llvmPrivateVarAlloc is done inside the body
4018 // callback so that OpenMPIRBuilder doesn't try to pass each GEP address
4019 // through a stack allocated structure.
4020 }
4021
4022 if (failed(copyFirstPrivateVars(contextOp.getOperation(), builder,
4023 moduleTranslation, srcGEPs, destGEPs,
4024 privateVarsInfo.privatizers,
4025 contextOp.getPrivateNeedsBarrier())))
4026 return llvm::make_error<PreviouslyReportedError>();
4027
4028 return builder.saveIP();
4029 };
4030
4031 auto loopInfo = [&]() -> llvm::Expected<llvm::CanonicalLoopInfo *> {
4032 llvm::CanonicalLoopInfo *loopInfo = findCurrentLoopInfo(moduleTranslation);
4033 return loopInfo;
4034 };
4035
4036 llvm::Value *ifCond = nullptr;
4037 llvm::Value *grainsize = nullptr;
4038 int sched = 0; // default
4039 mlir::Value grainsizeVal = contextOp.getGrainsize();
4040 mlir::Value numTasksVal = contextOp.getNumTasks();
4041 if (Value ifVar = contextOp.getIfExpr())
4042 ifCond = moduleTranslation.lookupValue(ifVar);
4043 if (grainsizeVal) {
4044 grainsize = moduleTranslation.lookupValue(grainsizeVal);
4045 sched = 1; // grainsize
4046 } else if (numTasksVal) {
4047 grainsize = moduleTranslation.lookupValue(numTasksVal);
4048 sched = 2; // num_tasks
4049 }
4050
4051 llvm::OpenMPIRBuilder::TaskDupCallbackTy taskDupOrNull = nullptr;
4052 if (taskStructMgr.getStructPtr())
4053 taskDupOrNull = taskDupCB;
4054
4055 llvm::OpenMPIRBuilder &ompBuilder = *moduleTranslation.getOpenMPBuilder();
4056 SmallVector<llvm::UncondBrInst *> cancelTerminators;
4057 // The directive to match here is OMPD_taskgroup because it is the
4058 // taskgroup which is canceled. This is handled here because it is the
4059 // task's cleanup block which should be branched to. It doesn't depend upon
4060 // nogroup because even in that case the taskloop might still be inside an
4061 // explicit taskgroup.
4062 pushCancelFinalizationCB(cancelTerminators, builder, ompBuilder, contextOp,
4063 llvm::omp::Directive::OMPD_taskgroup);
4064
4065 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4066 bool effectiveNoGroup = contextOp.getNogroup() || implicitTaskgroup;
4067 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
4068 moduleTranslation.getOpenMPBuilder()->createTaskloop(
4069 ompLoc, allocaIP, deallocBlocks, bodyCB, loopInfo, lbVal, ubVal,
4070 stepVal, contextOp.getUntied(), ifCond, grainsize, effectiveNoGroup,
4071 sched, moduleTranslation.lookupValue(contextOp.getFinal()),
4072 contextOp.getMergeable(),
4073 moduleTranslation.lookupValue(contextOp.getPriority()),
4074 loopOp.getCollapseNumLoops(), taskDupOrNull,
4075 taskStructMgr.getStructPtr());
4076
4077 if (failed(handleError(afterIP, opInst)))
4078 return failure();
4079
4080 popCancelFinalizationCB(cancelTerminators, ompBuilder, afterIP.get());
4081
4082 builder.restoreIP(*afterIP);
4083
4084 // Close the implicit taskgroup we opened for task_reduction. The end call
4085 // must execute on the encountering thread, so use the outer-scope gtid.
4086 if (implicitTaskgroup) {
4087 llvm::OpenMPIRBuilder::LocationDescription endLoc(builder);
4088 uint32_t srcLocSize;
4089 llvm::Constant *srcLocStr =
4090 ompBuilder.getOrCreateSrcLocStr(endLoc, srcLocSize);
4091 llvm::Value *ident = ompBuilder.getOrCreateIdent(srcLocStr, srcLocSize);
4092 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the
4093 // gtid call lands at our insertion point.
4094 ompBuilder.updateToLocation(endLoc);
4095 llvm::Value *outerGtid = ompBuilder.getOrCreateThreadID(ident);
4096 llvm::FunctionCallee endTgFn = ompBuilder.getOrCreateRuntimeFunction(
4097 *moduleTranslation.getLLVMModule(),
4098 llvm::omp::OMPRTL___kmpc_end_taskgroup);
4099 builder.CreateCall(endTgFn, {ident, outerGtid});
4100 }
4101 return success();
4102}
4103
4104/// Build an outlined init helper for a task_reduction declare_reduction op.
4105/// Signature: void(ptr %priv, ptr %orig). For non-byref reductions, the init
4106/// region's mold argument is mapped following the same rule as the regular
4107/// reduction path (`mapInitializationArgs`): a non-pointer mold loads the
4108/// value from %orig, while a pointer-typed mold receives %orig directly. The
4109/// yielded value is stored into %priv.
4110static llvm::Function *
4111emitTaskReductionInitFn(omp::DeclareReductionOp decl, StringRef baseName,
4112 LLVM::ModuleTranslation &moduleTranslation) {
4113 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4114 llvm::LLVMContext &ctx = llvmModule->getContext();
4115 llvm::Type *voidTy = llvm::Type::getVoidTy(ctx);
4116 llvm::Type *ptrTy = llvm::PointerType::getUnqual(ctx);
4117 llvm::FunctionType *fty =
4118 llvm::FunctionType::get(voidTy, {ptrTy, ptrTy}, false);
4119 llvm::Function *fn =
4120 llvm::Function::Create(fty, llvm::GlobalValue::InternalLinkage,
4121 baseName + ".red.init", llvmModule);
4122 fn->setDoesNotRecurse();
4123 fn->getArg(0)->setName("priv");
4124 fn->getArg(1)->setName("orig");
4125
4126 llvm::BasicBlock *entry = llvm::BasicBlock::Create(ctx, "entry", fn);
4127 llvm::IRBuilder<> b(entry);
4128
4129 // Map the initializer's mold argument the same way the regular reduction
4130 // path does in `mapInitializationArgs`: only load the original value when a
4131 // non-pointer mold is expected. For a pointer-typed mold the storage pointer
4132 // (%orig) is passed through directly, so a mold-yielding initializer lowers
4133 // to `store ptr %orig, ptr %priv` rather than emitting a spurious load.
4134 Value moldArg = decl.getInitializerMoldArg();
4135 llvm::Value *origVal = fn->getArg(1);
4136 if (!isa<LLVM::LLVMPointerType>(moldArg.getType()))
4137 origVal = b.CreateLoad(moduleTranslation.convertType(moldArg.getType()),
4138 fn->getArg(1), "omp.orig");
4139 moduleTranslation.mapValue(moldArg, origVal);
4141 if (failed(inlineConvertOmpRegions(decl.getInitializerRegion(),
4142 "omp.taskred.init", b, moduleTranslation,
4143 &phis))) {
4144 fn->eraseFromParent();
4145 return nullptr;
4146 }
4147 assert(phis.size() == 1 &&
4148 "expected one value yielded from reduction initializer");
4149 b.CreateStore(phis[0], fn->getArg(0));
4150 b.CreateRetVoid();
4151
4152 moduleTranslation.forgetMapping(decl.getInitializerRegion());
4153 return fn;
4154}
4155
4156/// Build an outlined combiner helper for a task_reduction declare_reduction op.
4157/// Signature: void(ptr %lhs, ptr %rhs). For non-byref reductions, the values
4158/// at *%lhs and *%rhs are loaded, fed into the combiner region, and the
4159/// yielded scalar is stored back into *%lhs.
4160static llvm::Function *
4161emitTaskReductionCombFn(omp::DeclareReductionOp decl, StringRef baseName,
4162 LLVM::ModuleTranslation &moduleTranslation) {
4163 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4164 llvm::LLVMContext &ctx = llvmModule->getContext();
4165 llvm::Type *voidTy = llvm::Type::getVoidTy(ctx);
4166 llvm::Type *ptrTy = llvm::PointerType::getUnqual(ctx);
4167 llvm::FunctionType *fty =
4168 llvm::FunctionType::get(voidTy, {ptrTy, ptrTy}, false);
4169 llvm::Function *fn =
4170 llvm::Function::Create(fty, llvm::GlobalValue::InternalLinkage,
4171 baseName + ".red.comb", llvmModule);
4172 fn->setDoesNotRecurse();
4173 fn->getArg(0)->setName("lhs");
4174 fn->getArg(1)->setName("rhs");
4175
4176 llvm::BasicBlock *entry = llvm::BasicBlock::Create(ctx, "entry", fn);
4177 llvm::IRBuilder<> b(entry);
4178
4179 llvm::Type *elemTy = moduleTranslation.convertType(decl.getType());
4180 Block &combBlock = decl.getReductionRegion().front();
4181 assert(combBlock.getNumArguments() == 2 &&
4182 "expected two arguments in declare_reduction combiner");
4183 llvm::Value *lhsVal = b.CreateLoad(elemTy, fn->getArg(0), "omp.lhs");
4184 llvm::Value *rhsVal = b.CreateLoad(elemTy, fn->getArg(1), "omp.rhs");
4185 moduleTranslation.mapValue(combBlock.getArgument(0), lhsVal);
4186 moduleTranslation.mapValue(combBlock.getArgument(1), rhsVal);
4187
4189 if (failed(inlineConvertOmpRegions(decl.getReductionRegion(),
4190 "omp.taskred.comb", b, moduleTranslation,
4191 &phis))) {
4192 fn->eraseFromParent();
4193 return nullptr;
4194 }
4195 assert(phis.size() == 1 &&
4196 "expected one value yielded from reduction combiner");
4197 b.CreateStore(phis[0], fn->getArg(0));
4198 b.CreateRetVoid();
4199
4200 moduleTranslation.forgetMapping(decl.getReductionRegion());
4201 return fn;
4202}
4203
4204/// Emit the per-taskgroup task_reduction descriptor array and the
4205/// `__kmpc_taskred_init` runtime call. \p origPtrs holds the LLVM values for
4206/// the original (shared) variables, one per declaration in \p redDecls.
4207/// `builder` must be set to the point at which the descriptor stores and the
4208/// init call should be emitted; the descriptor array itself is allocated at
4209/// \p allocaIP. \p helperNamePrefix is used to disambiguate the generated
4210/// init/combiner helper symbol names between taskgroup and taskloop callers.
4211///
4212/// When \p isModifier is false, emits `__kmpc_taskred_init` and returns the
4213/// `ptr` value it produces (the taskgroup reduction handle). When \p isModifier
4214/// is true, emits `__kmpc_taskred_modifier_init` instead to open a
4215/// task-reduction scope for a parallel or worksharing construct, passing
4216/// \p isWorksharing as the runtime `is_ws` argument. Returns null on failure.
4217///
4218/// Only the non-byref form is handled here. Byref reductions have already
4219/// been rejected by `checkImplementationStatus`.
4220static llvm::Value *emitTaskReductionInitCall(
4222 ArrayRef<llvm::Value *> origPtrs, StringRef helperNamePrefix,
4223 llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder::InsertPointTy allocaIP,
4224 LLVM::ModuleTranslation &moduleTranslation, bool isModifier,
4225 bool isWorksharing) {
4226 assert(redDecls.size() == origPtrs.size() &&
4227 "expected one orig pointer per reduction decl");
4228 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4229 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4230 llvm::LLVMContext &ctx = llvmModule->getContext();
4231 const llvm::DataLayout &dl = llvmModule->getDataLayout();
4232
4233 llvm::Type *ptrTy = llvm::PointerType::getUnqual(ctx);
4234 llvm::Type *i32Ty = llvm::Type::getInt32Ty(ctx);
4235 llvm::Type *sizeTy =
4236 llvm::Type::getIntNTy(ctx, dl.getPointerSizeInBits(/*AddrSpace=*/0));
4237
4238 // Identified `kmp_taskred_input_t` struct, matching the layout used by
4239 // Clang's CGOpenMPRuntime::emitTaskReductionInit.
4240 llvm::StructType *redInputTy =
4241 llvm::StructType::getTypeByName(ctx, "kmp_taskred_input_t");
4242 if (!redInputTy)
4243 redInputTy = llvm::StructType::create(
4244 ctx, {ptrTy, ptrTy, sizeTy, ptrTy, ptrTy, ptrTy, i32Ty},
4245 "kmp_taskred_input_t");
4246
4247 unsigned n = redDecls.size();
4248 llvm::ArrayType *arrTy = llvm::ArrayType::get(redInputTy, n);
4249
4250 // Allocate the descriptor array in the enclosing function's alloca block.
4251 llvm::AllocaInst *arrAlloca;
4252 {
4253 llvm::IRBuilderBase::InsertPointGuard guard(builder);
4254 builder.restoreIP(allocaIP);
4255 arrAlloca =
4256 builder.CreateAlloca(arrTy, /*ArraySize=*/nullptr, ".taskred.input");
4257 }
4258
4259 // Fill each descriptor entry at the current builder insertion point.
4260 llvm::Value *zero = builder.getInt32(0);
4261 for (unsigned i = 0; i < n; ++i) {
4262 omp::DeclareReductionOp decl = redDecls[i];
4263 llvm::Value *orig = origPtrs[i];
4264 if (auto *origPtrTy = llvm::dyn_cast<llvm::PointerType>(orig->getType());
4265 origPtrTy && origPtrTy->getAddressSpace() != 0)
4266 orig = builder.CreateAddrSpaceCast(orig, ptrTy);
4267 llvm::Type *elemTy = moduleTranslation.convertType(decl.getType());
4268 uint64_t size = dl.getTypeAllocSize(elemTy).getFixedValue();
4269
4270 std::string baseName =
4271 (llvm::Twine(helperNamePrefix) + decl.getSymName()).str();
4272 llvm::Function *initFn =
4273 emitTaskReductionInitFn(decl, baseName, moduleTranslation);
4274 llvm::Function *combFn =
4275 emitTaskReductionCombFn(decl, baseName, moduleTranslation);
4276 if (!initFn || !combFn)
4277 return nullptr;
4278 llvm::Value *elemPtr = builder.CreateInBoundsGEP(
4279 arrTy, arrAlloca, {zero, builder.getInt32(i)}, ".taskred.elem");
4280 auto storeField = [&](unsigned fieldIdx, llvm::Value *val) {
4281 llvm::Value *fieldPtr =
4282 builder.CreateStructGEP(redInputTy, elemPtr, fieldIdx);
4283 builder.CreateStore(val, fieldPtr);
4284 };
4285 storeField(0, orig); // reduce_shar
4286 storeField(1, orig); // reduce_orig
4287 storeField(2, llvm::ConstantInt::get(sizeTy, size)); // reduce_size
4288 storeField(3, initFn); // reduce_init
4289 storeField(4, llvm::ConstantPointerNull::get(ptrTy)); // reduce_fini
4290 storeField(5, combFn); // reduce_comb
4291 storeField(6, llvm::ConstantInt::get(i32Ty, 0)); // flags
4292 }
4293
4294 // Emit the runtime call that registers the task reduction data.
4295 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4296 uint32_t srcLocSize;
4297 llvm::Constant *srcLocStr =
4298 ompBuilder->getOrCreateSrcLocStr(ompLoc, srcLocSize);
4299 llvm::Value *ident = ompBuilder->getOrCreateIdent(srcLocStr, srcLocSize);
4300 ompBuilder->updateToLocation(ompLoc);
4301 llvm::Value *gtid = ompBuilder->getOrCreateThreadID(ident);
4302 if (isModifier) {
4303 // __kmpc_taskred_modifier_init(loc, gtid, is_ws, num, &arr) opens a
4304 // task-reduction scope for the enclosing parallel/worksharing region.
4305 llvm::FunctionCallee modInit = ompBuilder->getOrCreateRuntimeFunction(
4306 *llvmModule, llvm::omp::OMPRTL___kmpc_taskred_modifier_init);
4307 return builder.CreateCall(modInit,
4308 {ident, gtid,
4309 builder.getInt32(isWorksharing ? 1 : 0),
4310 builder.getInt32(n), arrAlloca},
4311 ".taskred.desc");
4312 }
4313 // __kmpc_taskred_init(gtid, num, &arr).
4314 llvm::FunctionCallee taskredInit = ompBuilder->getOrCreateRuntimeFunction(
4315 *llvmModule, llvm::omp::OMPRTL___kmpc_taskred_init);
4316 return builder.CreateCall(taskredInit, {gtid, builder.getInt32(n), arrAlloca},
4317 ".taskred.desc");
4318}
4319
4320/// Emits `__kmpc_task_reduction_modifier_fini(loc, gtid, is_ws)` at the current
4321/// builder insertion point, closing the task-reduction scope opened by the
4322/// `task` reduction modifier on a parallel or worksharing construct.
4323static void
4324emitTaskReductionModifierFini(bool isWorksharing, llvm::IRBuilderBase &builder,
4325 LLVM::ModuleTranslation &moduleTranslation) {
4326 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4327 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4328 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4329 uint32_t srcLocSize;
4330 llvm::Constant *srcLocStr =
4331 ompBuilder->getOrCreateSrcLocStr(ompLoc, srcLocSize);
4332 llvm::Value *ident = ompBuilder->getOrCreateIdent(srcLocStr, srcLocSize);
4333 ompBuilder->updateToLocation(ompLoc);
4334 llvm::Value *gtid = ompBuilder->getOrCreateThreadID(ident);
4335 llvm::FunctionCallee fini = ompBuilder->getOrCreateRuntimeFunction(
4336 *llvmModule, llvm::omp::OMPRTL___kmpc_task_reduction_modifier_fini);
4337 builder.CreateCall(fini,
4338 {ident, gtid, builder.getInt32(isWorksharing ? 1 : 0)});
4339}
4340
4341/// Converts an OpenMP taskgroup construct into LLVM IR using OpenMPIRBuilder.
4342static LogicalResult
4343convertOmpTaskgroupOp(omp::TaskgroupOp tgOp, llvm::IRBuilderBase &builder,
4344 LLVM::ModuleTranslation &moduleTranslation) {
4345 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
4346 if (failed(checkImplementationStatus(*tgOp)))
4347 return failure();
4348
4349 // Resolve and validate task_reduction declarations up front. We only handle
4350 // declare_reduction ops shaped like a non-byref scalar reduction in this
4351 // first cut; richer shapes (two-argument initializer, cleanup region,
4352 // missing combiner) require additional infrastructure.
4354 if (auto syms = tgOp.getTaskReductionSyms()) {
4355 redDecls.reserve(syms->size());
4356 for (auto sym : syms->getAsRange<SymbolRefAttr>()) {
4358 tgOp, sym);
4359 if (!decl)
4360 return tgOp.emitError()
4361 << "failed to resolve task_reduction declare_reduction symbol "
4362 << sym.getRootReference() << " in omp.taskgroup";
4363 if (decl.getInitializerRegion().front().getNumArguments() != 1)
4364 return tgOp.emitError("not yet implemented: task_reduction with "
4365 "two-argument initializer in omp.taskgroup");
4366 if (!decl.getCleanupRegion().empty())
4367 return tgOp.emitError("not yet implemented: task_reduction with "
4368 "cleanup region in omp.taskgroup");
4369 if (decl.getReductionRegion().empty())
4370 return tgOp.emitError("task_reduction declare_reduction is missing a "
4371 "combiner region");
4372 redDecls.push_back(decl);
4373 }
4374 }
4375
4376 auto bodyCB =
4377 [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
4378 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
4379 builder.restoreIP(codegenIP);
4380
4381 if (!redDecls.empty()) {
4383 origPtrs.reserve(redDecls.size());
4384 for (Value v : tgOp.getTaskReductionVars())
4385 origPtrs.push_back(moduleTranslation.lookupValue(v));
4386 if (!emitTaskReductionInitCall(redDecls, origPtrs, "__omp_taskred_",
4387 builder, allocaIP, moduleTranslation))
4388 return llvm::createStringError(
4389 llvm::inconvertibleErrorCode(),
4390 "failed to emit task_reduction initialization for omp.taskgroup");
4391 }
4392
4393 // Inside the taskgroup body, each task_reduction block argument refers to
4394 // the same shared/original storage that the runtime now knows about via
4395 // the descriptor array. Inner tasks that declare in_reduction look up
4396 // per-task private copies through the runtime; the taskgroup body itself
4397 // uses the original variable.
4398 for (auto [i, blockArg] :
4399 llvm::enumerate(tgOp.getRegion().getArguments())) {
4400 llvm::Value *orig =
4401 moduleTranslation.lookupValue(tgOp.getTaskReductionVars()[i]);
4402 moduleTranslation.mapValue(blockArg, orig);
4403 }
4404
4405 return convertOmpOpRegions(tgOp.getRegion(), "omp.taskgroup.region",
4406 builder, moduleTranslation)
4407 .takeError();
4408 };
4409
4411 InsertPointTy allocaIP =
4412 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
4413 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4414 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
4415 moduleTranslation.getOpenMPBuilder()->createTaskgroup(
4416 ompLoc, allocaIP, deallocBlocks, bodyCB);
4417
4418 if (failed(handleError(afterIP, *tgOp)))
4419 return failure();
4420
4421 builder.restoreIP(*afterIP);
4422 return success();
4423}
4424
4425static LogicalResult
4426convertOmpInteropInitOp(omp::InteropInitOp initOp, llvm::IRBuilderBase &builder,
4427 LLVM::ModuleTranslation &moduleTranslation) {
4428 if (!initOp.getDependVars().empty() || initOp.getDependKinds() ||
4429 !initOp.getDependIterated().empty() || initOp.getDependIteratedKinds())
4430 return initOp.emitError()
4431 << "not yet implemented: Unhandled clause depend in "
4432 << omp::InteropInitOp::getOperationName() << " operation";
4433
4434 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4435 llvm::Value *interopVar =
4436 moduleTranslation.lookupValue(initOp.getInteropVar());
4437 llvm::Value *device = initOp.getDevice()
4438 ? moduleTranslation.lookupValue(initOp.getDevice())
4439 : nullptr;
4440
4441 // TODO: Handle depend clauses when supported.
4442 llvm::Value *numDeps = llvm::ConstantInt::get(builder.getInt32Ty(), 0);
4443 llvm::Value *depArray = llvm::ConstantPointerNull::get(builder.getPtrTy());
4444 bool hasNowait = initOp.getNowait();
4445
4446 // A single `init` clause may list both `target` and `targetsync`, but the
4447 // runtime init call takes a single interop-type. Collapse the set to one
4448 // value, matching Clang: if `target` is present use Target, otherwise
4449 // TargetSync. The offload runtime object model supports only one type per
4450 // object; representing both would require a runtime change.
4451 bool hasTarget = false, hasTargetSync = false;
4452 for (mlir::Attribute typeAttr : initOp.getInteropTypes()) {
4453 switch (cast<omp::InteropTypeAttr>(typeAttr).getValue()) {
4454 case omp::InteropType::target:
4455 hasTarget = true;
4456 break;
4457 case omp::InteropType::targetsync:
4458 hasTargetSync = true;
4459 break;
4460 }
4461 }
4462 llvm::omp::OMPInteropType interopType =
4463 (!hasTarget && hasTargetSync) ? llvm::omp::OMPInteropType::TargetSync
4464 : llvm::omp::OMPInteropType::Target;
4465 ompBuilder->createOMPInteropInit(builder, interopVar, interopType, device,
4466 numDeps, depArray, hasNowait);
4467 return success();
4468}
4469
4470static LogicalResult
4471convertOmpInteropDestroyOp(omp::InteropDestroyOp destroyOp,
4472 llvm::IRBuilderBase &builder,
4473 LLVM::ModuleTranslation &moduleTranslation) {
4474 if (!destroyOp.getDependVars().empty() || destroyOp.getDependKinds() ||
4475 !destroyOp.getDependIterated().empty() ||
4476 destroyOp.getDependIteratedKinds())
4477 return destroyOp.emitError()
4478 << "not yet implemented: Unhandled clause depend in "
4479 << omp::InteropDestroyOp::getOperationName() << " operation";
4480
4481 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4482 llvm::Value *interopVar =
4483 moduleTranslation.lookupValue(destroyOp.getInteropVar());
4484 llvm::Value *device =
4485 destroyOp.getDevice()
4486 ? moduleTranslation.lookupValue(destroyOp.getDevice())
4487 : nullptr;
4488
4489 llvm::Value *numDeps = llvm::ConstantInt::get(builder.getInt32Ty(), 0);
4490 llvm::Value *depArray = llvm::ConstantPointerNull::get(builder.getPtrTy());
4491 bool hasNowait = destroyOp.getNowait();
4492
4493 ompBuilder->createOMPInteropDestroy(builder, interopVar, device, numDeps,
4494 depArray, hasNowait);
4495 return success();
4496}
4497
4498static LogicalResult
4499convertOmpInteropUseOp(omp::InteropUseOp useOp, llvm::IRBuilderBase &builder,
4500 LLVM::ModuleTranslation &moduleTranslation) {
4501 if (!useOp.getDependVars().empty() || useOp.getDependKinds() ||
4502 !useOp.getDependIterated().empty() || useOp.getDependIteratedKinds())
4503 return useOp.emitError()
4504 << "not yet implemented: Unhandled clause depend in "
4505 << omp::InteropUseOp::getOperationName() << " operation";
4506
4507 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4508 llvm::Value *interopVar =
4509 moduleTranslation.lookupValue(useOp.getInteropVar());
4510 llvm::Value *device = useOp.getDevice()
4511 ? moduleTranslation.lookupValue(useOp.getDevice())
4512 : nullptr;
4513
4514 llvm::Value *numDeps = llvm::ConstantInt::get(builder.getInt32Ty(), 0);
4515 llvm::Value *depArray = llvm::ConstantPointerNull::get(builder.getPtrTy());
4516 bool hasNowait = useOp.getNowait();
4517
4518 ompBuilder->createOMPInteropUse(builder, interopVar, device, numDeps,
4519 depArray, hasNowait);
4520 return success();
4521}
4522
4523static LogicalResult
4524convertOmpTaskwaitOp(omp::TaskwaitOp twOp, llvm::IRBuilderBase &builder,
4525 LLVM::ModuleTranslation &moduleTranslation) {
4526 if (failed(checkImplementationStatus(*twOp)))
4527 return failure();
4528
4529 llvm::OpenMPIRBuilder::DependenciesInfo dds;
4530 if (failed(buildDependData(
4531 twOp.getDependVars(), twOp.getDependKinds(), twOp.getDependIterated(),
4532 twOp.getDependIteratedKinds(), builder, moduleTranslation, dds))) {
4533 return failure();
4534 }
4535
4536 moduleTranslation.getOpenMPBuilder()->createTaskwait(builder, dds);
4537 if (dds.DepArray) {
4538 builder.CreateFree(dds.DepArray);
4539 }
4540
4541 return success();
4542}
4543
4544/// Converts an OpenMP workshare loop into LLVM IR using OpenMPIRBuilder.
4545static LogicalResult
4546convertOmpWsloop(Operation &opInst, llvm::IRBuilderBase &builder,
4547 LLVM::ModuleTranslation &moduleTranslation) {
4548 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4549 auto wsloopOp = cast<omp::WsloopOp>(opInst);
4550 if (failed(checkImplementationStatus(opInst)))
4551 return failure();
4552
4553 auto loopOp = cast<omp::LoopNestOp>(wsloopOp.getWrappedLoop());
4554 llvm::ArrayRef<bool> isByRef = getIsByRef(wsloopOp.getReductionByref());
4555 assert(isByRef.size() == wsloopOp.getNumReductionVars());
4556
4557 // Static is the default.
4558 auto schedule =
4559 wsloopOp.getScheduleKind().value_or(omp::ClauseScheduleKind::Static);
4560
4561 // Find the loop configuration.
4562 llvm::Value *step = moduleTranslation.lookupValue(loopOp.getLoopSteps()[0]);
4563 llvm::Type *ivType = step->getType();
4564 llvm::Value *chunk = nullptr;
4565 if (wsloopOp.getScheduleChunk()) {
4566 llvm::Value *chunkVar =
4567 moduleTranslation.lookupValue(wsloopOp.getScheduleChunk());
4568 chunk = builder.CreateSExtOrTrunc(chunkVar, ivType);
4569 }
4570
4571 omp::DistributeOp distributeOp = nullptr;
4572 llvm::Value *distScheduleChunk = nullptr;
4573 bool hasDistSchedule = false;
4574 if (llvm::isa_and_present<omp::DistributeOp>(opInst.getParentOp())) {
4575 distributeOp = cast<omp::DistributeOp>(opInst.getParentOp());
4576 hasDistSchedule = distributeOp.getDistScheduleStatic();
4577 if (distributeOp.getDistScheduleChunkSize()) {
4578 llvm::Value *chunkVar = moduleTranslation.lookupValue(
4579 distributeOp.getDistScheduleChunkSize());
4580 distScheduleChunk = builder.CreateSExtOrTrunc(chunkVar, ivType);
4581 }
4582 }
4583
4584 PrivateVarsInfo privateVarsInfo(wsloopOp);
4585
4587 collectReductionDecls(wsloopOp, reductionDecls);
4588
4589 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
4590 findAllocInsertPoints(builder, moduleTranslation);
4591
4592 SmallVector<llvm::Value *> privateReductionVariables(
4593 wsloopOp.getNumReductionVars());
4594
4596 wsloopOp, builder, moduleTranslation, privateVarsInfo, allocaIP);
4597 if (handleError(afterAllocas, opInst).failed())
4598 return failure();
4599
4600 DenseMap<Value, llvm::Value *> reductionVariableMap;
4601
4602 MutableArrayRef<BlockArgument> reductionArgs =
4603 cast<omp::BlockArgOpenMPOpInterface>(opInst).getReductionBlockArgs();
4604
4605 SmallVector<DeferredStore> deferredStores;
4606
4607 if (failed(allocReductionVars(wsloopOp, reductionArgs, builder,
4608 moduleTranslation, allocaIP, reductionDecls,
4609 privateReductionVariables, reductionVariableMap,
4610 deferredStores, isByRef)))
4611 return failure();
4612
4613 if (handleError(initPrivateVars(builder, moduleTranslation, privateVarsInfo),
4614 opInst)
4615 .failed())
4616 return failure();
4617
4618 if (failed(copyFirstPrivateVars(
4619 wsloopOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
4620 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
4621 wsloopOp.getPrivateNeedsBarrier())))
4622 return failure();
4623
4624 assert(afterAllocas.get()->getSinglePredecessor());
4625 if (failed(initReductionVars(wsloopOp, reductionArgs, builder,
4626 moduleTranslation,
4627 afterAllocas.get()->getSinglePredecessor(),
4628 reductionDecls, privateReductionVariables,
4629 reductionVariableMap, isByRef, deferredStores)))
4630 return failure();
4631
4632 // For `reduction(task, ...)` open a task-reduction scope for the worksharing
4633 // loop. Participating explicit tasks accumulate into the per-thread private
4634 // copies, which the worksharing reduction then combines across threads.
4635 bool isTaskReductionMod =
4636 wsloopOp.getReductionMod() == omp::ReductionModifier::task &&
4637 wsloopOp.getNumReductionVars() > 0;
4638 if (isTaskReductionMod &&
4639 !emitTaskReductionInitCall(reductionDecls, privateReductionVariables,
4640 "__omp_taskred_mod_", builder, allocaIP,
4641 moduleTranslation, /*isModifier=*/true,
4642 /*isWorksharing=*/true))
4643 return wsloopOp.emitError(
4644 "failed to emit task reduction modifier initialization");
4645
4646 // TODO: Handle doacross loops when the ordered clause has a parameter.
4647 bool isOrdered = wsloopOp.getOrdered().has_value();
4648 std::optional<omp::ScheduleModifier> scheduleMod = wsloopOp.getScheduleMod();
4649 bool isSimd = wsloopOp.getScheduleSimd();
4650 bool loopNeedsBarrier = !wsloopOp.getNowait();
4651
4652 // The only legal way for the direct parent to be omp.distribute is that this
4653 // represents 'distribute parallel do'. Otherwise, this is a regular
4654 // worksharing loop.
4655 llvm::omp::WorksharingLoopType workshareLoopType =
4656 llvm::isa_and_present<omp::DistributeOp>(opInst.getParentOp())
4657 ? llvm::omp::WorksharingLoopType::DistributeForStaticLoop
4658 : llvm::omp::WorksharingLoopType::ForStaticLoop;
4659
4660 SmallVector<llvm::UncondBrInst *> cancelTerminators;
4661 pushCancelFinalizationCB(cancelTerminators, builder, *ompBuilder, wsloopOp,
4662 llvm::omp::Directive::OMPD_for);
4663
4664 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4665
4666 // Initialize linear variables and linear step
4667 LinearClauseProcessor linearClauseProcessor;
4668
4669 if (!wsloopOp.getLinearVars().empty()) {
4670 auto linearVarTypes = wsloopOp.getLinearVarTypes().value();
4671 for (mlir::Attribute linearVarType : linearVarTypes)
4672 linearClauseProcessor.registerType(moduleTranslation, linearVarType);
4673
4674 for (auto [idx, linearVar] : llvm::enumerate(wsloopOp.getLinearVars()))
4675 linearClauseProcessor.createLinearVar(
4676 builder, moduleTranslation, moduleTranslation.lookupValue(linearVar),
4677 idx);
4678 for (mlir::Value linearStep : wsloopOp.getLinearStepVars())
4679 linearClauseProcessor.initLinearStep(moduleTranslation, linearStep);
4680 }
4681
4683 wsloopOp.getRegion(), "omp.wsloop.region", builder, moduleTranslation);
4684
4685 if (failed(handleError(regionBlock, opInst)))
4686 return failure();
4687
4688 llvm::CanonicalLoopInfo *loopInfo = findCurrentLoopInfo(moduleTranslation);
4689
4690 // Emit Initialization and Update IR for linear variables
4691 if (!wsloopOp.getLinearVars().empty()) {
4692 linearClauseProcessor.initLinearVar(builder, moduleTranslation,
4693 loopInfo->getPreheader());
4694 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterBarrierIP =
4695 moduleTranslation.getOpenMPBuilder()->createBarrier(
4696 builder, llvm::omp::OMPD_barrier);
4697 if (failed(handleError(afterBarrierIP, *loopOp)))
4698 return failure();
4699 builder.restoreIP(*afterBarrierIP);
4700 linearClauseProcessor.updateLinearVar(builder, loopInfo->getBody(),
4701 loopInfo->getIndVar());
4702 linearClauseProcessor.splitLinearFiniBB(builder, loopInfo->getExit());
4703 }
4704
4705 builder.SetInsertPoint(*regionBlock, (*regionBlock)->begin());
4706
4707 // Check if we can generate no-loop kernel
4708 bool noLoopMode = false;
4709 omp::TargetOp targetOp = wsloopOp->getParentOfType<mlir::omp::TargetOp>();
4710 if (targetOp &&
4711 targetOp.getKernelType() == omp::TargetExecMode::spmd_no_loop) {
4712 Operation *targetCapturedOp =
4713 cast<omp::ComposableOpInterface>(*targetOp).findCapturedOp();
4714 // We need this check because, without it, noLoopMode would be set to true
4715 // for every omp.wsloop nested inside a no-loop SPMD target region, even if
4716 // that loop is not the top-level SPMD one.
4717 if (loopOp == targetCapturedOp)
4718 noLoopMode = true;
4719 }
4720
4721 for (size_t index = 0; index < wsloopOp.getLinearVars().size(); index++)
4722 linearClauseProcessor.rewriteInPlace(builder, loopInfo->getBody(),
4723 loopInfo->getLatch(), index);
4724
4725 llvm::OpenMPIRBuilder::InsertPointOrErrorTy wsloopIP =
4726 ompBuilder->applyWorkshareLoop(
4727 ompLoc.DL, loopInfo, allocaIP, loopNeedsBarrier,
4728 convertToScheduleKind(schedule), chunk, isSimd,
4729 scheduleMod == omp::ScheduleModifier::monotonic,
4730 scheduleMod == omp::ScheduleModifier::nonmonotonic, isOrdered,
4731 workshareLoopType, noLoopMode, hasDistSchedule, distScheduleChunk);
4732
4733 if (failed(handleError(wsloopIP, opInst)))
4734 return failure();
4735
4736 // Emit finalization and in-place rewrites for linear vars.
4737 if (!wsloopOp.getLinearVars().empty()) {
4738 llvm::OpenMPIRBuilder::InsertPointTy oldIP = builder.saveIP();
4739 assert(loopInfo->getLastIter() &&
4740 "`lastiter` in CanonicalLoopInfo is nullptr");
4741 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterBarrierIP =
4742 linearClauseProcessor.finalizeLinearVar(builder, moduleTranslation,
4743 loopInfo->getLastIter());
4744 if (failed(handleError(afterBarrierIP, *loopOp)))
4745 return failure();
4746
4747 builder.restoreIP(oldIP);
4748 }
4749
4750 // Set the correct branch target for task cancellation
4751 popCancelFinalizationCB(cancelTerminators, *ompBuilder, wsloopIP.get());
4752
4753 // Close the task-reduction scope before the worksharing reduction combine.
4754 if (isTaskReductionMod)
4755 emitTaskReductionModifierFini(/*isWorksharing=*/true, builder,
4756 moduleTranslation);
4757
4758 // Process the reductions if required.
4759 if (failed(createReductionsAndCleanup(
4760 wsloopOp, builder, moduleTranslation, allocaIP, reductionDecls,
4761 privateReductionVariables, isByRef, wsloopOp.getNowait(),
4762 /*isTeamsReduction=*/false)))
4763 return failure();
4764
4765 return cleanupPrivateVars(wsloopOp, builder, moduleTranslation,
4766 wsloopOp.getLoc(), privateVarsInfo);
4767}
4768
4769/// Converts the OpenMP parallel operation to LLVM IR.
4770static LogicalResult
4771convertOmpParallel(omp::ParallelOp opInst, llvm::IRBuilderBase &builder,
4772 LLVM::ModuleTranslation &moduleTranslation) {
4773 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
4774 ArrayRef<bool> isByRef = getIsByRef(opInst.getReductionByref());
4775 assert(isByRef.size() == opInst.getNumReductionVars());
4776 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4777 bool isCancellable = constructIsCancellable(opInst);
4778
4779 if (failed(checkImplementationStatus(*opInst)))
4780 return failure();
4781
4782 PrivateVarsInfo privateVarsInfo(opInst);
4783
4784 // Collect reduction declarations
4786 collectReductionDecls(opInst, reductionDecls);
4787 SmallVector<llvm::Value *> privateReductionVariables(
4788 opInst.getNumReductionVars());
4789 SmallVector<DeferredStore> deferredStores;
4790 // Only open a task-reduction scope when the `task` modifier is present and
4791 // there are reduction variables to combine; otherwise the matching fini in
4792 // the reduction-combine path (guarded by getNumReductionVars() > 0) would be
4793 // skipped, leaving the modifier init unbalanced.
4794 bool isTaskReductionMod =
4795 opInst.getReductionMod() == omp::ReductionModifier::task &&
4796 opInst.getNumReductionVars() > 0;
4797
4798 auto bodyGenCB =
4799 [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
4800 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
4802 opInst, builder, moduleTranslation, privateVarsInfo, allocaIP);
4803 if (handleError(afterAllocas, *opInst).failed())
4804 return llvm::make_error<PreviouslyReportedError>();
4805
4806 // Allocate reduction vars
4807 DenseMap<Value, llvm::Value *> reductionVariableMap;
4808
4809 MutableArrayRef<BlockArgument> reductionArgs =
4810 cast<omp::BlockArgOpenMPOpInterface>(*opInst).getReductionBlockArgs();
4811
4812 allocaIP =
4813 InsertPointTy(allocaIP.getBlock(),
4814 allocaIP.getBlock()->getTerminator()->getIterator());
4815
4816 if (failed(allocReductionVars(
4817 opInst, reductionArgs, builder, moduleTranslation, allocaIP,
4818 reductionDecls, privateReductionVariables, reductionVariableMap,
4819 deferredStores, isByRef)))
4820 return llvm::make_error<PreviouslyReportedError>();
4821
4822 assert(afterAllocas.get()->getSinglePredecessor());
4823 builder.restoreIP(codeGenIP);
4824
4825 if (handleError(
4826 initPrivateVars(builder, moduleTranslation, privateVarsInfo),
4827 *opInst)
4828 .failed())
4829 return llvm::make_error<PreviouslyReportedError>();
4830
4831 if (failed(copyFirstPrivateVars(
4832 opInst, builder, moduleTranslation, privateVarsInfo.mlirVars,
4833 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
4834 opInst.getPrivateNeedsBarrier())))
4835 return llvm::make_error<PreviouslyReportedError>();
4836
4837 if (failed(
4838 initReductionVars(opInst, reductionArgs, builder, moduleTranslation,
4839 afterAllocas.get()->getSinglePredecessor(),
4840 reductionDecls, privateReductionVariables,
4841 reductionVariableMap, isByRef, deferredStores)))
4842 return llvm::make_error<PreviouslyReportedError>();
4843
4844 // For `reduction(task, ...)` open a task-reduction scope so participating
4845 // explicit tasks accumulate into the per-thread private copies; the
4846 // parallel reduction then combines those copies across the team.
4847 if (isTaskReductionMod &&
4848 !emitTaskReductionInitCall(reductionDecls, privateReductionVariables,
4849 "__omp_taskred_mod_", builder, allocaIP,
4850 moduleTranslation, /*isModifier=*/true,
4851 /*isWorksharing=*/false))
4852 return llvm::createStringError(
4853 "failed to emit task reduction modifier initialization");
4854
4855 // Save the alloca insertion point on ModuleTranslation stack for use in
4856 // nested regions.
4858 moduleTranslation, allocaIP, deallocBlocks);
4859
4860 // ParallelOp has only one region associated with it.
4862 opInst.getRegion(), "omp.par.region", builder, moduleTranslation);
4863 if (!regionBlock)
4864 return regionBlock.takeError();
4865
4866 // Process the reductions if required.
4867 if (opInst.getNumReductionVars() > 0) {
4868 // Collect reduction info
4870 SmallVector<OwningAtomicReductionGen> owningAtomicReductionGens;
4872 owningReductionGenRefDataPtrGens;
4874 collectReductionInfo(opInst, builder, moduleTranslation, reductionDecls,
4875 owningReductionGens, owningAtomicReductionGens,
4876 owningReductionGenRefDataPtrGens,
4877 privateReductionVariables, reductionInfos, isByRef);
4878
4879 // Move to region cont block
4880 builder.SetInsertPoint((*regionBlock)->getTerminator());
4881
4882 // Close the task-reduction scope before the per-thread reduction
4883 // contributions are combined across the team.
4884 if (isTaskReductionMod)
4885 emitTaskReductionModifierFini(/*isWorksharing=*/false, builder,
4886 moduleTranslation);
4887
4888 // Generate reductions from info
4889 llvm::UnreachableInst *tempTerminator = builder.CreateUnreachable();
4890 builder.SetInsertPoint(tempTerminator);
4891
4892 llvm::OpenMPIRBuilder::InsertPointOrErrorTy contInsertPoint =
4893 ompBuilder->createReductions(builder, allocaIP, reductionInfos,
4894 isByRef,
4895 /*IsNoWait=*/false,
4896 /*IsTeamsReduction=*/false);
4897 if (!contInsertPoint)
4898 return contInsertPoint.takeError();
4899
4900 if (!contInsertPoint->getBlock())
4901 return llvm::make_error<PreviouslyReportedError>();
4902
4903 tempTerminator->eraseFromParent();
4904 builder.restoreIP(*contInsertPoint);
4905 }
4906
4907 return llvm::Error::success();
4908 };
4909
4910 auto privCB = [](InsertPointTy allocaIP, InsertPointTy codeGenIP,
4911 llvm::Value &, llvm::Value &val, llvm::Value *&replVal) {
4912 // tell OpenMPIRBuilder not to do anything. We handled Privatisation in
4913 // bodyGenCB.
4914 replVal = &val;
4915 return codeGenIP;
4916 };
4917
4918 // TODO: Perform finalization actions for variables. This has to be
4919 // called for variables which have destructors/finalizers.
4920 auto finiCB = [&](InsertPointTy codeGenIP) -> llvm::Error {
4921 InsertPointTy oldIP = builder.saveIP();
4922 builder.restoreIP(codeGenIP);
4923
4924 // if the reduction has a cleanup region, inline it here to finalize the
4925 // reduction variables
4926 SmallVector<Region *> reductionCleanupRegions;
4927 llvm::transform(reductionDecls, std::back_inserter(reductionCleanupRegions),
4928 [](omp::DeclareReductionOp reductionDecl) {
4929 return &reductionDecl.getCleanupRegion();
4930 });
4931 if (failed(inlineOmpRegionCleanup(
4932 reductionCleanupRegions, privateReductionVariables,
4933 moduleTranslation, builder, "omp.reduction.cleanup")))
4934 return llvm::createStringError(
4935 "failed to inline `cleanup` region of `omp.declare_reduction`");
4936
4937 if (failed(cleanupPrivateVars(opInst, builder, moduleTranslation,
4938 opInst.getLoc(), privateVarsInfo)))
4939 return llvm::make_error<PreviouslyReportedError>();
4940
4941 // If we could be performing cancellation, add the cancellation barrier on
4942 // the way out of the outlined region.
4943 if (isCancellable) {
4944 auto IPOrErr = ompBuilder->createBarrier(
4945 llvm::OpenMPIRBuilder::LocationDescription(builder),
4946 llvm::omp::Directive::OMPD_unknown,
4947 /* ForceSimpleCall */ false,
4948 /* CheckCancelFlag */ false);
4949 if (!IPOrErr)
4950 return IPOrErr.takeError();
4951 }
4952
4953 builder.restoreIP(oldIP);
4954 return llvm::Error::success();
4955 };
4956
4957 llvm::Value *ifCond = nullptr;
4958 if (auto ifVar = opInst.getIfExpr())
4959 ifCond = moduleTranslation.lookupValue(ifVar);
4960 llvm::Value *numThreads = nullptr;
4961 if (!opInst.getNumThreadsVars().empty())
4962 numThreads = moduleTranslation.lookupValue(opInst.getNumThreads(0));
4963 auto pbKind = llvm::omp::OMP_PROC_BIND_default;
4964 if (auto bind = opInst.getProcBindKind())
4965 pbKind = getProcBindKind(*bind);
4966
4968 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
4969 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
4970 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4971
4972 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
4973 ompBuilder->createParallel(ompLoc, allocaIP, deallocBlocks, bodyGenCB,
4974 privCB, finiCB, ifCond, numThreads, pbKind,
4975 isCancellable);
4976
4977 if (failed(handleError(afterIP, *opInst)))
4978 return failure();
4979
4980 builder.restoreIP(*afterIP);
4981 return success();
4982}
4983
4984/// Convert Order attribute to llvm::omp::OrderKind.
4985static llvm::omp::OrderKind
4986convertOrderKind(std::optional<omp::ClauseOrderKind> o) {
4987 if (!o)
4988 return llvm::omp::OrderKind::OMP_ORDER_unknown;
4989 switch (*o) {
4990 case omp::ClauseOrderKind::Concurrent:
4991 return llvm::omp::OrderKind::OMP_ORDER_concurrent;
4992 }
4993 llvm_unreachable("Unknown ClauseOrderKind kind");
4994}
4995
4996/// Converts an OpenMP simd loop into LLVM IR using OpenMPIRBuilder.
4997static LogicalResult
4998convertOmpSimd(Operation &opInst, llvm::IRBuilderBase &builder,
4999 LLVM::ModuleTranslation &moduleTranslation) {
5000 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5001 auto simdOp = cast<omp::SimdOp>(opInst);
5002
5003 if (failed(checkImplementationStatus(opInst)))
5004 return failure();
5005
5006 PrivateVarsInfo privateVarsInfo(simdOp);
5007
5008 MutableArrayRef<BlockArgument> reductionArgs =
5009 cast<omp::BlockArgOpenMPOpInterface>(opInst).getReductionBlockArgs();
5010 DenseMap<Value, llvm::Value *> reductionVariableMap;
5011 SmallVector<llvm::Value *> privateReductionVariables(
5012 simdOp.getNumReductionVars());
5013 SmallVector<DeferredStore> deferredStores;
5015 collectReductionDecls(simdOp, reductionDecls);
5016 llvm::ArrayRef<bool> isByRef = getIsByRef(simdOp.getReductionByref());
5017 assert(isByRef.size() == simdOp.getNumReductionVars());
5018
5019 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
5020 findAllocInsertPoints(builder, moduleTranslation);
5021
5023 simdOp, builder, moduleTranslation, privateVarsInfo, allocaIP);
5024 if (handleError(afterAllocas, opInst).failed())
5025 return failure();
5026
5027 // Initialize linear variables and linear step
5028 LinearClauseProcessor linearClauseProcessor;
5029 if (linearClauseProcessor.initLinearIV(simdOp).failed())
5030 return failure();
5031
5032 if (!simdOp.getLinearVars().empty()) {
5033 auto linearVarTypes = simdOp.getLinearVarTypes().value();
5034 for (mlir::Attribute linearVarType : linearVarTypes)
5035 linearClauseProcessor.registerType(moduleTranslation, linearVarType);
5036 for (auto [idx, linearVar] : llvm::enumerate(simdOp.getLinearVars())) {
5037 bool isImplicit = false;
5038 for (auto [mlirPrivVar, llvmPrivateVar] : llvm::zip_equal(
5039 privateVarsInfo.mlirVars, privateVarsInfo.llvmVars)) {
5040 // If the linear variable is implicit, reuse the already
5041 // existing llvm::Value
5042 if (linearVar == mlirPrivVar) {
5043 isImplicit = true;
5044 linearClauseProcessor.createLinearVar(builder, moduleTranslation,
5045 llvmPrivateVar, idx);
5046 break;
5047 }
5048 }
5049
5050 if (!isImplicit)
5051 linearClauseProcessor.createLinearVar(
5052 builder, moduleTranslation,
5053 moduleTranslation.lookupValue(linearVar), idx);
5054 }
5055 for (mlir::Value linearStep : simdOp.getLinearStepVars())
5056 linearClauseProcessor.initLinearStep(moduleTranslation, linearStep);
5057 }
5058
5059 if (failed(allocReductionVars(simdOp, reductionArgs, builder,
5060 moduleTranslation, allocaIP, reductionDecls,
5061 privateReductionVariables, reductionVariableMap,
5062 deferredStores, isByRef)))
5063 return failure();
5064
5065 if (handleError(initPrivateVars(builder, moduleTranslation, privateVarsInfo),
5066 opInst)
5067 .failed())
5068 return failure();
5069
5070 // No call to copyFirstPrivateVars because FIRSTPRIVATE is not allowed for
5071 // SIMD.
5072
5073 assert(afterAllocas.get()->getSinglePredecessor());
5074 if (failed(initReductionVars(simdOp, reductionArgs, builder,
5075 moduleTranslation,
5076 afterAllocas.get()->getSinglePredecessor(),
5077 reductionDecls, privateReductionVariables,
5078 reductionVariableMap, isByRef, deferredStores)))
5079 return failure();
5080
5081 llvm::ConstantInt *simdlen = nullptr;
5082 if (std::optional<uint64_t> simdlenVar = simdOp.getSimdlen())
5083 simdlen = builder.getInt64(simdlenVar.value());
5084
5085 llvm::ConstantInt *safelen = nullptr;
5086 if (std::optional<uint64_t> safelenVar = simdOp.getSafelen())
5087 safelen = builder.getInt64(safelenVar.value());
5088
5089 llvm::MapVector<llvm::Value *, llvm::Value *> alignedVars;
5090 llvm::omp::OrderKind order = convertOrderKind(simdOp.getOrder());
5091
5092 llvm::BasicBlock *sourceBlock = builder.GetInsertBlock();
5093 std::optional<ArrayAttr> alignmentValues = simdOp.getAlignments();
5094 mlir::OperandRange operands = simdOp.getAlignedVars();
5095 for (size_t i = 0; i < operands.size(); ++i) {
5096 llvm::Value *alignment = nullptr;
5097 llvm::Value *llvmVal = moduleTranslation.lookupValue(operands[i]);
5098 llvm::Type *ty = llvmVal->getType();
5099
5100 auto intAttr = cast<IntegerAttr>((*alignmentValues)[i]);
5101 alignment = builder.getInt64(intAttr.getInt());
5102 assert(ty->isPointerTy() && "Invalid type for aligned variable");
5103 assert(alignment && "Invalid alignment value");
5104
5105 // Check if the alignment value is not a power of 2. If so, skip emitting
5106 // alignment.
5107 if (!intAttr.getValue().isPowerOf2())
5108 continue;
5109
5110 auto curInsert = builder.saveIP();
5111 builder.SetInsertPoint(sourceBlock);
5112 llvmVal = builder.CreateLoad(ty, llvmVal);
5113 builder.restoreIP(curInsert);
5114 alignedVars[llvmVal] = alignment;
5115 }
5116
5118 simdOp.getRegion(), "omp.simd.region", builder, moduleTranslation);
5119
5120 if (failed(handleError(regionBlock, opInst)))
5121 return failure();
5122
5123 llvm::CanonicalLoopInfo *loopInfo = findCurrentLoopInfo(moduleTranslation);
5124 // Emit Initialization for linear variables
5125 if (simdOp.getLinearVars().size()) {
5126 linearClauseProcessor.initLinearVar(builder, moduleTranslation,
5127 loopInfo->getPreheader());
5128
5129 linearClauseProcessor.updateLinearVar(builder, loopInfo->getBody(),
5130 loopInfo->getIndVar());
5131 }
5132 builder.SetInsertPoint(*regionBlock, (*regionBlock)->begin());
5133
5134 for (size_t index = 0; index < simdOp.getLinearVars().size(); index++)
5135 linearClauseProcessor.rewriteInPlace(builder, loopInfo->getBody(),
5136 loopInfo->getLatch(), index);
5137
5138 ompBuilder->applySimd(loopInfo, alignedVars,
5139 simdOp.getIfExpr()
5140 ? moduleTranslation.lookupValue(simdOp.getIfExpr())
5141 : nullptr,
5142 order, simdlen, safelen);
5143
5144 linearClauseProcessor.updateLinearIV(builder, moduleTranslation);
5145 linearClauseProcessor.emitStoresForLinearVar(builder);
5146
5147 // We now need to reduce the per-simd-lane reduction variable into the
5148 // original variable. This works a bit differently to other reductions (e.g.
5149 // wsloop) because we don't need to call into the OpenMP runtime to handle
5150 // threads: everything happened in this one thread.
5151 for (auto [i, tuple] : llvm::enumerate(
5152 llvm::zip(reductionDecls, isByRef, simdOp.getReductionVars(),
5153 privateReductionVariables))) {
5154 auto [decl, byRef, reductionVar, privateReductionVar] = tuple;
5155
5156 OwningReductionGen gen = makeReductionGen(decl, builder, moduleTranslation);
5157 llvm::Value *originalVariable = moduleTranslation.lookupValue(reductionVar);
5158 llvm::Type *reductionType = moduleTranslation.convertType(decl.getType());
5159
5160 // We have one less load for by-ref case because that load is now inside of
5161 // the reduction region.
5162 llvm::Value *redValue = originalVariable;
5163 if (!byRef)
5164 redValue =
5165 builder.CreateLoad(reductionType, redValue, "red.value." + Twine(i));
5166 llvm::Value *privateRedValue = builder.CreateLoad(
5167 reductionType, privateReductionVar, "red.private.value." + Twine(i));
5168 llvm::Value *reduced;
5169
5170 auto res = gen(builder.saveIP(), redValue, privateRedValue, reduced);
5171 if (failed(handleError(res, opInst)))
5172 return failure();
5173 builder.restoreIP(res.get());
5174
5175 // For by-ref case, the store is inside of the reduction region.
5176 if (!byRef)
5177 builder.CreateStore(reduced, originalVariable);
5178 }
5179
5180 // After the construct, deallocate private reduction variables.
5181 SmallVector<Region *> reductionRegions;
5182 llvm::transform(reductionDecls, std::back_inserter(reductionRegions),
5183 [](omp::DeclareReductionOp reductionDecl) {
5184 return &reductionDecl.getCleanupRegion();
5185 });
5186 if (failed(inlineOmpRegionCleanup(reductionRegions, privateReductionVariables,
5187 moduleTranslation, builder,
5188 "omp.reduction.cleanup")))
5189 return failure();
5190
5191 return cleanupPrivateVars(simdOp, builder, moduleTranslation, simdOp.getLoc(),
5192 privateVarsInfo);
5193}
5194
5195/// Converts an OpenMP loop nest into LLVM IR using OpenMPIRBuilder.
5196static LogicalResult
5197convertOmpLoopNest(Operation &opInst, llvm::IRBuilderBase &builder,
5198 LLVM::ModuleTranslation &moduleTranslation) {
5199 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5200 auto loopOp = cast<omp::LoopNestOp>(opInst);
5201
5202 if (failed(checkImplementationStatus(opInst)))
5203 return failure();
5204
5205 // Set up the source location value for OpenMP runtime.
5206 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5207
5208 // Generator of the canonical loop body.
5211 auto bodyGen = [&](llvm::OpenMPIRBuilder::InsertPointTy ip,
5212 llvm::Value *iv) -> llvm::Error {
5213 // Make sure further conversions know about the induction variable.
5214 moduleTranslation.mapValue(
5215 loopOp.getRegion().front().getArgument(loopInfos.size()), iv);
5216
5217 // Capture the body insertion point for use in nested loops. BodyIP of the
5218 // CanonicalLoopInfo always points to the beginning of the entry block of
5219 // the body.
5220 bodyInsertPoints.push_back(ip);
5221
5222 if (loopInfos.size() != loopOp.getNumLoops() - 1)
5223 return llvm::Error::success();
5224
5225 // Convert the body of the loop.
5226 builder.restoreIP(ip);
5228 loopOp.getRegion(), "omp.loop_nest.region", builder, moduleTranslation);
5229 if (!regionBlock)
5230 return regionBlock.takeError();
5231
5232 builder.SetInsertPoint(*regionBlock, (*regionBlock)->begin());
5233 return llvm::Error::success();
5234 };
5235
5236 // Delegate actual loop construction to the OpenMP IRBuilder.
5237 // TODO: this currently assumes omp.loop_nest is semantically similar to SCF
5238 // loop, i.e. it has a positive step, uses signed integer semantics.
5239 // Reconsider this code when the nested loop operation clearly supports more
5240 // cases.
5241 for (unsigned i = 0, e = loopOp.getNumLoops(); i < e; ++i) {
5242 llvm::Value *lowerBound =
5243 moduleTranslation.lookupValue(loopOp.getLoopLowerBounds()[i]);
5244 llvm::Value *upperBound =
5245 moduleTranslation.lookupValue(loopOp.getLoopUpperBounds()[i]);
5246 llvm::Value *step = moduleTranslation.lookupValue(loopOp.getLoopSteps()[i]);
5247
5248 // Make sure loop trip count are emitted in the preheader of the outermost
5249 // loop at the latest so that they are all available for the new collapsed
5250 // loop will be created below.
5251 llvm::OpenMPIRBuilder::LocationDescription loc = ompLoc;
5252 llvm::OpenMPIRBuilder::InsertPointTy computeIP = ompLoc.IP;
5253 if (i != 0) {
5254 loc = llvm::OpenMPIRBuilder::LocationDescription(bodyInsertPoints.back(),
5255 ompLoc.DL);
5256 computeIP = loopInfos.front()->getPreheaderIP();
5257 }
5258
5260 ompBuilder->createCanonicalLoop(
5261 loc, bodyGen, lowerBound, upperBound, step,
5262 /*IsSigned=*/true, loopOp.getLoopInclusive(), computeIP);
5263
5264 if (failed(handleError(loopResult, *loopOp)))
5265 return failure();
5266
5267 loopInfos.push_back(*loopResult);
5268 }
5269
5270 llvm::OpenMPIRBuilder::InsertPointTy afterIP =
5271 loopInfos.front()->getAfterIP();
5272
5273 // Do tiling.
5274 if (const auto &tiles = loopOp.getTileSizes()) {
5275 llvm::Type *ivType = loopInfos.front()->getIndVarType();
5277
5278 for (auto tile : tiles.value()) {
5279 llvm::Value *tileVal = llvm::ConstantInt::get(ivType, tile);
5280 tileSizes.push_back(tileVal);
5281 }
5282
5283 std::vector<llvm::CanonicalLoopInfo *> newLoops =
5284 ompBuilder->tileLoops(ompLoc.DL, loopInfos, tileSizes);
5285
5286 // Update afterIP to get the correct insertion point after
5287 // tiling.
5288 llvm::BasicBlock *afterBB = newLoops.front()->getAfter();
5289 llvm::BasicBlock *afterAfterBB = afterBB->getSingleSuccessor();
5290 afterIP = {afterAfterBB, afterAfterBB->begin()};
5291
5292 // Update the loop infos.
5293 loopInfos.clear();
5294 for (const auto &newLoop : newLoops)
5295 loopInfos.push_back(newLoop);
5296 } // Tiling done.
5297
5298 // Do collapse.
5299 const auto &numCollapse = loopOp.getCollapseNumLoops();
5301 loopInfos.begin(), loopInfos.begin() + (numCollapse));
5302
5303 auto newTopLoopInfo =
5304 ompBuilder->collapseLoops(ompLoc.DL, collapseLoopInfos, {});
5305
5306 assert(newTopLoopInfo && "New top loop information is missing");
5307 moduleTranslation.stackWalk<OpenMPLoopInfoStackFrame>(
5308 [&](OpenMPLoopInfoStackFrame &frame) {
5309 frame.loopInfo = newTopLoopInfo;
5310 return WalkResult::interrupt();
5311 });
5312
5313 // Continue building IR after the loop. Note that the LoopInfo returned by
5314 // `collapseLoops` points inside the outermost loop and is intended for
5315 // potential further loop transformations. Use the insertion point stored
5316 // before collapsing loops instead.
5317 builder.restoreIP(afterIP);
5318 return success();
5319}
5320
5321/// Convert an omp.canonical_loop to LLVM-IR
5322static LogicalResult
5323convertOmpCanonicalLoopOp(omp::CanonicalLoopOp op, llvm::IRBuilderBase &builder,
5324 LLVM::ModuleTranslation &moduleTranslation) {
5325 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5326
5327 llvm::OpenMPIRBuilder::LocationDescription loopLoc(builder);
5328 Value loopIV = op.getInductionVar();
5329 Value loopTC = op.getTripCount();
5330
5331 llvm::Value *llvmTC = moduleTranslation.lookupValue(loopTC);
5332
5334 ompBuilder->createCanonicalLoop(
5335 loopLoc,
5336 [&](llvm::OpenMPIRBuilder::InsertPointTy ip, llvm::Value *llvmIV) {
5337 // Register the mapping of MLIR induction variable to LLVM-IR
5338 // induction variable
5339 moduleTranslation.mapValue(loopIV, llvmIV);
5340
5341 builder.restoreIP(ip);
5343 convertOmpOpRegions(op.getRegion(), "omp.loop.region", builder,
5344 moduleTranslation);
5345
5346 return bodyGenStatus.takeError();
5347 },
5348 llvmTC, "omp.loop");
5349 if (!llvmOrError)
5350 return op.emitError(llvm::toString(llvmOrError.takeError()));
5351
5352 llvm::CanonicalLoopInfo *llvmCLI = *llvmOrError;
5353 llvm::IRBuilderBase::InsertPoint afterIP = llvmCLI->getAfterIP();
5354 builder.restoreIP(afterIP);
5355
5356 // Register the mapping of MLIR loop to LLVM-IR OpenMPIRBuilder loop
5357 if (Value cli = op.getCli())
5358 moduleTranslation.mapOmpLoop(cli, llvmCLI);
5359
5360 return success();
5361}
5362
5363/// Apply a `#pragma omp unroll` / "!$omp unroll" transformation using the
5364/// OpenMPIRBuilder.
5365static LogicalResult
5366applyUnrollHeuristic(omp::UnrollHeuristicOp op, llvm::IRBuilderBase &builder,
5367 LLVM::ModuleTranslation &moduleTranslation) {
5368 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5369
5370 Value applyee = op.getApplyee();
5371 assert(applyee && "Loop to apply unrolling on required");
5372
5373 llvm::CanonicalLoopInfo *consBuilderCLI =
5374 moduleTranslation.lookupOMPLoop(applyee);
5375 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5376 ompBuilder->unrollLoopHeuristic(loc.DL, consBuilderCLI);
5377
5378 moduleTranslation.invalidateOmpLoop(applyee);
5379 return success();
5380}
5381
5382/// Apply a `#pragma omp unroll partial` / `!$omp unroll partial`
5383/// transformation using the OpenMPIRBuilder.
5384static LogicalResult
5385applyUnrollPartial(omp::UnrollPartialOp op, llvm::IRBuilderBase &builder,
5386 LLVM::ModuleTranslation &moduleTranslation) {
5387 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5388
5389 Value applyee = op.getApplyee();
5390 assert(applyee && "Loop to apply unrolling on required");
5391
5392 llvm::CanonicalLoopInfo *consBuilderCLI =
5393 moduleTranslation.lookupOMPLoop(applyee);
5394 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5395
5396 // No generatee is supported yet, so the unrolled loop's CanonicalLoopInfo is
5397 // not requested and unrolling is deferred to LLVM's LoopUnroll pass.
5398 int32_t factor = static_cast<int32_t>(op.getUnrollFactor());
5399 ompBuilder->unrollLoopPartial(loc.DL, consBuilderCLI, factor,
5400 /*UnrolledCLI=*/nullptr);
5401
5402 moduleTranslation.invalidateOmpLoop(applyee);
5403 return success();
5404}
5405
5406/// Apply a `#pragma omp tile` / `!$omp tile` transformation using the
5407/// OpenMPIRBuilder.
5408static LogicalResult applyTile(omp::TileOp op, llvm::IRBuilderBase &builder,
5409 LLVM::ModuleTranslation &moduleTranslation) {
5410 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5411 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5412
5414 SmallVector<llvm::Value *> translatedSizes;
5415
5416 for (Value size : op.getSizes()) {
5417 llvm::Value *translatedSize = moduleTranslation.lookupValue(size);
5418 assert(translatedSize &&
5419 "sizes clause arguments must already be translated");
5420 translatedSizes.push_back(translatedSize);
5421 }
5422
5423 for (Value applyee : op.getApplyees()) {
5424 llvm::CanonicalLoopInfo *consBuilderCLI =
5425 moduleTranslation.lookupOMPLoop(applyee);
5426 assert(applyee && "Canonical loop must already been translated");
5427 translatedLoops.push_back(consBuilderCLI);
5428 }
5429
5430 auto generatedLoops =
5431 ompBuilder->tileLoops(loc.DL, translatedLoops, translatedSizes);
5432 if (!op.getGeneratees().empty()) {
5433 for (auto [mlirLoop, genLoop] :
5434 zip_equal(op.getGeneratees(), generatedLoops))
5435 moduleTranslation.mapOmpLoop(mlirLoop, genLoop);
5436 }
5437
5438 // CLIs can only be consumed once
5439 for (Value applyee : op.getApplyees())
5440 moduleTranslation.invalidateOmpLoop(applyee);
5441
5442 return success();
5443}
5444
5445/// Apply a `#pragma omp fuse` / `!$omp fuse` transformation using the
5446/// OpenMPIRBuilder.
5447static LogicalResult applyFuse(omp::FuseOp op, llvm::IRBuilderBase &builder,
5448 LLVM::ModuleTranslation &moduleTranslation) {
5449 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5450 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5451
5452 // Select what CLIs are going to be fused
5453 SmallVector<llvm::CanonicalLoopInfo *> beforeFuse, toFuse, afterFuse;
5454 for (size_t i = 0; i < op.getApplyees().size(); i++) {
5455 Value applyee = op.getApplyees()[i];
5456 llvm::CanonicalLoopInfo *consBuilderCLI =
5457 moduleTranslation.lookupOMPLoop(applyee);
5458 assert(applyee && "Canonical loop must already been translated");
5459 if (op.getFirst().has_value() && i < op.getFirst().value() - 1)
5460 beforeFuse.push_back(consBuilderCLI);
5461 else if (op.getCount().has_value() &&
5462 i >= op.getFirst().value() + op.getCount().value() - 1)
5463 afterFuse.push_back(consBuilderCLI);
5464 else
5465 toFuse.push_back(consBuilderCLI);
5466 }
5467 assert(
5468 (op.getGeneratees().empty() ||
5469 beforeFuse.size() + afterFuse.size() + 1 == op.getGeneratees().size()) &&
5470 "Wrong number of generatees");
5471
5472 // do the fuse
5473 auto generatedLoop = ompBuilder->fuseLoops(loc.DL, toFuse);
5474 if (!op.getGeneratees().empty()) {
5475 size_t i = 0;
5476 for (; i < beforeFuse.size(); i++)
5477 moduleTranslation.mapOmpLoop(op.getGeneratees()[i], beforeFuse[i]);
5478 moduleTranslation.mapOmpLoop(op.getGeneratees()[i++], generatedLoop);
5479 for (; i < afterFuse.size(); i++)
5480 moduleTranslation.mapOmpLoop(op.getGeneratees()[i], afterFuse[i]);
5481 }
5482
5483 // CLIs can only be consumed once
5484 for (Value applyee : op.getApplyees())
5485 moduleTranslation.invalidateOmpLoop(applyee);
5486
5487 return success();
5488}
5489
5490/// Convert an Atomic Ordering attribute to llvm::AtomicOrdering.
5491static llvm::AtomicOrdering
5492convertAtomicOrdering(std::optional<omp::ClauseMemoryOrderKind> ao) {
5493 if (!ao)
5494 return llvm::AtomicOrdering::Monotonic; // Default Memory Ordering
5495
5496 switch (*ao) {
5497 case omp::ClauseMemoryOrderKind::Seq_cst:
5498 return llvm::AtomicOrdering::SequentiallyConsistent;
5499 case omp::ClauseMemoryOrderKind::Acq_rel:
5500 return llvm::AtomicOrdering::AcquireRelease;
5501 case omp::ClauseMemoryOrderKind::Acquire:
5502 return llvm::AtomicOrdering::Acquire;
5503 case omp::ClauseMemoryOrderKind::Release:
5504 return llvm::AtomicOrdering::Release;
5505 case omp::ClauseMemoryOrderKind::Relaxed:
5506 return llvm::AtomicOrdering::Monotonic;
5507 }
5508 llvm_unreachable("Unknown ClauseMemoryOrderKind kind");
5509}
5510
5511/// Convert omp.atomic.read operation to LLVM IR.
5512static LogicalResult
5513convertOmpAtomicRead(Operation &opInst, llvm::IRBuilderBase &builder,
5514 LLVM::ModuleTranslation &moduleTranslation) {
5515 auto readOp = cast<omp::AtomicReadOp>(opInst);
5516 if (failed(checkImplementationStatus(opInst)))
5517 return failure();
5518
5519 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5520 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
5521 findAllocInsertPoints(builder, moduleTranslation);
5522
5523 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5524
5525 llvm::AtomicOrdering AO = convertAtomicOrdering(readOp.getMemoryOrder());
5526 llvm::Value *x = moduleTranslation.lookupValue(readOp.getX());
5527 llvm::Value *v = moduleTranslation.lookupValue(readOp.getV());
5528
5529 llvm::Type *elementType =
5530 moduleTranslation.convertType(readOp.getElementType());
5531
5532 llvm::OpenMPIRBuilder::AtomicOpValue V = {v, elementType, false, false};
5533 llvm::OpenMPIRBuilder::AtomicOpValue X = {x, elementType, false, false};
5534 builder.restoreIP(ompBuilder->createAtomicRead(ompLoc, X, V, AO, allocaIP));
5535 return success();
5536}
5537
5538/// Converts an omp.atomic.write operation to LLVM IR.
5539static LogicalResult
5540convertOmpAtomicWrite(Operation &opInst, llvm::IRBuilderBase &builder,
5541 LLVM::ModuleTranslation &moduleTranslation) {
5542 auto writeOp = cast<omp::AtomicWriteOp>(opInst);
5543 if (failed(checkImplementationStatus(opInst)))
5544 return failure();
5545
5546 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5547 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
5548 findAllocInsertPoints(builder, moduleTranslation);
5549
5550 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5551 llvm::AtomicOrdering ao = convertAtomicOrdering(writeOp.getMemoryOrder());
5552 llvm::Value *expr = moduleTranslation.lookupValue(writeOp.getExpr());
5553 llvm::Value *dest = moduleTranslation.lookupValue(writeOp.getX());
5554 llvm::Type *ty = moduleTranslation.convertType(writeOp.getExpr().getType());
5555 llvm::OpenMPIRBuilder::AtomicOpValue x = {dest, ty, /*isSigned=*/false,
5556 /*isVolatile=*/false};
5557 builder.restoreIP(
5558 ompBuilder->createAtomicWrite(ompLoc, x, expr, ao, allocaIP));
5559 return success();
5560}
5561
5562/// Converts an LLVM dialect binary operation to the corresponding enum value
5563/// for `atomicrmw` supported binary operation.
5564static llvm::AtomicRMWInst::BinOp convertBinOpToAtomic(Operation &op) {
5566 .Case([&](LLVM::AddOp) { return llvm::AtomicRMWInst::BinOp::Add; })
5567 .Case([&](LLVM::SubOp) { return llvm::AtomicRMWInst::BinOp::Sub; })
5568 .Case([&](LLVM::AndOp) { return llvm::AtomicRMWInst::BinOp::And; })
5569 .Case([&](LLVM::OrOp) { return llvm::AtomicRMWInst::BinOp::Or; })
5570 .Case([&](LLVM::XOrOp) { return llvm::AtomicRMWInst::BinOp::Xor; })
5571 .Case([&](LLVM::UMaxOp) { return llvm::AtomicRMWInst::BinOp::UMax; })
5572 .Case([&](LLVM::UMinOp) { return llvm::AtomicRMWInst::BinOp::UMin; })
5573 .Case([&](LLVM::FAddOp) { return llvm::AtomicRMWInst::BinOp::FAdd; })
5574 .Case([&](LLVM::FSubOp) { return llvm::AtomicRMWInst::BinOp::FSub; })
5575 .Default(llvm::AtomicRMWInst::BinOp::BAD_BINOP);
5576}
5577
5578static void extractAtomicControlFlags(omp::AtomicUpdateOp atomicUpdateOp,
5579 bool &isIgnoreDenormalMode,
5580 bool &isFineGrainedMemory,
5581 bool &isRemoteMemory) {
5582 isIgnoreDenormalMode = false;
5583 isFineGrainedMemory = false;
5584 isRemoteMemory = false;
5585 if (atomicUpdateOp &&
5586 atomicUpdateOp->hasAttr(atomicUpdateOp.getAtomicControlAttrName())) {
5587 mlir::omp::AtomicControlAttr atomicControlAttr =
5588 atomicUpdateOp.getAtomicControlAttr();
5589 isIgnoreDenormalMode = atomicControlAttr.getIgnoreDenormalMode();
5590 isFineGrainedMemory = atomicControlAttr.getFineGrainedMemory();
5591 isRemoteMemory = atomicControlAttr.getRemoteMemory();
5592 }
5593}
5594
5595/// Converts an OpenMP atomic update operation using OpenMPIRBuilder.
5596static LogicalResult
5597convertOmpAtomicUpdate(omp::AtomicUpdateOp &opInst,
5598 llvm::IRBuilderBase &builder,
5599 LLVM::ModuleTranslation &moduleTranslation) {
5600 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5601 if (failed(checkImplementationStatus(*opInst)))
5602 return failure();
5603
5604 // Convert values and types.
5605 auto &innerOpList = opInst.getRegion().front().getOperations();
5606 bool isXBinopExpr{false};
5607 llvm::AtomicRMWInst::BinOp binop;
5608 mlir::Value mlirExpr;
5609 llvm::Value *llvmExpr = nullptr;
5610 llvm::Value *llvmX = nullptr;
5611 llvm::Type *llvmXElementType = nullptr;
5612 if (innerOpList.size() == 2) {
5613 // The two operations here are the update and the terminator.
5614 // Since we can identify the update operation, there is a possibility
5615 // that we can generate the atomicrmw instruction.
5616 mlir::Operation &innerOp = *opInst.getRegion().front().begin();
5617 if (!llvm::is_contained(innerOp.getOperands(),
5618 opInst.getRegion().getArgument(0))) {
5619 return opInst.emitError("no atomic update operation with region argument"
5620 " as operand found inside atomic.update region");
5621 }
5622 binop = convertBinOpToAtomic(innerOp);
5623 isXBinopExpr = innerOp.getOperand(0) == opInst.getRegion().getArgument(0);
5624 mlirExpr = (isXBinopExpr ? innerOp.getOperand(1) : innerOp.getOperand(0));
5625 llvmExpr = moduleTranslation.lookupValue(mlirExpr);
5626 } else {
5627 // Since the update region includes more than one operation
5628 // we will resort to generating a cmpxchg loop.
5629 binop = llvm::AtomicRMWInst::BinOp::BAD_BINOP;
5630 }
5631 llvmX = moduleTranslation.lookupValue(opInst.getX());
5632 llvmXElementType = moduleTranslation.convertType(
5633 opInst.getRegion().getArgument(0).getType());
5634 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {llvmX, llvmXElementType,
5635 /*isSigned=*/false,
5636 /*isVolatile=*/false};
5637
5638 llvm::AtomicOrdering atomicOrdering =
5639 convertAtomicOrdering(opInst.getMemoryOrder());
5640
5641 // Generate update code.
5642 auto updateFn =
5643 [&opInst, &moduleTranslation](
5644 llvm::Value *atomicx,
5645 llvm::IRBuilder<> &builder) -> llvm::Expected<llvm::Value *> {
5646 Block &bb = *opInst.getRegion().begin();
5647 moduleTranslation.mapValue(*opInst.getRegion().args_begin(), atomicx);
5648 moduleTranslation.mapBlock(&bb, builder.GetInsertBlock());
5649 if (failed(moduleTranslation.convertBlock(bb, true, builder)))
5650 return llvm::make_error<PreviouslyReportedError>();
5651
5652 omp::YieldOp yieldop = dyn_cast<omp::YieldOp>(bb.getTerminator());
5653 assert(yieldop && yieldop.getResults().size() == 1 &&
5654 "terminator must be omp.yield op and it must have exactly one "
5655 "argument");
5656 return moduleTranslation.lookupValue(yieldop.getResults()[0]);
5657 };
5658
5659 bool isIgnoreDenormalMode;
5660 bool isFineGrainedMemory;
5661 bool isRemoteMemory;
5662 extractAtomicControlFlags(opInst, isIgnoreDenormalMode, isFineGrainedMemory,
5663 isRemoteMemory);
5664 // Handle ambiguous alloca, if any.
5665 auto allocaIP = findAllocInsertPoints(builder, moduleTranslation);
5666 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5667 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
5668 ompBuilder->createAtomicUpdate(ompLoc, allocaIP, llvmAtomicX, llvmExpr,
5669 atomicOrdering, binop, updateFn,
5670 isXBinopExpr, isIgnoreDenormalMode,
5671 isFineGrainedMemory, isRemoteMemory);
5672
5673 if (failed(handleError(afterIP, *opInst)))
5674 return failure();
5675
5676 builder.restoreIP(*afterIP);
5677 return success();
5678}
5679
5680static LogicalResult
5681convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
5682 llvm::IRBuilderBase &builder,
5683 LLVM::ModuleTranslation &moduleTranslation) {
5684 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5685 if (failed(checkImplementationStatus(*atomicCaptureOp)))
5686 return failure();
5687
5688 mlir::Value mlirExpr;
5689 bool isXBinopExpr = false, isPostfixUpdate = false;
5690 llvm::AtomicRMWInst::BinOp binop = llvm::AtomicRMWInst::BinOp::BAD_BINOP;
5691
5692 omp::AtomicUpdateOp atomicUpdateOp = atomicCaptureOp.getAtomicUpdateOp();
5693 omp::AtomicWriteOp atomicWriteOp = atomicCaptureOp.getAtomicWriteOp();
5694
5695 assert((atomicUpdateOp || atomicWriteOp) &&
5696 "internal op must be an atomic.update or atomic.write op");
5697
5698 if (atomicWriteOp) {
5699 isPostfixUpdate = true;
5700 mlirExpr = atomicWriteOp.getExpr();
5701 } else {
5702 isPostfixUpdate = atomicCaptureOp.getSecondOp() ==
5703 atomicCaptureOp.getAtomicUpdateOp().getOperation();
5704 auto &innerOpList = atomicUpdateOp.getRegion().front().getOperations();
5705 // Find the binary update operation that uses the region argument
5706 // and get the expression to update
5707 if (innerOpList.size() == 2) {
5708 mlir::Operation &innerOp = *atomicUpdateOp.getRegion().front().begin();
5709 if (!llvm::is_contained(innerOp.getOperands(),
5710 atomicUpdateOp.getRegion().getArgument(0))) {
5711 return atomicUpdateOp.emitError(
5712 "no atomic update operation with region argument"
5713 " as operand found inside atomic.update region");
5714 }
5715 binop = convertBinOpToAtomic(innerOp);
5716 isXBinopExpr =
5717 innerOp.getOperand(0) == atomicUpdateOp.getRegion().getArgument(0);
5718 mlirExpr = (isXBinopExpr ? innerOp.getOperand(1) : innerOp.getOperand(0));
5719 } else {
5720 binop = llvm::AtomicRMWInst::BinOp::BAD_BINOP;
5721 }
5722 }
5723
5724 llvm::Value *llvmExpr = moduleTranslation.lookupValue(mlirExpr);
5725 llvm::Value *llvmX =
5726 moduleTranslation.lookupValue(atomicCaptureOp.getAtomicReadOp().getX());
5727 llvm::Value *llvmV =
5728 moduleTranslation.lookupValue(atomicCaptureOp.getAtomicReadOp().getV());
5729 llvm::Type *llvmXElementType = moduleTranslation.convertType(
5730 atomicCaptureOp.getAtomicReadOp().getElementType());
5731 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {llvmX, llvmXElementType,
5732 /*isSigned=*/false,
5733 /*isVolatile=*/false};
5734 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicV = {llvmV, llvmXElementType,
5735 /*isSigned=*/false,
5736 /*isVolatile=*/false};
5737
5738 llvm::AtomicOrdering atomicOrdering =
5739 convertAtomicOrdering(atomicCaptureOp.getMemoryOrder());
5740
5741 auto updateFn =
5742 [&](llvm::Value *atomicx,
5743 llvm::IRBuilder<> &builder) -> llvm::Expected<llvm::Value *> {
5744 if (atomicWriteOp)
5745 return moduleTranslation.lookupValue(atomicWriteOp.getExpr());
5746 Block &bb = *atomicUpdateOp.getRegion().begin();
5747 moduleTranslation.mapValue(*atomicUpdateOp.getRegion().args_begin(),
5748 atomicx);
5749 moduleTranslation.mapBlock(&bb, builder.GetInsertBlock());
5750 if (failed(moduleTranslation.convertBlock(bb, true, builder)))
5751 return llvm::make_error<PreviouslyReportedError>();
5752
5753 omp::YieldOp yieldop = dyn_cast<omp::YieldOp>(bb.getTerminator());
5754 assert(yieldop && yieldop.getResults().size() == 1 &&
5755 "terminator must be omp.yield op and it must have exactly one "
5756 "argument");
5757 return moduleTranslation.lookupValue(yieldop.getResults()[0]);
5758 };
5759
5760 bool isIgnoreDenormalMode;
5761 bool isFineGrainedMemory;
5762 bool isRemoteMemory;
5763 extractAtomicControlFlags(atomicUpdateOp, isIgnoreDenormalMode,
5764 isFineGrainedMemory, isRemoteMemory);
5765 // Handle ambiguous alloca, if any.
5766 auto allocaIP = findAllocInsertPoints(builder, moduleTranslation);
5767 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5768 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
5769 ompBuilder->createAtomicCapture(
5770 ompLoc, allocaIP, llvmAtomicX, llvmAtomicV, llvmExpr, atomicOrdering,
5771 binop, updateFn, atomicUpdateOp, isPostfixUpdate, isXBinopExpr,
5772 isIgnoreDenormalMode, isFineGrainedMemory, isRemoteMemory);
5773
5774 if (failed(handleError(afterIP, *atomicCaptureOp)))
5775 return failure();
5776
5777 builder.restoreIP(*afterIP);
5778 return success();
5779}
5780
5781/// Helper to extract the OMPAtomicCompareOp from an integer comparison
5782/// predicate. Returns std::nullopt for unsupported predicates.
5783static std::optional<llvm::omp::OMPAtomicCompareOp>
5784convertICmpPredicateToAtomicCompareOp(LLVM::ICmpPredicate predicate) {
5785 switch (predicate) {
5786 case LLVM::ICmpPredicate::eq:
5787 return llvm::omp::OMPAtomicCompareOp::EQ;
5788 case LLVM::ICmpPredicate::slt:
5789 case LLVM::ICmpPredicate::ult:
5790 return llvm::omp::OMPAtomicCompareOp::MIN;
5791 case LLVM::ICmpPredicate::sgt:
5792 case LLVM::ICmpPredicate::ugt:
5793 return llvm::omp::OMPAtomicCompareOp::MAX;
5794 default:
5795 return std::nullopt;
5796 }
5797}
5798
5799/// Helper to extract the OMPAtomicCompareOp from a floating-point comparison
5800/// predicate. Returns std::nullopt for unsupported predicates.
5801static std::optional<llvm::omp::OMPAtomicCompareOp>
5802convertFCmpPredicateToAtomicCompareOp(LLVM::FCmpPredicate predicate) {
5803 switch (predicate) {
5804 case LLVM::FCmpPredicate::oeq:
5805 case LLVM::FCmpPredicate::ueq:
5806 return llvm::omp::OMPAtomicCompareOp::EQ;
5807 case LLVM::FCmpPredicate::olt:
5808 case LLVM::FCmpPredicate::ult:
5809 return llvm::omp::OMPAtomicCompareOp::MIN;
5810 case LLVM::FCmpPredicate::ogt:
5811 case LLVM::FCmpPredicate::ugt:
5812 return llvm::omp::OMPAtomicCompareOp::MAX;
5813 default:
5814 return std::nullopt;
5815 }
5816}
5817
5818/// Converts an omp.atomic.compare operation to LLVM IR.
5819///
5820/// if (x == e) x = d
5821/// The region contains a comparison + select pattern:
5822/// ^bb0(%xval: T):
5823/// %cmp = llvm.icmp/fcmp <pred> %xval, %e : T
5824/// %sel = llvm.select %cmp, %d, %xval : i1, T
5825/// omp.yield(%sel : T)
5826///
5827/// From MLIR extract:
5828/// 1) comparison operator
5829/// 2) expected value (e)
5830/// 3) desired value (d)
5831/// These are passed to OpenMPIRBuilder::createAtomicCompare which generates
5832/// the actual cmpxchg / atomicrmw instruction.
5833///
5834static LogicalResult
5835convertOmpAtomicCompare(omp::AtomicCompareOp atomicCompareOp,
5836 llvm::IRBuilderBase &builder,
5837 LLVM::ModuleTranslation &moduleTranslation) {
5838 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5839 if (failed(checkImplementationStatus(*atomicCompareOp)))
5840 return failure();
5841
5842 Region &region = atomicCompareOp.getRegion();
5843 Block &block = region.front();
5844
5845 // Determine element type from the region block argument
5846 llvm::Type *llvmXElementType =
5847 moduleTranslation.convertType(block.getArgument(0).getType());
5848 if (!llvmXElementType)
5849 return atomicCompareOp.emitError(
5850 "unable to determine element type for atomic compare");
5851
5852 llvm::Value *llvmX = moduleTranslation.lookupValue(atomicCompareOp.getX());
5853
5854 // IsSigned is determined from the comparison predicate in the region.
5855 // Signed ICmp predicates (slt/sgt) set this to true; unsigned (ult/ugt)
5856 // leave it false. For EQ and float comparisons, signedness is irrelevant.
5857 bool isSigned = false;
5858 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {llvmX, llvmXElementType,
5859 isSigned,
5860 /*IsVolatile=*/false};
5861
5862 llvm::AtomicOrdering atomicOrdering =
5863 convertAtomicOrdering(atomicCompareOp.getMemoryOrder());
5864
5865 auto isAtomicComparePatternOp = [](Operation &op) {
5866 return llvm::isa<LLVM::ICmpOp, LLVM::FCmpOp, LLVM::SelectOp, LLVM::AndOp,
5867 LLVM::OrOp>(op);
5868 };
5869
5870 // Pre-translate operations inside the region that compute e and d (e.g.,
5871 // GEP, loads for dereferencing Fortran pointers) but are not part of the
5872 // atomic compare-and-swap pattern (icmp/fcmp, select, and/or).
5873 //
5874 // 1) Validity: The OpenMP spec requires e and d to be evaluated before the
5875 // atomic operation, so emitting their computation here is correct.
5876 // 2) Memory effects: These ops only depend on values defined outside the
5877 // region. They cannot observe the block argument (%xval), which is the
5878 // value loaded atomically by cmpxchg and does not exist yet.
5879 // 3) Invariant enforcement: The `allOperandsMapped` check below skips any
5880 // op whose operands include the unmapped block argument, guaranteeing
5881 // only region-external-dependent ops are pre-translated.
5882 for (Operation &op : block.without_terminator()) {
5883 // Skip operations that form the atomic compare pattern — these are
5884 // not emitted as individual instructions but are analyzed below to
5885 // extract the comparison predicate, expected value (e), and desired
5886 // value (d) for generating a single cmpxchg/atomicrmw.
5887 if (isAtomicComparePatternOp(op))
5888 continue;
5889
5890 // Avoid translating ops that depend on the unmapped block argument.
5891 bool allOperandsMapped = llvm::all_of(op.getOperands(), [&](mlir::Value v) {
5892 return moduleTranslation.lookupValue(v) != nullptr;
5893 });
5894 if (!allOperandsMapped)
5895 continue;
5896
5897 if (failed(moduleTranslation.convertOperation(op, builder)))
5898 return atomicCompareOp.emitError(
5899 "failed to translate operation inside atomic compare region");
5900 }
5901
5902 // Look up a value that may have been pre-translated or defined outside the
5903 // region.
5904 auto materializeValue = [&](mlir::Value val) -> llvm::Value * {
5905 // Check if the value is already mapped (pre-translated or defined outside).
5906 if (llvm::Value *existing = moduleTranslation.lookupValue(val))
5907 return existing;
5908 // Fallback for a single LoadOp whose address is mapped but whose result
5909 // was not pre-translated.
5910 if (auto loadOp = val.getDefiningOp<LLVM::LoadOp>()) {
5911 if (loadOp->getParentRegion() == &region) {
5912 llvm::Value *loadAddr = moduleTranslation.lookupValue(loadOp.getAddr());
5913 if (!loadAddr)
5914 return nullptr;
5915 llvm::Type *loadType =
5916 moduleTranslation.convertType(loadOp.getResult().getType());
5917 return builder.CreateLoad(loadType, loadAddr);
5918 }
5919 }
5920 return nullptr;
5921 };
5922
5923 // Walk the region to extract comparison predicate, eVal, and dVal.
5924 // if (x == eVal) x = dVal
5925 llvm::omp::OMPAtomicCompareOp compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
5926 llvm::Value *eVal = nullptr;
5927 llvm::Value *dVal = nullptr;
5928 bool isXBinopExpr = false;
5929
5930 auto traceToAggregate = [](mlir::Value v) -> mlir::Value {
5931 if (auto extractOp = v.getDefiningOp<LLVM::ExtractValueOp>())
5932 return extractOp.getContainer();
5933 return nullptr;
5934 };
5935
5936 // Check for a decomposed complex comparison pattern:
5937 // %re_x = llvm.extractvalue %xval[0]
5938 // %re_e = llvm.extractvalue %eStruct[0]
5939 // %cmp_re = llvm.fcmp "oeq" %re_x, %re_e
5940 // %im_x = llvm.extractvalue %xval[1]
5941 // %im_e = llvm.extractvalue %eStruct[1]
5942 // %cmp_im = llvm.fcmp "oeq" %im_x, %im_e
5943 // %cmp = llvm.and %cmp_re, %cmp_im (for EQ)
5944 // Detect this by looking for AndOp/OrOp whose operands are both FCmpOps
5945 // operating on ExtractValueOps from the block argument.
5946 bool isComplexPattern = false;
5947 for (Operation &op : block.getOperations()) {
5948 if (!isa<LLVM::AndOp, LLVM::OrOp>(op))
5949 continue;
5950
5951 // Using : %cmp = llvm.and %cmp_re, %cmp_im
5952 auto lhsFcmp = op.getOperand(0).getDefiningOp<LLVM::FCmpOp>();
5953 auto rhsFcmp = op.getOperand(1).getDefiningOp<LLVM::FCmpOp>();
5954 if (!lhsFcmp || !rhsFcmp)
5955 continue;
5956
5957 // Using : %cmp_re = llvm.fcmp "oeq" %re_x, %re_e
5958 // Check presence of x (block argument) and get e.
5959 mlir::Value lhsAgg0 = traceToAggregate(lhsFcmp.getOperand(0));
5960 mlir::Value lhsAgg1 = traceToAggregate(lhsFcmp.getOperand(1));
5961 bool lhsXIsOp0 = (lhsAgg0 == block.getArgument(0));
5962 bool lhsXIsOp1 = (lhsAgg1 == block.getArgument(0));
5963 if (!lhsXIsOp0 && !lhsXIsOp1)
5964 continue;
5965 mlir::Value eAggregate = lhsXIsOp0 ? lhsAgg1 : lhsAgg0;
5966 if (!eAggregate)
5967 continue;
5968
5969 if (isa<LLVM::AndOp>(op))
5970 compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
5971 else
5972 // OrOp corresponds to NE, which is not a valid atomic compare op.
5973 return atomicCompareOp.emitError(
5974 "unsupported comparison predicate (NE) for complex atomic compare");
5975
5976 isXBinopExpr = lhsXIsOp0;
5977 eVal = materializeValue(eAggregate);
5978 isComplexPattern = true;
5979 break;
5980 }
5981
5982 if (isComplexPattern) {
5983 // dVal from SelectOp or YieldOp.
5984 for (Operation &op : block.getOperations()) {
5985 if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
5986 dVal = materializeValue(selectOp.getTrueValue());
5987 break;
5988 }
5989 }
5990 if (!dVal) {
5991 auto yieldOp = cast<omp::YieldOp>(block.getTerminator());
5992 if (yieldOp.getResults().empty())
5993 return atomicCompareOp.emitError(
5994 "failed to extract desired value (d) from atomic compare region");
5995 dVal = materializeValue(yieldOp.getResults()[0]);
5996 }
5997
5998 const llvm::DataLayout &DL =
5999 builder.GetInsertBlock()->getModule()->getDataLayout();
6000 unsigned totalBits =
6001 DL.getTypeStoreSizeInBits(llvmXElementType).getFixedValue();
6002
6003 llvm::IntegerType *intTy =
6004 llvm::IntegerType::get(builder.getContext(), totalBits);
6005
6006 llvm::Align complexAlign = DL.getABITypeAlign(llvmXElementType);
6007 llvm::Align intAlign = DL.getABITypeAlign(intTy);
6008 llvm::Align maxAlign = std::max(complexAlign, intAlign);
6009
6010 llvm::AllocaInst *eAlloca =
6011 builder.CreateAlloca(llvmXElementType, nullptr, "cmplx.e");
6012 eAlloca->setAlignment(maxAlign);
6013 llvm::AllocaInst *dAlloca =
6014 builder.CreateAlloca(llvmXElementType, nullptr, "cmplx.d");
6015 dAlloca->setAlignment(maxAlign);
6016
6017 builder.CreateAlignedStore(eVal, eAlloca, maxAlign);
6018 llvm::Value *eInt =
6019 builder.CreateAlignedLoad(intTy, eAlloca, maxAlign, "cmplx.e.int");
6020 builder.CreateAlignedStore(dVal, dAlloca, maxAlign);
6021 llvm::Value *dInt =
6022 builder.CreateAlignedLoad(intTy, dAlloca, maxAlign, "cmplx.d.int");
6023
6024 llvm::AtomicOrdering failOrdering =
6025 llvm::AtomicCmpXchgInst::getStrongestFailureOrdering(atomicOrdering);
6026 auto *cmpXchg = builder.CreateAtomicCmpXchg(llvmX, eInt, dInt, maxAlign,
6027 atomicOrdering, failOrdering);
6028 cmpXchg->setWeak(atomicCompareOp.getWeak());
6029
6030 // Emit flush after atomic compare if needed (for release, acq_rel,
6031 // seq_cst orderings).
6032 if (atomicOrdering == llvm::AtomicOrdering::Release ||
6033 atomicOrdering == llvm::AtomicOrdering::AcquireRelease ||
6034 atomicOrdering == llvm::AtomicOrdering::SequentiallyConsistent) {
6035 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6036 ompBuilder->createFlush(ompLoc);
6037 }
6038 return success();
6039 } else {
6040
6041 for (Operation &op : block.getOperations()) {
6042 if (auto icmpOp = dyn_cast<LLVM::ICmpOp>(op)) {
6043 auto maybeOp =
6044 convertICmpPredicateToAtomicCompareOp(icmpOp.getPredicate());
6045 if (!maybeOp)
6046 return atomicCompareOp.emitError(
6047 "unsupported comparison predicate in atomic compare");
6048 compareOp = *maybeOp;
6049
6050 LLVM::ICmpPredicate pred = icmpOp.getPredicate();
6051 isSigned = (pred == LLVM::ICmpPredicate::slt ||
6052 pred == LLVM::ICmpPredicate::sgt ||
6053 pred == LLVM::ICmpPredicate::sle ||
6054 pred == LLVM::ICmpPredicate::sge);
6055
6056 // Identify which operand is the block argument (x) and which is e.
6057 isXBinopExpr = (icmpOp.getOperand(0) == block.getArgument(0));
6058 mlir::Value eOperand =
6059 isXBinopExpr ? icmpOp.getOperand(1) : icmpOp.getOperand(0);
6060 eVal = materializeValue(eOperand);
6061 } else if (auto fcmpOp = dyn_cast<LLVM::FCmpOp>(op)) {
6062 auto maybeOp =
6063 convertFCmpPredicateToAtomicCompareOp(fcmpOp.getPredicate());
6064 if (!maybeOp)
6065 return atomicCompareOp.emitError(
6066 "unsupported comparison predicate in atomic compare");
6067 compareOp = *maybeOp;
6068
6069 isXBinopExpr = (fcmpOp.getOperand(0) == block.getArgument(0));
6070 mlir::Value eOperand =
6071 isXBinopExpr ? fcmpOp.getOperand(1) : fcmpOp.getOperand(0);
6072 eVal = materializeValue(eOperand);
6073 } else if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
6074 if (!dVal)
6075 dVal = materializeValue(selectOp.getTrueValue());
6076 }
6077 }
6078 }
6079
6080 // For non-complex patterns, also extract dVal from SelectOp.
6081 if (!dVal) {
6082 for (Operation &op : block.getOperations()) {
6083 if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
6084 dVal = materializeValue(selectOp.getTrueValue());
6085 break;
6086 }
6087 }
6088 }
6089
6090 if (!eVal)
6091 return atomicCompareOp.emitError(
6092 "failed to extract expected value (e) from atomic compare region");
6093 if (!dVal) {
6094 // Fall back to the yield operand.
6095 auto yieldOp = cast<omp::YieldOp>(block.getTerminator());
6096 if (yieldOp.getResults().empty())
6097 return atomicCompareOp.emitError(
6098 "failed to extract desired value (d) from atomic compare region");
6099 dVal = materializeValue(yieldOp.getResults()[0]);
6100 }
6101
6102 llvmAtomicX.IsSigned = isSigned;
6103
6104 llvm::OpenMPIRBuilder::AtomicOpValue vOpVal = {nullptr, nullptr, false,
6105 false};
6106 llvm::OpenMPIRBuilder::AtomicOpValue rOpVal = {nullptr, nullptr, false,
6107 false};
6108 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6109
6110 bool isWeak = atomicCompareOp.getWeak();
6111
6112 bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
6113 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
6114 ompBuilder->createAtomicCompare(ompLoc, llvmAtomicX, vOpVal, rOpVal, eVal,
6115 dVal, atomicOrdering, compareOp,
6116 isXBinopExpr, false, false, isWeak);
6117 ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
6118
6119 if (failed(handleError(afterIP, *atomicCompareOp)))
6120 return failure();
6121
6122 builder.restoreIP(*afterIP);
6123 return success();
6124}
6125
6126static llvm::omp::Directive convertCancellationConstructType(
6127 omp::ClauseCancellationConstructType directive) {
6128 switch (directive) {
6129 case omp::ClauseCancellationConstructType::Loop:
6130 return llvm::omp::Directive::OMPD_for;
6131 case omp::ClauseCancellationConstructType::Parallel:
6132 return llvm::omp::Directive::OMPD_parallel;
6133 case omp::ClauseCancellationConstructType::Sections:
6134 return llvm::omp::Directive::OMPD_sections;
6135 case omp::ClauseCancellationConstructType::Taskgroup:
6136 return llvm::omp::Directive::OMPD_taskgroup;
6137 }
6138 llvm_unreachable("Unhandled cancellation construct type");
6139}
6140
6141static LogicalResult
6142convertOmpCancel(omp::CancelOp op, llvm::IRBuilderBase &builder,
6143 LLVM::ModuleTranslation &moduleTranslation) {
6144 if (failed(checkImplementationStatus(*op.getOperation())))
6145 return failure();
6146
6147 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6148 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
6149
6150 llvm::Value *ifCond = nullptr;
6151 if (Value ifVar = op.getIfExpr())
6152 ifCond = moduleTranslation.lookupValue(ifVar);
6153
6154 llvm::omp::Directive cancelledDirective =
6155 convertCancellationConstructType(op.getCancelDirective());
6156
6157 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
6158 ompBuilder->createCancel(ompLoc, ifCond, cancelledDirective);
6159
6160 if (failed(handleError(afterIP, *op.getOperation())))
6161 return failure();
6162
6163 builder.restoreIP(afterIP.get());
6164
6165 return success();
6166}
6167
6168static LogicalResult
6169convertOmpCancellationPoint(omp::CancellationPointOp op,
6170 llvm::IRBuilderBase &builder,
6171 LLVM::ModuleTranslation &moduleTranslation) {
6172 if (failed(checkImplementationStatus(*op.getOperation())))
6173 return failure();
6174
6175 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6176 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
6177
6178 llvm::omp::Directive cancelledDirective =
6179 convertCancellationConstructType(op.getCancelDirective());
6180
6181 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
6182 ompBuilder->createCancellationPoint(ompLoc, cancelledDirective);
6183
6184 if (failed(handleError(afterIP, *op.getOperation())))
6185 return failure();
6186
6187 builder.restoreIP(afterIP.get());
6188
6189 return success();
6190}
6191
6192/// Converts an OpenMP Threadprivate operation into LLVM IR using
6193/// OpenMPIRBuilder.
6194static LogicalResult
6195convertOmpThreadprivate(Operation &opInst, llvm::IRBuilderBase &builder,
6196 LLVM::ModuleTranslation &moduleTranslation) {
6197 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6198 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
6199 auto threadprivateOp = cast<omp::ThreadprivateOp>(opInst);
6200
6201 if (failed(checkImplementationStatus(opInst)))
6202 return failure();
6203
6204 Value symAddr = threadprivateOp.getSymAddr();
6205 auto *symOp = symAddr.getDefiningOp();
6206
6207 if (auto asCast = dyn_cast<LLVM::AddrSpaceCastOp>(symOp))
6208 symOp = asCast.getOperand().getDefiningOp();
6209
6210 if (!isa<LLVM::AddressOfOp>(symOp))
6211 return opInst.emitError("Addressing symbol not found");
6212 LLVM::AddressOfOp addressOfOp = dyn_cast<LLVM::AddressOfOp>(symOp);
6213
6214 LLVM::GlobalOp global =
6215 addressOfOp.getGlobal(moduleTranslation.symbolTable());
6216 llvm::GlobalValue *globalValue = moduleTranslation.lookupGlobal(global);
6217 llvm::Type *type = globalValue->getValueType();
6218 llvm::TypeSize typeSize =
6219 builder.GetInsertBlock()->getModule()->getDataLayout().getTypeStoreSize(
6220 type);
6221 llvm::ConstantInt *size = builder.getInt64(typeSize.getFixedValue());
6222 llvm::Value *callInst = ompBuilder->createCachedThreadPrivate(
6223 ompLoc, globalValue, size, global.getSymName() + ".cache");
6224 moduleTranslation.mapValue(opInst.getResult(0), callInst);
6225
6226 return success();
6227}
6228
6229static llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseKind
6230convertToDeviceClauseKind(mlir::omp::DeclareTargetDeviceType deviceClause) {
6231 switch (deviceClause) {
6232 case mlir::omp::DeclareTargetDeviceType::host:
6233 return llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseHost;
6234 break;
6235 case mlir::omp::DeclareTargetDeviceType::nohost:
6236 return llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseNoHost;
6237 break;
6238 case mlir::omp::DeclareTargetDeviceType::any:
6239 return llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseAny;
6240 break;
6241 }
6242 llvm_unreachable("unhandled device clause");
6243}
6244
6245static llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryKind
6247 mlir::omp::DeclareTargetCaptureClause captureClause) {
6248 switch (captureClause) {
6249 case mlir::omp::DeclareTargetCaptureClause::to:
6250 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryTo;
6251 case mlir::omp::DeclareTargetCaptureClause::link:
6252 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryLink;
6253 case mlir::omp::DeclareTargetCaptureClause::enter:
6254 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryEnter;
6255 case mlir::omp::DeclareTargetCaptureClause::none:
6256 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryNone;
6257 }
6258 llvm_unreachable("unhandled capture clause");
6259}
6260
6262 Operation *op = value.getDefiningOp();
6263 if (auto addrCast = dyn_cast_if_present<LLVM::AddrSpaceCastOp>(op))
6264 op = addrCast->getOperand(0).getDefiningOp();
6265 if (auto addressOfOp = dyn_cast_if_present<LLVM::AddressOfOp>(op)) {
6266 auto modOp = addressOfOp->getParentOfType<mlir::ModuleOp>();
6267 return modOp.lookupSymbol(addressOfOp.getGlobalName());
6268 }
6269 return nullptr;
6270}
6271
6273 while (Operation *op = value.getDefiningOp()) {
6274 if (auto addrCast = dyn_cast_if_present<LLVM::AddrSpaceCastOp>(op))
6275 value = addrCast.getOperand();
6276 // Traces through hlfir.declare, fir.declare to reach the base address and
6277 // use for type lookup.
6278 else if (op->getName().getIdentifier() &&
6279 (op->getName().getIdentifier().str() == "hlfir.declare" ||
6280 op->getName().getIdentifier().str() == "fir.declare")) {
6281 if (op->getNumOperands() > 0)
6282 value = op->getOperand(0);
6283 else
6284 break;
6285 } else {
6286 break;
6287 }
6288 }
6289 return value;
6290}
6291
6292static llvm::SmallString<64>
6293getDeclareTargetRefPtrSuffix(LLVM::GlobalOp globalOp,
6294 llvm::OpenMPIRBuilder &ompBuilder,
6295 llvm::vfs::FileSystem &vfs) {
6296 llvm::SmallString<64> suffix;
6297 llvm::raw_svector_ostream os(suffix);
6298 if (globalOp.getVisibility() == mlir::SymbolTable::Visibility::Private) {
6299 auto loc = globalOp->getLoc()->findInstanceOf<FileLineColLoc>();
6300 auto fileInfoCallBack = [&loc]() {
6301 return std::pair<std::string, uint64_t>(
6302 llvm::StringRef(loc.getFilename()), loc.getLine());
6303 };
6304
6305 os << llvm::format(
6306 "_%x",
6307 ompBuilder.getTargetEntryUniqueInfo(fileInfoCallBack, vfs).FileID);
6308 }
6309 os << "_decl_tgt_ref_ptr";
6310
6311 return suffix;
6312}
6313
6314static bool isDeclareTargetLink(Value value) {
6315 if (auto declareTargetGlobal =
6316 dyn_cast_if_present<omp::DeclareTargetInterface>(
6317 getGlobalOpFromValue(value)))
6318 if (declareTargetGlobal.getDeclareTargetCaptureClause() ==
6319 omp::DeclareTargetCaptureClause::link)
6320 return true;
6321 return false;
6322}
6323
6324static bool isDeclareTargetTo(Value value) {
6325 if (auto declareTargetGlobal =
6326 dyn_cast_if_present<omp::DeclareTargetInterface>(
6327 getGlobalOpFromValue(value)))
6328 if (declareTargetGlobal.getDeclareTargetCaptureClause() ==
6329 omp::DeclareTargetCaptureClause::to ||
6330 declareTargetGlobal.getDeclareTargetCaptureClause() ==
6331 omp::DeclareTargetCaptureClause::enter)
6332 return true;
6333 return false;
6334}
6335
6336// Returns the reference pointer generated by the lowering of the declare
6337// target operation in cases where the link clause is used or the to clause is
6338// used in USM mode.
6339static llvm::Value *
6341 LLVM::ModuleTranslation &moduleTranslation) {
6342 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
6343 if (auto gOp =
6344 dyn_cast_or_null<LLVM::GlobalOp>(getGlobalOpFromValue(value))) {
6345 // In this case, we must utilise the reference pointer generated by
6346 // the declare target operation, similar to Clang
6347 if (isDeclareTargetLink(value) ||
6348 (isDeclareTargetTo(value) &&
6349 ompBuilder->Config.hasRequiresUnifiedSharedMemory())) {
6351 gOp, *ompBuilder, moduleTranslation.getFileSystem());
6352
6353 if (gOp.getSymName().contains(suffix))
6354 return moduleTranslation.getLLVMModule()->getNamedValue(
6355 gOp.getSymName());
6356
6357 return moduleTranslation.getLLVMModule()->getNamedValue(
6358 (gOp.getSymName().str() + suffix.str()).str());
6359 }
6360 }
6361 return nullptr;
6362}
6363
6364namespace {
6365// Append customMappers information to existing MapInfosTy
6366struct MapInfosTy : llvm::OpenMPIRBuilder::MapInfosTy {
6367 SmallVector<Operation *, 4> Mappers;
6368
6369 /// Append arrays in \a CurInfo.
6370 void append(MapInfosTy &curInfo) {
6371 Mappers.append(curInfo.Mappers.begin(), curInfo.Mappers.end());
6372 llvm::OpenMPIRBuilder::MapInfosTy::append(curInfo);
6373 }
6374};
6375// A small helper structure to contain data gathered
6376// for map lowering and coalese it into one area and
6377// avoiding extra computations such as searches in the
6378// llvm module for lowered mapped variables or checking
6379// if something is declare target (and retrieving the
6380// value) more than neccessary.
6381struct MapInfoData : MapInfosTy {
6382 llvm::SmallVector<bool, 4> IsDeclareTarget;
6383 llvm::SmallVector<bool, 4> IsAMember;
6384 // Identify if mapping was added by mapClause or use_device clauses.
6385 llvm::SmallVector<bool, 4> IsAMapping;
6386 llvm::SmallVector<mlir::Operation *, 4> MapClause;
6387 llvm::SmallVector<llvm::Value *, 4> OriginalValue;
6388 // Stripped off array/pointer to get the underlying
6389 // element type
6390 llvm::SmallVector<llvm::Type *, 4> BaseType;
6391
6392 /// Append arrays in \a CurInfo.
6393 void append(MapInfoData &CurInfo) {
6394 IsDeclareTarget.append(CurInfo.IsDeclareTarget.begin(),
6395 CurInfo.IsDeclareTarget.end());
6396 MapClause.append(CurInfo.MapClause.begin(), CurInfo.MapClause.end());
6397 OriginalValue.append(CurInfo.OriginalValue.begin(),
6398 CurInfo.OriginalValue.end());
6399 BaseType.append(CurInfo.BaseType.begin(), CurInfo.BaseType.end());
6400 MapInfosTy::append(CurInfo);
6401 }
6402};
6403
6404enum class TargetDirectiveEnumTy : uint32_t {
6405 None = 0,
6406 Target = 1,
6407 TargetData = 2,
6408 TargetEnterData = 3,
6409 TargetExitData = 4,
6410 TargetUpdate = 5
6411};
6412
6413static TargetDirectiveEnumTy getTargetDirectiveEnumTyFromOp(Operation *op) {
6414 return llvm::TypeSwitch<Operation *, TargetDirectiveEnumTy>(op)
6415 .Case([](omp::TargetDataOp) { return TargetDirectiveEnumTy::TargetData; })
6416 .Case([](omp::TargetEnterDataOp) {
6417 return TargetDirectiveEnumTy::TargetEnterData;
6418 })
6419 .Case([&](omp::TargetExitDataOp) {
6420 return TargetDirectiveEnumTy::TargetExitData;
6421 })
6422 .Case([&](omp::TargetUpdateOp) {
6423 return TargetDirectiveEnumTy::TargetUpdate;
6424 })
6425 .Case([&](omp::TargetOp) { return TargetDirectiveEnumTy::Target; })
6426 .Default([&](Operation *op) { return TargetDirectiveEnumTy::None; });
6427}
6428
6429} // namespace
6430
6431static uint64_t getArrayElementSizeInBits(LLVM::LLVMArrayType arrTy,
6432 DataLayout &dl) {
6433 if (auto nestedArrTy = llvm::dyn_cast_if_present<LLVM::LLVMArrayType>(
6434 arrTy.getElementType()))
6435 return getArrayElementSizeInBits(nestedArrTy, dl);
6436 return dl.getTypeSizeInBits(arrTy.getElementType());
6437}
6438
6439// The intent is to verify if the mapped data being passed is a
6440// pointer -> pointee that requires special handling in certain cases,
6441// e.g. applying the OMP_MAP_PTR_AND_OBJ map type.
6442//
6443// There may be a better way to verify this, but unfortunately with
6444// opaque pointers we lose the ability to easily check if something is
6445// a pointer whilst maintaining access to the underlying type.
6446static bool checkIfPointerMap(omp::MapInfoOp mapOp) {
6447 // If we have a varPtrPtr field assigned then the underlying type is a pointer
6448 if (mapOp.getVarPtrPtr())
6449 return true;
6450
6451 // If the map data is declare target with a link clause, then it's represented
6452 // as a pointer when we lower it to LLVM-IR even if at the MLIR level it has
6453 // no relation to pointers.
6454 if (isDeclareTargetLink(mapOp.getVarPtr()))
6455 return true;
6456
6457 return false;
6458}
6459
6460// This function calculates the size to be offloaded for a specified type, given
6461// its associated map clause (which can contain bounds information which affects
6462// the total size), this size is calculated based on the underlying element type
6463// e.g. given a 1-D array of ints, we will calculate the size from the integer
6464// type * number of elements in the array. This size can be used in other
6465// calculations but is ultimately used as an argument to the OpenMP runtimes
6466// kernel argument structure which is generated through the combinedInfo data
6467// structures.
6468// This function is somewhat equivalent to Clang's getExprTypeSize inside of
6469// CGOpenMPRuntime.cpp.
6470static llvm::Value *getSizeInBytes(DataLayout &dl, const mlir::Type &type,
6471 Operation *clauseOp,
6472 llvm::Value *basePointer,
6473 llvm::Type *baseType,
6474 llvm::IRBuilderBase &builder,
6475 LLVM::ModuleTranslation &moduleTranslation) {
6476 if (auto memberClause =
6477 mlir::dyn_cast_if_present<mlir::omp::MapInfoOp>(clauseOp)) {
6478 // This calculates the size to transfer based on bounds and the underlying
6479 // element type, provided bounds have been specified (Fortran
6480 // pointers/allocatables/target and arrays that have sections specified fall
6481 // into this as well)
6482 if (!memberClause.getBounds().empty()) {
6483 llvm::Value *elementCount = builder.getInt64(1);
6484 for (auto bounds : memberClause.getBounds()) {
6485 if (auto boundOp = mlir::dyn_cast_if_present<mlir::omp::MapBoundsOp>(
6486 bounds.getDefiningOp())) {
6487 // The below calculation for the size to be mapped calculated from the
6488 // map.info's bounds is: (elemCount * [UB - LB] + 1), later we
6489 // multiply by the underlying element types byte size to get the full
6490 // size to be offloaded based on the bounds
6491 elementCount = builder.CreateMul(
6492 elementCount,
6493 builder.CreateAdd(
6494 builder.CreateSub(
6495 moduleTranslation.lookupValue(boundOp.getUpperBound()),
6496 moduleTranslation.lookupValue(boundOp.getLowerBound())),
6497 builder.getInt64(1)));
6498 }
6499 }
6500
6501 // utilising getTypeSizeInBits instead of getTypeSize as getTypeSize gives
6502 // the size in inconsistent byte or bit format.
6503 uint64_t underlyingTypeSzInBits = dl.getTypeSizeInBits(type);
6504 if (auto arrTy = llvm::dyn_cast_if_present<LLVM::LLVMArrayType>(type))
6505 underlyingTypeSzInBits = getArrayElementSizeInBits(arrTy, dl);
6506
6507 // The size in bytes x number of elements, the sizeInBytes stored is
6508 // the underyling types size, e.g. if ptr<i32>, it'll be the i32's
6509 // size, so we do some on the fly runtime math to get the size in
6510 // bytes from the extent (ub - lb) * sizeInBytes. NOTE: This may need
6511 // some adjustment for members with more complex types.
6512 llvm::Value *sizeCalc = builder.CreateMul(
6513 elementCount, builder.getInt64(underlyingTypeSzInBits / 8),
6514 "element_count");
6515
6516 // This is a part of a "complicated" bit of size calculation logic that is
6517 // in place to handle a couple of scenarios, one specific to Fortran and
6518 // the other a more general OpenMP issue. The other piece of the
6519 // calculation can be found as the final size calculation within the
6520 // processIndividualMap function. Ideally we would move it here, but due
6521 // to the complexity of calculating the final base address of some
6522 // constructs (required for a nullary check), it's left as the final step.
6523 // So, in the below 2 cases, the nullary check is in processIndividualMap
6524 // and the size equality check is here. The cases this modifications help
6525 // cover are:
6526 //
6527 // 1) If an argument has a null base pointer, then the size must be set to
6528 // 0 to avoid the runtime exploding/complaining about an illegal
6529 // pointer map. The size returning non-zero is feasible in certain
6530 // cases if for example someone has specified there own bounds/range.
6531 // 2) We wish to support a very specific OpenMP Fortran edge-case where a
6532 // size zero array can be legally presence checked and found to be on
6533 // device when it has been mapped. In these rare occasions the
6534 // allocatable/pointer will have a size of 1 allocated for the
6535 // underlying data, but this wall not be represented within the size of
6536 // the descriptor, so we get a non-nullary pointer and a size of 0,
6537 // allowing us to specify a size of 1 in these cases registering it on
6538 // the device mapping table as present.
6539 //
6540 // The default fall through case is just returning the size calculation
6541 // above, if we are not nullary and the size we calculate is non-zero,
6542 // which is basically any pointer type that is allocated in someway
6543 // (providing you are not running on a rare system that allows malloc's of
6544 // size 0 with whatever caveats that may come with).
6545 //
6546 // Later in the nullary check in processIndividualMap it just devolves to
6547 // selecting a size of 0 if we are nullary, if we are not, we will return
6548 // either 1 or the calculated size, depending on the outcome of this
6549 // select.
6550 if (checkIfPointerMap(memberClause)) {
6551 return builder.CreateSelect(
6552 builder.CreateICmpEQ(sizeCalc, builder.getInt64(0)),
6553 builder.getInt64(1), sizeCalc);
6554 }
6555
6556 return sizeCalc;
6557 }
6558 }
6559
6560 return builder.getInt64(dl.getTypeSizeInBits(type) / 8);
6561}
6562
6563// Convert the MLIR map flag set to the runtime map flag set for embedding
6564// in LLVM-IR. This is important as the two bit-flag lists do not correspond
6565// 1-to-1 as there's flags the runtime doesn't care about and vice versa.
6566// Certain flags are discarded here such as RefPtee and co.
6567static llvm::omp::OpenMPOffloadMappingFlags
6568convertClauseMapFlags(omp::ClauseMapFlags mlirFlags) {
6569 const bool hasExplicitMap =
6570 (mlirFlags & ~omp::ClauseMapFlags::is_device_ptr) !=
6571 omp::ClauseMapFlags::none;
6572
6573 llvm::omp::OpenMPOffloadMappingFlags mapType =
6574 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE;
6575
6576 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::to))
6577 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TO;
6578
6579 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::from))
6580 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_FROM;
6581
6582 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::always))
6583 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ALWAYS;
6584
6585 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::del))
6586 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_DELETE;
6587
6588 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::return_param))
6589 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
6590
6591 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::priv))
6592 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_PRIVATE;
6593
6594 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::literal))
6595 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL;
6596
6597 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::implicit))
6598 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_IMPLICIT;
6599
6600 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::close))
6601 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_CLOSE;
6602
6603 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::present))
6604 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_PRESENT;
6605
6606 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::ompx_hold))
6607 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_OMPX_HOLD;
6608
6609 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::attach))
6610 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH;
6611
6612 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::is_device_ptr)) {
6613 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TARGET_PARAM;
6614 if (!hasExplicitMap)
6615 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL;
6616 }
6617
6618 return mapType;
6619}
6620
6622 MapInfoData &mapData, SmallVectorImpl<Value> &mapVars,
6623 LLVM::ModuleTranslation &moduleTranslation, DataLayout &dl,
6624 llvm::IRBuilderBase &builder, ArrayRef<Value> useDevPtrOperands = {},
6625 ArrayRef<Value> useDevAddrOperands = {},
6626 ArrayRef<Value> hasDevAddrOperands = {}) {
6627
6628 auto checkRefPtrOrPteeMapWithAttach = [](omp::ClauseMapFlags mapType) {
6629 bool hasRefType =
6630 bitEnumContainsAll(mapType, omp::ClauseMapFlags::ref_ptr) ||
6631 bitEnumContainsAll(mapType, omp::ClauseMapFlags::ref_ptee);
6632 return hasRefType &&
6633 bitEnumContainsAll(mapType, omp::ClauseMapFlags::attach);
6634 };
6635
6636 auto checkIsAMember = [](const auto &mapVars, auto mapOp) {
6637 // Check if this is a member mapping and correctly assign that it is, if
6638 // it is a member of a larger object.
6639 // TODO: Need better handling of members, and distinguishing of members
6640 // that are implicitly allocated on device vs explicitly passed in as
6641 // arguments.
6642 // TODO: May require some further additions to support nested record
6643 // types, i.e. member maps that can have member maps.
6644 for (Value mapValue : mapVars) {
6645 auto map = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
6646 for (auto member : map.getMembers())
6647 if (member == mapOp)
6648 return true;
6649 }
6650 return false;
6651 };
6652
6653 // Process MapOperands
6654 for (Value mapValue : mapVars) {
6655 auto mapOp = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
6656 bool isRefPtrOrPteeMapWithAttach =
6657 checkRefPtrOrPteeMapWithAttach(mapOp.getMapType());
6658 Value offloadPtr = (mapOp.getVarPtrPtr() && !isRefPtrOrPteeMapWithAttach)
6659 ? mapOp.getVarPtrPtr()
6660 : mapOp.getVarPtr();
6661 mapData.OriginalValue.push_back(moduleTranslation.lookupValue(offloadPtr));
6662 mapData.Pointers.push_back(
6663 isRefPtrOrPteeMapWithAttach
6664 ? moduleTranslation.lookupValue(mapOp.getVarPtrPtr())
6665 : mapData.OriginalValue.back());
6666
6667 if (llvm::Value *refPtr =
6668 getRefPtrIfDeclareTarget(offloadPtr, moduleTranslation)) {
6669 mapData.IsDeclareTarget.push_back(true);
6670 mapData.BasePointers.push_back(refPtr);
6671 } else if (isDeclareTargetTo(offloadPtr)) {
6672 mapData.IsDeclareTarget.push_back(true);
6673 mapData.BasePointers.push_back(mapData.OriginalValue.back());
6674 } else { // regular mapped variable
6675 mapData.IsDeclareTarget.push_back(false);
6676 mapData.BasePointers.push_back(mapData.OriginalValue.back());
6677 }
6678
6679 // In every situation we currently have if we have a varPtrPtr present
6680 // we wish to utilise it's type for the base type, main cases are
6681 // currently Fortran descriptor base address maps and attach maps.
6682 mapData.BaseType.push_back(moduleTranslation.convertType(
6683 mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtrType().value()
6684 : mapOp.getVarPtrType()));
6685
6686 // For the attach map cases, it's a little odd, as we effectively have to
6687 // utilise the base address (including all bounds offsets) for the pointer
6688 // field, the pointer address for the base address field, and the pointer
6689 // not the data (base addresses) size. So we end up with a mix of base
6690 // types and sizes we wish to insert here.
6691 mlir::Type sizeType = (isRefPtrOrPteeMapWithAttach || !mapOp.getVarPtrPtr())
6692 ? mapOp.getVarPtrType()
6693 : mapOp.getVarPtrPtrType().value();
6694 mapData.Sizes.push_back(getSizeInBytes(
6695 dl, sizeType, isRefPtrOrPteeMapWithAttach ? nullptr : mapOp,
6696 mapData.Pointers.back(), moduleTranslation.convertType(sizeType),
6697 builder, moduleTranslation));
6698 mapData.MapClause.push_back(mapOp.getOperation());
6699 mapData.Types.push_back(convertClauseMapFlags(mapOp.getMapType()));
6700 mapData.Names.push_back(LLVM::createMappingInformation(
6701 mapOp.getLoc(), *moduleTranslation.getOpenMPBuilder()));
6702 mapData.DevicePointers.push_back(llvm::OpenMPIRBuilder::DeviceInfoTy::None);
6703 if (mapOp.getMapperId())
6704 mapData.Mappers.push_back(
6706 mapOp, mapOp.getMapperIdAttr()));
6707 else
6708 mapData.Mappers.push_back(nullptr);
6709 mapData.IsAMapping.push_back(true);
6710 mapData.IsAMember.push_back(checkIsAMember(mapVars, mapOp));
6711 }
6712
6713 auto findMapInfo = [&mapData](llvm::Value *val,
6714 llvm::OpenMPIRBuilder::DeviceInfoTy devInfoTy,
6715 size_t memberCount) {
6716 unsigned index = 0;
6717 bool found = false;
6718 for (llvm::Value *basePtr : mapData.OriginalValue) {
6719 auto mapOp = cast<omp::MapInfoOp>(mapData.MapClause[index]);
6720 // TODO: Currently we define an equivalent mapping as
6721 // the same base pointer and an equivalent member count, but
6722 // that is a loose definition. We may have to extend to check
6723 // for other fields (varPtrPtr/individual members being mapped).
6724 // Note: Attach maps are not the same as a normal data transfer
6725 // they specify to the runtime to perform an attach map and they
6726 // (at least at the moment) are never something we would aim to
6727 // return in a use_dev_* clause, so they are skipped in terms of
6728 // duplicate maps.
6729 bool isAttachMap =
6730 (mapData.Types[index] &
6731 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) ==
6732 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH;
6733 if (!isAttachMap && basePtr == val && mapData.IsAMapping[index] &&
6734 memberCount == mapOp.getMembers().size()) {
6735 found = true;
6736 mapData.Types[index] |=
6737 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
6738 mapData.DevicePointers[index] = devInfoTy;
6739 }
6740 index++;
6741 }
6742 return found;
6743 };
6744
6745 // Process useDevPtr(Addr)Operands
6746 auto addDevInfos = [&](const llvm::ArrayRef<Value> &useDevOperands,
6747 llvm::OpenMPIRBuilder::DeviceInfoTy devInfoTy) {
6748 for (Value mapValue : useDevOperands) {
6749 auto mapOp = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
6750 Value offloadPtr =
6751 mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtr() : mapOp.getVarPtr();
6752 llvm::Value *origValue = moduleTranslation.lookupValue(offloadPtr);
6753
6754 // Check if map info is already present for this entry.
6755 if (!findMapInfo(origValue, devInfoTy, mapOp.getMembers().size())) {
6756 mapData.OriginalValue.push_back(origValue);
6757 mapData.Pointers.push_back(mapData.OriginalValue.back());
6758 mapData.IsDeclareTarget.push_back(false);
6759 mapData.BasePointers.push_back(mapData.OriginalValue.back());
6760 mlir::Type baseTy = mapOp.getVarPtrPtr()
6761 ? mapOp.getVarPtrPtrType().value()
6762 : mapOp.getVarPtrType();
6763 mapData.BaseType.push_back(moduleTranslation.convertType(baseTy));
6764 mapData.Sizes.push_back(builder.getInt64(0));
6765 mapData.MapClause.push_back(mapOp.getOperation());
6766 mapData.Types.push_back(
6767 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM);
6768 mapData.Names.push_back(LLVM::createMappingInformation(
6769 mapOp.getLoc(), *moduleTranslation.getOpenMPBuilder()));
6770 mapData.DevicePointers.push_back(devInfoTy);
6771 mapData.Mappers.push_back(nullptr);
6772 mapData.IsAMapping.push_back(false);
6773 mapData.IsAMember.push_back(checkIsAMember(useDevOperands, mapOp));
6774 }
6775 }
6776 };
6777
6778 addDevInfos(useDevAddrOperands, llvm::OpenMPIRBuilder::DeviceInfoTy::Address);
6779 addDevInfos(useDevPtrOperands, llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer);
6780
6781 for (Value mapValue : hasDevAddrOperands) {
6782 auto mapOp = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
6783 Value offloadPtr =
6784 mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtr() : mapOp.getVarPtr();
6785 llvm::Value *origValue = moduleTranslation.lookupValue(offloadPtr);
6786 auto mapType = convertClauseMapFlags(mapOp.getMapType());
6787 auto mapTypeAlways = llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ALWAYS;
6788 bool isDevicePtr =
6789 (mapOp.getMapType() & omp::ClauseMapFlags::is_device_ptr) !=
6790 omp::ClauseMapFlags::none;
6791
6792 mapData.OriginalValue.push_back(origValue);
6793 mapData.BasePointers.push_back(origValue);
6794 mapData.Pointers.push_back(origValue);
6795 mapData.IsDeclareTarget.push_back(false);
6796
6797 mlir::Type baseTy = mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtrType().value()
6798 : mapOp.getVarPtrType();
6799 mapData.BaseType.push_back(moduleTranslation.convertType(baseTy));
6800 mapData.Sizes.push_back(builder.getInt64(dl.getTypeSize(baseTy)));
6801
6802 mapData.MapClause.push_back(mapOp.getOperation());
6803 if (llvm::to_underlying(mapType & mapTypeAlways)) {
6804 // Descriptors are mapped with the ALWAYS flag, since they can get
6805 // rematerialized, so the address of the decriptor for a given object
6806 // may change from one place to another.
6807 mapData.Types.push_back(mapType);
6808 // Technically it's possible for a non-descriptor mapping to have
6809 // both has-device-addr and ALWAYS, so lookup the mapper in case it
6810 // exists.
6811 if (mapOp.getMapperId()) {
6812 mapData.Mappers.push_back(
6814 mapOp, mapOp.getMapperIdAttr()));
6815 } else {
6816 mapData.Mappers.push_back(nullptr);
6817 }
6818 } else {
6819 // For is_device_ptr we need the map type to propagate so the runtime
6820 // can materialize the device-side copy of the pointer container.
6821 mapData.Types.push_back(
6822 isDevicePtr ? mapType
6823 : llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL);
6824 mapData.Mappers.push_back(nullptr);
6825 }
6826 mapData.Names.push_back(LLVM::createMappingInformation(
6827 mapOp.getLoc(), *moduleTranslation.getOpenMPBuilder()));
6828 mapData.DevicePointers.push_back(
6829 isDevicePtr ? llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer
6830 : llvm::OpenMPIRBuilder::DeviceInfoTy::Address);
6831 mapData.IsAMapping.push_back(false);
6832 mapData.IsAMember.push_back(checkIsAMember(hasDevAddrOperands, mapOp));
6833 }
6834}
6835
6836static int getMapDataMemberIdx(MapInfoData &mapData, omp::MapInfoOp memberOp) {
6837 auto *res = llvm::find(mapData.MapClause, memberOp);
6838 assert(res != mapData.MapClause.end() &&
6839 "MapInfoOp for member not found in MapData, cannot return index");
6840 return std::distance(mapData.MapClause.begin(), res);
6841}
6842
6844 omp::MapInfoOp mapInfo, bool first = true) {
6845 ArrayAttr indexAttr = mapInfo.getMembersIndexAttr();
6846 llvm::SmallVector<size_t> occludedChildren;
6847 llvm::sort(
6848 indices.begin(), indices.end(), [&](const size_t a, const size_t b) {
6849 // Bail early if we are asked to look at the same index. If we do not
6850 // bail early, we can end up mistakenly adding indices to
6851 // occludedChildren. This can occur with some types of libc++ hardening.
6852 if (a == b)
6853 return false;
6854
6855 auto memberIndicesA = cast<ArrayAttr>(indexAttr[a]);
6856 auto memberIndicesB = cast<ArrayAttr>(indexAttr[b]);
6857
6858 for (auto it : llvm::zip(memberIndicesA, memberIndicesB)) {
6859 int64_t aIndex = mlir::cast<IntegerAttr>(std::get<0>(it)).getInt();
6860 int64_t bIndex = mlir::cast<IntegerAttr>(std::get<1>(it)).getInt();
6861
6862 if (aIndex == bIndex)
6863 continue;
6864
6865 if (aIndex < bIndex)
6866 return first;
6867
6868 if (aIndex > bIndex)
6869 return !first;
6870 }
6871
6872 // Iterated up until the end of the smallest member and
6873 // they were found to be equal up to that point, so select
6874 // the member with the lowest index count, so the "parent"
6875 bool memberAParent = memberIndicesA.size() < memberIndicesB.size();
6876 if (memberAParent)
6877 occludedChildren.push_back(b);
6878 else
6879 occludedChildren.push_back(a);
6880 return memberAParent;
6881 });
6882
6883 for (auto v : occludedChildren)
6884 indices.erase(std::remove(indices.begin(), indices.end(), v),
6885 indices.end());
6886}
6887
6888static omp::MapInfoOp getFirstOrLastMappedMemberPtr(omp::MapInfoOp mapInfo,
6889 bool first) {
6890 ArrayAttr indexAttr = mapInfo.getMembersIndexAttr();
6891 // Only 1 member has been mapped, we can return it.
6892 if (indexAttr.size() == 1)
6893 return cast<omp::MapInfoOp>(mapInfo.getMembers()[0].getDefiningOp());
6894 llvm::SmallVector<size_t> indices(indexAttr.size());
6895 std::iota(indices.begin(), indices.end(), 0);
6896 sortMapIndices(indices, mapInfo, first);
6897 return llvm::cast<omp::MapInfoOp>(
6898 mapInfo.getMembers()[indices.front()].getDefiningOp());
6899}
6900
6901/// This function calculates the array/pointer offset for map data provided
6902/// with bounds operations, e.g. when provided something like the following:
6903///
6904/// Fortran
6905/// map(tofrom: array(2:5, 3:2))
6906///
6907/// We must calculate the initial pointer offset to pass across, this function
6908/// performs this using bounds.
6909///
6910/// TODO/WARNING: This only supports Fortran's column major indexing currently
6911/// as is noted in the note below and comments in the function, we must extend
6912/// this function when we add a C++ frontend.
6913/// NOTE: which while specified in row-major order it currently needs to be
6914/// flipped for Fortran's column order array allocation and access (as
6915/// opposed to C++'s row-major, hence the backwards processing where order is
6916/// important). This is likely important to keep in mind for the future when
6917/// we incorporate a C++ frontend, both frontends will need to agree on the
6918/// ordering of generated bounds operations (one may have to flip them) to
6919/// make the below lowering frontend agnostic. The offload size
6920/// calcualtion may also have to be adjusted for C++.
6921static std::vector<llvm::Value *>
6923 llvm::IRBuilderBase &builder, bool isArrayTy,
6924 OperandRange bounds) {
6925 std::vector<llvm::Value *> idx;
6926 // There's no bounds to calculate an offset from, we can safely
6927 // ignore and return no indices.
6928 if (bounds.empty())
6929 return idx;
6930
6931 // If we have an array type, then we have its type so can treat it as a
6932 // normal GEP instruction where the bounds operations are simply indexes
6933 // into the array. We currently do reverse order of the bounds, which
6934 // I believe leans more towards Fortran's column-major in memory.
6935 if (isArrayTy) {
6936 idx.push_back(builder.getInt64(0));
6937 for (int i = bounds.size() - 1; i >= 0; --i) {
6938 if (auto boundOp = dyn_cast_if_present<omp::MapBoundsOp>(
6939 bounds[i].getDefiningOp())) {
6940 idx.push_back(moduleTranslation.lookupValue(boundOp.getLowerBound()));
6941 }
6942 }
6943 } else {
6944 // If we do not have an array type, but we have bounds, then we're dealing
6945 // with a pointer that's being treated like an array and we have the
6946 // underlying type e.g. an i32, or f64 etc, e.g. a fortran descriptor base
6947 // address (pointer pointing to the actual data) so we must caclulate the
6948 // offset using a single index which the following loop attempts to
6949 // compute using the standard column-major algorithm e.g for a 3D array:
6950 //
6951 // ((((c_idx * b_len) + b_idx) * a_len) + a_idx)
6952 //
6953 // It is of note that it's doing column-major rather than row-major at the
6954 // moment, but having a way for the frontend to indicate which major format
6955 // to use or standardizing/canonicalizing the order of the bounds to compute
6956 // the offset may be useful in the future when there's other frontends with
6957 // different formats.
6958 for (int i = bounds.size() - 1; i >= 0; --i) {
6959 if (auto boundOp = dyn_cast_if_present<omp::MapBoundsOp>(
6960 bounds[i].getDefiningOp())) {
6961 if (i == ((int)bounds.size() - 1))
6962 idx.emplace_back(
6963 moduleTranslation.lookupValue(boundOp.getLowerBound()));
6964 else
6965 idx.back() = builder.CreateAdd(
6966 builder.CreateMul(idx.back(), moduleTranslation.lookupValue(
6967 boundOp.getExtent())),
6968 moduleTranslation.lookupValue(boundOp.getLowerBound()));
6969 }
6970 }
6971 }
6972
6973 return idx;
6974}
6975
6977 llvm::transform(values, std::back_inserter(ints), [](Attribute value) {
6978 return cast<IntegerAttr>(value).getInt();
6979 });
6980}
6981
6982// Gathers members that are overlapping in the parent, excluding members that
6983// themselves overlap, keeping the top-most (closest to parents level) map.
6984static void
6986 omp::MapInfoOp parentOp) {
6987 // No members mapped, no overlaps.
6988 if (parentOp.getMembers().empty())
6989 return;
6990
6991 // Single member, we can insert and return early.
6992 if (parentOp.getMembers().size() == 1) {
6993 overlapMapDataIdxs.push_back(0);
6994 return;
6995 }
6996
6997 ArrayAttr indexAttr = parentOp.getMembersIndexAttr();
6998 size_t numMembers = indexAttr.size();
6999
7000 // Pre-convert all member indices to integer arrays for efficient comparison.
7001 llvm::SmallVector<llvm::SmallVector<int64_t>> memberIndices(numMembers);
7002 for (auto [i, indicesAttr] : llvm::enumerate(indexAttr))
7003 getAsIntegers(cast<ArrayAttr>(indicesAttr), memberIndices[i]);
7004
7005 // For each member, check if it's superseded by another (shorter prefix)
7006 // member. If member j's indices are a prefix of member i's indices, then
7007 // i is a child of j and should be skipped. e.g. if member [0] is mapped,
7008 // we skip members [0,1], [0,2], etc.
7009 llvm::SmallDenseSet<size_t> skipIndices;
7010 for (size_t i = 0; i < numMembers; ++i) {
7011 const auto &iIndices = memberIndices[i];
7012 for (size_t j = 0; j < numMembers; ++j) {
7013 if (i == j)
7014 continue;
7015 const auto &jIndices = memberIndices[j];
7016 // If j's indices are a strict prefix of i's indices, skip i
7017 if (jIndices.size() < iIndices.size() &&
7018 std::equal(jIndices.begin(), jIndices.end(), iIndices.begin())) {
7019 skipIndices.insert(i);
7020 break; // No need to check other potential parents
7021 }
7022 }
7023 }
7024
7025 // Collect indices of members that are not superseded by a parent.
7026 for (size_t i = 0; i < numMembers; ++i)
7027 if (!skipIndices.contains(i))
7028 overlapMapDataIdxs.push_back(i);
7029}
7030
7031/// This function handles the insertion of a single item of map data from
7032/// MapInfoData into the OMPIRBuilder's MapInfo list. Utilising this function
7033/// means the map being inserted can be treated as a non-parent map entity,
7034/// if the memberOfFlag is set then the map being inserted is treated as
7035/// a member map of a larger entity. The insertion into the MapInfo list of
7036/// the OMPIRBuilder can vary based on a number of factors, such as if it's
7037/// a ref_ptr or ref_ptee map, if it's a member of a record, what construct
7038/// the map belongs to and the various map type bit flags that are set for
7039/// the map.
7040static void
7041processIndividualMap(llvm::IRBuilderBase &builder,
7042 llvm::OpenMPIRBuilder &ompBuilder, MapInfoData &mapData,
7043 size_t mapDataIdx, MapInfosTy &combinedInfo,
7044 TargetDirectiveEnumTy targetDirective,
7045 llvm::omp::OpenMPOffloadMappingFlags memberOfFlag =
7046 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE,
7047 bool isTargetParam = true, int mapDataParentIdx = -1) {
7048 auto mapFlag = mapData.Types[mapDataIdx];
7049 auto mapInfoOp = llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIdx]);
7050
7051 bool isPtrTy = checkIfPointerMap(mapInfoOp);
7052 bool isAttachMap = ((convertClauseMapFlags(mapInfoOp.getMapType()) &
7053 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) ==
7054 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH);
7055
7056 // Declare target variables are not passed to the kernel, and for the moment
7057 // attach maps are not passed to the kernel. However, it is possible to create
7058 // attach maps that transfer data and thus can be kernel arguments, but our
7059 // existing frontend does not do this.
7060 if (isTargetParam &&
7061 (targetDirective == TargetDirectiveEnumTy::Target &&
7062 !mapData.IsDeclareTarget[mapDataIdx]) &&
7063 !isAttachMap)
7064 mapFlag |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TARGET_PARAM;
7065
7066 if (mapInfoOp.getMapCaptureType() == omp::VariableCaptureKind::ByCopy &&
7067 !isPtrTy)
7068 mapFlag |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL;
7069
7070 // If we have a pointer and it's part of a MEMBER_OF mapping we do not apply
7071 // MEMBER_OF, as the runtime currently has a work-around that utilises
7072 // MEMBER_OF to prevent reference updating in certain scenarios instead of
7073 // target_param. However, this causes a noticeable issue in cases where we
7074 // map some data (Fortran descriptor primarily at the moment), alter it on
7075 // the host, and then expect it to not be updated in a subsequent implicit map
7076 // (such as an implicit map on a target).
7077 if (memberOfFlag != llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE) {
7078 if (!isPtrTy && !isAttachMap)
7079 ompBuilder.setCorrectMemberOfFlag(mapFlag, memberOfFlag);
7080
7081 // The return parameter should be the over-riding parent in cases where we
7082 // have a return parameter that is echoed to all members, the main case of
7083 // this currently is with fortran descriptors. It may need more finessing
7084 // for C/C++ in the future or descriptors that are members of derived
7085 // types.
7086 mapFlag &= ~llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
7087 }
7088
7089 // We apply MAP_PTR_AND_OBJ when within a declare mapper object as it enforces
7090 // MEMBER_OF mappings on maps that are passed the initial nesting depth, which
7091 // includes pointed to data and attach members, both of which are technically
7092 // not part of the main object. This has the side effect of causing early
7093 // map-backs in certain cases where an implicit declare mapper has been
7094 // emitted for a target region. Applying MAP_PTR_AND_OBJ in these situations
7095 // circumvents this.
7096 if (isPtrTy && !isAttachMap && mapData.IsDeclareTarget[mapDataIdx])
7097 mapFlag |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_PTR_AND_OBJ;
7098
7099 // if we're provided a mapDataParentIdx, then the data being mapped is
7100 // part of a larger object (in a parent <-> member mapping) and in this
7101 // case our BasePointer should be the parent. Except in the edge case
7102 // where we are mapping pointee data, where we try staying close to
7103 // what Clang currently does and utilise the regular base pointer of the
7104 // data.
7105 bool isRefPtee =
7106 !bitEnumContainsAll(mapInfoOp.getMapType(),
7107 omp::ClauseMapFlags::ref_ptr) &&
7108 bitEnumContainsAll(mapInfoOp.getMapType(), omp::ClauseMapFlags::ref_ptee);
7109 bool isRefPtrPtee = bitEnumContainsAll(mapInfoOp.getMapType(),
7110 omp::ClauseMapFlags::ref_ptr |
7111 omp::ClauseMapFlags::ref_ptee);
7112
7113 if (!mapInfoOp->getParentOfType<omp::DeclareMapperOp>() &&
7114 mapDataParentIdx >= 0 && !(isRefPtee || (isRefPtrPtee && isPtrTy))) {
7115 combinedInfo.BasePointers.emplace_back(
7116 mapData.BasePointers[mapDataParentIdx]);
7117 } else {
7118 combinedInfo.BasePointers.emplace_back(mapData.BasePointers[mapDataIdx]);
7119 }
7120
7121 combinedInfo.Pointers.emplace_back(mapData.Pointers[mapDataIdx]);
7122 combinedInfo.DevicePointers.emplace_back(
7123 memberOfFlag != llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE
7124 ? llvm::OpenMPIRBuilder::DeviceInfoTy::None
7125 : mapData.DevicePointers[mapDataIdx]);
7126 combinedInfo.Mappers.emplace_back(mapData.Mappers[mapDataIdx]);
7127 combinedInfo.Names.emplace_back(mapData.Names[mapDataIdx]);
7128 combinedInfo.Types.emplace_back(mapFlag);
7129 combinedInfo.Sizes.emplace_back(
7130 isPtrTy ? builder.CreateSelect(
7131 builder.CreateIsNull(mapData.Pointers[mapDataIdx]),
7132 builder.getInt64(0), mapData.Sizes[mapDataIdx])
7133 : mapData.Sizes[mapDataIdx]);
7134}
7135
7136// This creates two insertions into the MapInfosTy data structure for the
7137// "parent" of a set of members, (usually a container e.g.
7138// class/structure/derived type) when subsequent members have also been
7139// explicitly mapped on the same map clause. Certain types, such as Fortran
7140// descriptors are mapped like this as well, however, the members are
7141// implicit as far as a user is concerned, but we must explicitly map them
7142// internally.
7143//
7144// This function also returns the memberOfFlag for this particular parent,
7145// which is utilised in subsequent member mappings (by modifying there map type
7146// with it) to indicate that a member is part of this parent and should be
7147// treated by the runtime as such. Important to achieve the correct mapping.
7148//
7149// This function borrows a lot from Clang's emitCombinedEntry function
7150// inside of CGOpenMPRuntime.cpp
7152 LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder,
7153 llvm::OpenMPIRBuilder &ompBuilder, DataLayout &dl, MapInfosTy &combinedInfo,
7154 MapInfoData &mapData, uint64_t mapDataIndex,
7155 llvm::omp::OpenMPOffloadMappingFlags memberOfFlag,
7156 TargetDirectiveEnumTy targetDirective) {
7157 using MapFlags = llvm::omp::OpenMPOffloadMappingFlags;
7158 assert(!ompBuilder.Config.isTargetDevice() &&
7159 "function only supported for host device codegen");
7160 auto parentClause =
7161 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
7162 auto *parentMapper = mapData.Mappers[mapDataIndex];
7163
7164 // Map the first segment of the parent. If a user-defined mapper is attached,
7165 // include the parent's to/from-style bits (and common modifiers) in this
7166 // base entry so the mapper receives correct copy semantics via its 'type'
7167 // parameter. Also keep TARGET_PARAM when required for kernel arguments.
7168 MapFlags baseFlag = (targetDirective == TargetDirectiveEnumTy::Target &&
7169 !mapData.IsDeclareTarget[mapDataIndex])
7170 ? MapFlags::OMP_MAP_TARGET_PARAM
7171 : MapFlags::OMP_MAP_NONE;
7172
7173 if (parentMapper) {
7174 // Preserve relevant map-type bits from the parent clause. These include
7175 // the copy direction (TO/FROM), as well as commonly used modifiers that
7176 // should be visible to the mapper for correct behaviour.
7177 MapFlags parentFlags = mapData.Types[mapDataIndex];
7178 MapFlags preserve = MapFlags::OMP_MAP_TO | MapFlags::OMP_MAP_FROM |
7179 MapFlags::OMP_MAP_ALWAYS | MapFlags::OMP_MAP_CLOSE |
7180 MapFlags::OMP_MAP_PRESENT |
7181 MapFlags::OMP_MAP_OMPX_HOLD |
7182 MapFlags::OMP_MAP_IMPLICIT;
7183 baseFlag |= (parentFlags & preserve);
7184 } else {
7185 MapFlags parentFlags = mapData.Types[mapDataIndex];
7186 MapFlags preserve =
7187 MapFlags::OMP_MAP_PRESENT | MapFlags::OMP_MAP_RETURN_PARAM;
7188 baseFlag |= (parentFlags & preserve);
7189 }
7190
7191 combinedInfo.Types.emplace_back(baseFlag);
7192 combinedInfo.DevicePointers.emplace_back(
7193 mapData.DevicePointers[mapDataIndex]);
7194 // Only attach the mapper to the base entry when we are mapping the whole
7195 // parent. Combined/segment entries must not carry a mapper; otherwise the
7196 // mapper can be invoked with a partial size, which is undefined behaviour.
7197 combinedInfo.Mappers.emplace_back(
7198 parentMapper && !parentClause.getPartialMap() ? parentMapper : nullptr);
7199 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
7200 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder));
7201 combinedInfo.BasePointers.emplace_back(mapData.BasePointers[mapDataIndex]);
7202
7203 // Calculate size of the parent object being mapped based on the
7204 // addresses at runtime, highAddr - lowAddr = size. This of course
7205 // doesn't factor in allocated data like pointers, hence the further
7206 // processing of members specified by users, or in the case of
7207 // Fortran pointers and allocatables, the mapping of the pointed to
7208 // data by the descriptor (which itself, is a structure containing
7209 // runtime information on the dynamically allocated data).
7210 llvm::Value *lowAddr, *highAddr;
7211 if (!parentClause.getPartialMap()) {
7212 lowAddr = builder.CreatePointerCast(mapData.Pointers[mapDataIndex],
7213 builder.getPtrTy());
7214 highAddr = builder.CreatePointerCast(
7215 builder.CreateConstGEP1_32(mapData.BaseType[mapDataIndex],
7216 mapData.Pointers[mapDataIndex], 1),
7217 builder.getPtrTy());
7218 combinedInfo.Pointers.emplace_back(mapData.Pointers[mapDataIndex]);
7219 } else {
7220 auto mapOp = dyn_cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
7221 int firstMemberIdx = getMapDataMemberIdx(
7222 mapData, getFirstOrLastMappedMemberPtr(mapOp, true));
7223 lowAddr = builder.CreatePointerCast(mapData.BasePointers[firstMemberIdx],
7224 builder.getPtrTy());
7225
7226 int lastMemberIdx = getMapDataMemberIdx(
7227 mapData, getFirstOrLastMappedMemberPtr(mapOp, false));
7228 auto lastMemberMapInfo =
7229 cast<omp::MapInfoOp>(mapData.MapClause[lastMemberIdx]);
7230
7231 // NOTE: Currently, for RefPtee the BaseType is set to the varPtrPtr field,
7232 // which is the pointer datas type and not the member within the structure
7233 // that it's part of, so we have to make sure we use the member type in this
7234 // case when calculating the parents size offsets.
7235 // TODO: May be good to extend MapInfoData to support tracking of both
7236 // VarPtr/VarPtrPtr BaseType's to better distinguish what's being used more
7237 // consistently.
7238 bool isRefPteeMap = bitEnumContainsAll(lastMemberMapInfo.getMapType(),
7239 omp::ClauseMapFlags::ref_ptee) &&
7240 !bitEnumContainsAll(lastMemberMapInfo.getMapType(),
7241 omp::ClauseMapFlags::ref_ptr);
7242 llvm::Type *castType = mapData.BaseType[lastMemberIdx];
7243 if (isRefPteeMap)
7244 castType =
7245 moduleTranslation.convertType(lastMemberMapInfo.getVarPtrType());
7246 highAddr = builder.CreatePointerCast(
7247 builder.CreateGEP(castType, mapData.BasePointers[lastMemberIdx],
7248 builder.getInt64(1)),
7249 builder.getPtrTy());
7250 combinedInfo.Pointers.emplace_back(mapData.BasePointers[firstMemberIdx]);
7251 }
7252
7253 llvm::Value *size = builder.CreateIntCast(
7254 builder.CreatePtrDiff(builder.getInt8Ty(), highAddr, lowAddr),
7255 builder.getInt64Ty(),
7256 /*isSigned=*/false);
7257 combinedInfo.Sizes.push_back(size);
7258
7259 // This creates the initial MEMBER_OF mapping that consists of
7260 // the parent/top level container (same as above effectively, except
7261 // with a fixed initial compile time size and separate maptype which
7262 // indicates the true mape type (tofrom etc.). This parent mapping is
7263 // only relevant if the structure in its totality is being mapped,
7264 // otherwise the above suffices.
7265 if (!parentClause.getPartialMap()) {
7266 // TODO: This will need to be expanded to include the whole host of logic
7267 // for the map flags that Clang currently supports (e.g. it should do some
7268 // further case specific flag modifications). For the moment, it handles
7269 // what we support as expected.
7270 MapFlags mapFlag = mapData.Types[mapDataIndex];
7271 bool hasMapClose = (MapFlags(mapFlag) & MapFlags::OMP_MAP_CLOSE) ==
7272 MapFlags::OMP_MAP_CLOSE;
7273 ompBuilder.setCorrectMemberOfFlag(mapFlag, memberOfFlag);
7274
7275 llvm::SmallVector<size_t> overlapIdxs;
7276 // Find all of the members that "overlap", i.e. occlude other members that
7277 // were mapped alongside the parent, e.g. member [0], occludes [0,1] and
7278 // [0,2], but not [1,0].
7279 getOverlappedMembers(overlapIdxs, parentClause);
7280
7281 // When we only have one overlap we skip the case that tries to segment the
7282 // mapping as best it can without creating holes, as the calculation is more
7283 // likely to have more overhead than anything we gain from mapping a smaller
7284 // chunk of data. This can be seen in cases where we are mapping Fortran
7285 // descriptors which are a special case of record type mapping.
7286 //
7287 // The cases for close and update are unique edge cases where the segmenting
7288 // does not play well with the runtime currently.
7289 if (targetDirective == TargetDirectiveEnumTy::TargetUpdate || hasMapClose ||
7290 overlapIdxs.size() == 1) {
7291 combinedInfo.Types.emplace_back(mapFlag);
7292 combinedInfo.DevicePointers.emplace_back(
7293 mapData.DevicePointers[mapDataIndex]);
7294 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
7295 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder));
7296 combinedInfo.BasePointers.emplace_back(
7297 mapData.BasePointers[mapDataIndex]);
7298 combinedInfo.Pointers.emplace_back(mapData.Pointers[mapDataIndex]);
7299 combinedInfo.Sizes.emplace_back(mapData.Sizes[mapDataIndex]);
7300 combinedInfo.Mappers.emplace_back(nullptr);
7301 } else {
7302 // We need to make sure the overlapped members are sorted in order of
7303 // lowest address to highest address.
7304 sortMapIndices(overlapIdxs, parentClause);
7305
7306 lowAddr = builder.CreatePointerCast(mapData.Pointers[mapDataIndex],
7307 builder.getPtrTy());
7308 highAddr = builder.CreatePointerCast(
7309 builder.CreateConstGEP1_32(mapData.BaseType[mapDataIndex],
7310 mapData.Pointers[mapDataIndex], 1),
7311 builder.getPtrTy());
7312
7313 // Currently, the return parameter should be the over-riding parent in
7314 // cases where we have a return parameter that is echoed to all members,
7315 // the main case of this currently is with fortran descriptors. It may
7316 // need more finessing for C/C++ in the future or descriptors that are
7317 // members of derived types.
7318 mapFlag &= ~llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
7319
7320 // TODO: We may want to skip arrays/array sections in this as Clang does.
7321 // It appears to be an optimisation rather than a necessity though,
7322 // but this requires further investigation. However, we would have to make
7323 // sure to not exclude maps with bounds that ARE pointers, as these are
7324 // processed as separate components, i.e. pointer + data.
7325 for (auto v : overlapIdxs) {
7326 auto mapDataOverlapIdx = getMapDataMemberIdx(
7327 mapData,
7328 cast<omp::MapInfoOp>(parentClause.getMembers()[v].getDefiningOp()));
7329 auto isPtrMap = checkIfPointerMap(
7330 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataOverlapIdx]));
7331 combinedInfo.Types.emplace_back(mapFlag);
7332 combinedInfo.DevicePointers.emplace_back(
7333 llvm::OpenMPIRBuilder::DeviceInfoTy::None);
7334 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
7335 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder));
7336 combinedInfo.BasePointers.emplace_back(
7337 mapData.BasePointers[mapDataIndex]);
7338 combinedInfo.Mappers.emplace_back(nullptr);
7339 combinedInfo.Pointers.emplace_back(lowAddr);
7340 auto sizeCalc = builder.CreateIntCast(
7341 builder.CreatePtrDiff(builder.getInt8Ty(),
7342 mapData.OriginalValue[mapDataOverlapIdx],
7343 lowAddr),
7344 builder.getInt64Ty(), /*isSigned=*/true);
7345 // In certain cases, we'll generate a size of 0 if we're not careful
7346 // (e.g. if lowAddr happens to be the first member), which isn't
7347 // correct, even if the runtimes is sometimes fine with it so, in these
7348 // scenarios we select the types size instead.
7349 auto sizeSel = builder.CreateSelect(
7350 builder.CreateICmpNE(builder.getInt64(0), sizeCalc), sizeCalc,
7351 isPtrMap ? llvm::ConstantExpr::getSizeOf(builder.getPtrTy())
7352 : mapData.Sizes[mapDataOverlapIdx]);
7353 combinedInfo.Sizes.emplace_back(sizeSel);
7354 lowAddr = builder.CreateConstGEP1_32(
7355 isPtrMap ? builder.getPtrTy() : mapData.BaseType[mapDataOverlapIdx],
7356 mapData.BasePointers[mapDataOverlapIdx], 1);
7357 }
7358
7359 combinedInfo.Types.emplace_back(mapFlag);
7360 combinedInfo.DevicePointers.emplace_back(
7361 llvm::OpenMPIRBuilder::DeviceInfoTy::None);
7362 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
7363 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder));
7364 combinedInfo.BasePointers.emplace_back(
7365 mapData.BasePointers[mapDataIndex]);
7366 combinedInfo.Mappers.emplace_back(nullptr);
7367 combinedInfo.Pointers.emplace_back(lowAddr);
7368 combinedInfo.Sizes.emplace_back(builder.CreateIntCast(
7369 builder.CreatePtrDiff(builder.getInt8Ty(), highAddr, lowAddr),
7370 builder.getInt64Ty(), true));
7371 }
7372 }
7373}
7374
7376 llvm::IRBuilderBase &builder,
7377 llvm::OpenMPIRBuilder &ompBuilder,
7378 DataLayout &dl, MapInfosTy &combinedInfo,
7379 MapInfoData &mapData, uint64_t mapDataIndex,
7380 TargetDirectiveEnumTy targetDirective) {
7381 assert(!ompBuilder.Config.isTargetDevice() &&
7382 "function only supported for host device codegen");
7383
7384 auto parentClause =
7385 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
7386
7387 // If we have a partial map (no parent referenced in the map clauses of the
7388 // directive, only members) and only a single member, we do not need to bind
7389 // the map of the member to the parent, we can pass the member separately.
7390 if (parentClause.getMembers().size() == 1 && parentClause.getPartialMap()) {
7391 auto memberClause = llvm::cast<omp::MapInfoOp>(
7392 parentClause.getMembers()[0].getDefiningOp());
7393 int memberDataIdx = getMapDataMemberIdx(mapData, memberClause);
7394 // Note: Clang treats arrays with explicit bounds that fall into this
7395 // category as a parent with map case, however, it seems this isn't a
7396 // requirement, and processing them as an individual map is fine. So,
7397 // we will handle them as individual maps for the moment, as it's
7398 // difficult for us to check this as we always require bounds to be
7399 // specified currently and it's also marginally more optimal (single
7400 // map rather than two). The difference may come from the fact that
7401 // Clang maps array without bounds as pointers (which we do not
7402 // currently do), whereas we treat them as arrays in all cases
7403 // currently.
7405 builder, ompBuilder, mapData, memberDataIdx, combinedInfo,
7406 targetDirective,
7407 /*MemberOfFlag=*/llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE,
7408 /*isTargetParam=*/true, mapDataIndex);
7409 return;
7410 }
7411
7412 auto collectMapInfoIdxs =
7413 [&](llvm::SmallVectorImpl<int64_t> &mapsAndInfoIdx) {
7414 auto parentClause =
7415 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
7416 mapsAndInfoIdx.push_back(getMapDataMemberIdx(mapData, parentClause));
7417 for (auto member : parentClause.getMembers())
7418 mapsAndInfoIdx.push_back(getMapDataMemberIdx(
7419 mapData, llvm::cast<omp::MapInfoOp>(member.getDefiningOp())));
7420 };
7421
7422 llvm::SmallVector<int64_t> mapInfoIdx;
7423 collectMapInfoIdxs(mapInfoIdx);
7424
7425 llvm::omp::OpenMPOffloadMappingFlags memberOfFlag =
7426 ompBuilder.getMemberOfFlag(combinedInfo.Types.size());
7427 for (size_t i = 0; i < mapInfoIdx.size(); i++) {
7428 // Index == 0 is the parent map and if it gets here it's an unattachable
7429 // type and should have OMP_MAP_TARGET_PARAM applied and no MEMBER_OF flag.
7430 if (i == 0) {
7431 mapParentWithMembers(moduleTranslation, builder, ompBuilder, dl,
7432 combinedInfo, mapData, mapInfoIdx[i], memberOfFlag,
7433 targetDirective);
7434 } else {
7435 processIndividualMap(builder, ompBuilder, mapData, mapInfoIdx[i],
7436 combinedInfo, targetDirective, memberOfFlag,
7437 /*isTargetParam=*/false, mapDataIndex);
7438 }
7439 }
7440}
7441
7442// This is a variation on Clang's GenerateOpenMPCapturedVars, which
7443// generates different operation (e.g. load/store) combinations for
7444// arguments to the kernel, based on map capture kinds which are then
7445// utilised in the combinedInfo in place of the original Map value.
7446static void
7447createAlteredByCaptureMap(MapInfoData &mapData,
7448 LLVM::ModuleTranslation &moduleTranslation,
7449 llvm::IRBuilderBase &builder) {
7450 assert(!moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
7451 "function only supported for host device codegen");
7452 for (size_t i = 0; i < mapData.MapClause.size(); ++i) {
7453 auto mapOp = cast<omp::MapInfoOp>(mapData.MapClause[i]);
7454 bool isAttachMap =
7455 ((convertClauseMapFlags(mapOp.getMapType()) &
7456 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) ==
7457 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH);
7458
7459 // If it's declare target, skip it, it's handled separately. However, if
7460 // it's declare target, and an attach map, we want to calculate the exact
7461 // address offset so that we attach correctly.
7462 if (!mapData.IsDeclareTarget[i] ||
7463 (mapData.IsDeclareTarget[i] && isAttachMap)) {
7464 omp::VariableCaptureKind captureKind = mapOp.getMapCaptureType();
7465 bool isPtrTy = checkIfPointerMap(mapOp);
7466
7467 // Currently handles array sectioning lowerbound case, but more
7468 // logic may be required in the future. Clang invokes EmitLValue,
7469 // which has specialised logic for special Clang types such as user
7470 // defines, so it is possible we will have to extend this for
7471 // structures or other complex types. As the general idea is that this
7472 // function mimics some of the logic from Clang that we require for
7473 // kernel argument passing from host -> device.
7474 switch (captureKind) {
7475 case omp::VariableCaptureKind::ByRef: {
7476 llvm::Value *newV = mapData.Pointers[i];
7477 std::vector<llvm::Value *> offsetIdx = calculateBoundsOffset(
7478 moduleTranslation, builder, mapData.BaseType[i]->isArrayTy(),
7479 mapOp.getBounds());
7480 if (isPtrTy)
7481 newV = builder.CreateLoad(builder.getPtrTy(), newV);
7482
7483 if (!offsetIdx.empty())
7484 newV = builder.CreateInBoundsGEP(mapData.BaseType[i], newV, offsetIdx,
7485 "array_offset");
7486 mapData.Pointers[i] = newV;
7487 } break;
7488 case omp::VariableCaptureKind::ByCopy: {
7489 llvm::Type *type = mapData.BaseType[i];
7490 llvm::Value *newV;
7491 if (mapData.Pointers[i]->getType()->isPointerTy())
7492 newV = builder.CreateLoad(type, mapData.Pointers[i]);
7493 else
7494 newV = mapData.Pointers[i];
7495
7496 if (!isPtrTy) {
7497 auto curInsert = builder.saveIP();
7498 llvm::DebugLoc DbgLoc = builder.getCurrentDebugLocation();
7499 builder.restoreIP(findAllocInsertPoints(builder, moduleTranslation));
7500 auto *memTempAlloc =
7501 builder.CreateAlloca(builder.getPtrTy(), nullptr, ".casted");
7502 builder.SetCurrentDebugLocation(DbgLoc);
7503 builder.restoreIP(curInsert);
7504
7505 builder.CreateStore(newV, memTempAlloc);
7506 newV = builder.CreateLoad(builder.getPtrTy(), memTempAlloc);
7507 }
7508
7509 mapData.Pointers[i] = newV;
7510 mapData.BasePointers[i] = newV;
7511 } break;
7512 case omp::VariableCaptureKind::This:
7513 case omp::VariableCaptureKind::VLAType:
7514 mapData.MapClause[i]->emitOpError("Unhandled capture kind");
7515 break;
7516 }
7517 }
7518 }
7519}
7520
7521// Generate all map related information and fill the combinedInfo.
7522static void genMapInfos(llvm::IRBuilderBase &builder,
7523 LLVM::ModuleTranslation &moduleTranslation,
7524 DataLayout &dl, MapInfosTy &combinedInfo,
7525 MapInfoData &mapData,
7526 TargetDirectiveEnumTy targetDirective) {
7527 assert(!moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
7528 "function only supported for host device codegen");
7529 // We wish to modify some of the methods in which arguments are
7530 // passed based on their capture type by the target region, this can
7531 // involve generating new loads and stores, which changes the
7532 // MLIR value to LLVM value mapping, however, we only wish to do this
7533 // locally for the current function/target and also avoid altering
7534 // ModuleTranslation, so we remap the base pointer or pointer stored
7535 // in the map infos corresponding MapInfoData, which is later accessed
7536 // by genMapInfos and createTarget to help generate the kernel and
7537 // kernel arg structure. It primarily becomes relevant in cases like
7538 // bycopy, or byref range'd arrays. In the default case, we simply
7539 // pass thee pointer byref as both basePointer and pointer.
7540 createAlteredByCaptureMap(mapData, moduleTranslation, builder);
7541
7542 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
7543
7544 // We operate under the assumption that all vectors that are
7545 // required in MapInfoData are of equal lengths (either filled with
7546 // default constructed data or appropiate information) so we can
7547 // utilise the size from any component of MapInfoData, if we can't
7548 // something is missing from the initial MapInfoData construction.
7549 for (size_t i = 0; i < mapData.MapClause.size(); ++i) {
7550 if (mapData.IsAMember[i])
7551 continue;
7552
7553 auto mapInfoOp = dyn_cast<omp::MapInfoOp>(mapData.MapClause[i]);
7554 if (!mapInfoOp.getMembers().empty()) {
7555 processMapWithMembersOf(moduleTranslation, builder, *ompBuilder, dl,
7556 combinedInfo, mapData, i, targetDirective);
7557 continue;
7558 }
7559
7560 processIndividualMap(builder, *ompBuilder, mapData, i, combinedInfo,
7561 targetDirective);
7562 }
7563}
7564
7565static llvm::Expected<llvm::Function *>
7566emitUserDefinedMapper(Operation *declMapperOp, llvm::IRBuilderBase &builder,
7567 LLVM::ModuleTranslation &moduleTranslation,
7568 llvm::StringRef mapperFuncName,
7569 TargetDirectiveEnumTy targetDirective);
7570
7571static llvm::Expected<llvm::Function *>
7572getOrCreateUserDefinedMapperFunc(Operation *op, llvm::IRBuilderBase &builder,
7573 LLVM::ModuleTranslation &moduleTranslation,
7574 TargetDirectiveEnumTy targetDirective) {
7575 assert(!moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
7576 "function only supported for host device codegen");
7577 auto declMapperOp = cast<omp::DeclareMapperOp>(op);
7578 std::string mapperFuncName =
7579 moduleTranslation.getOpenMPBuilder()->createPlatformSpecificName(
7580 {"omp_mapper", declMapperOp.getSymName()});
7581
7582 if (auto *lookupFunc = moduleTranslation.lookupFunction(mapperFuncName))
7583 return lookupFunc;
7584
7585 // Recursive types can cause re-entrant mapper emission. The mapper function
7586 // is created by OpenMPIRBuilder before the callbacks run, so it may already
7587 // exist in the LLVM module even though it is not yet registered in the
7588 // ModuleTranslation mapping table. Reuse and register it to break the
7589 // recursion.
7590 if (llvm::Function *existingFunc =
7591 moduleTranslation.getLLVMModule()->getFunction(mapperFuncName)) {
7592 moduleTranslation.mapFunction(mapperFuncName, existingFunc);
7593 return existingFunc;
7594 }
7595
7596 return emitUserDefinedMapper(declMapperOp, builder, moduleTranslation,
7597 mapperFuncName, targetDirective);
7598}
7599
7600static llvm::Expected<llvm::Function *>
7601emitUserDefinedMapper(Operation *op, llvm::IRBuilderBase &builder,
7602 LLVM::ModuleTranslation &moduleTranslation,
7603 llvm::StringRef mapperFuncName,
7604 TargetDirectiveEnumTy targetDirective) {
7605 assert(!moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
7606 "function only supported for host device codegen");
7607 auto declMapperOp = cast<omp::DeclareMapperOp>(op);
7608 auto declMapperInfoOp = declMapperOp.getDeclareMapperInfo();
7609 if (failed(checkImplementationStatus(*declMapperInfoOp)))
7610 return llvm::make_error<PreviouslyReportedError>();
7611
7612 DataLayout dl = DataLayout(declMapperOp->getParentOfType<ModuleOp>());
7613 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
7614 llvm::Type *varType = moduleTranslation.convertType(declMapperOp.getType());
7615 SmallVector<Value> mapVars = declMapperInfoOp.getMapVars();
7616
7617 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
7618
7619 // Fill up the arrays with all the mapped variables.
7620 MapInfosTy combinedInfo;
7621 auto genMapInfoCB =
7622 [&](InsertPointTy codeGenIP, llvm::Value *ptrPHI,
7623 llvm::Value *unused2) -> llvm::OpenMPIRBuilder::MapInfosOrErrorTy {
7624 builder.restoreIP(codeGenIP);
7625 moduleTranslation.mapValue(declMapperOp.getSymVal(), ptrPHI);
7626 moduleTranslation.mapBlock(&declMapperOp.getRegion().front(),
7627 builder.GetInsertBlock());
7628 if (failed(moduleTranslation.convertBlock(declMapperOp.getRegion().front(),
7629 /*ignoreArguments=*/true,
7630 builder)))
7631 return llvm::make_error<PreviouslyReportedError>();
7632 MapInfoData mapData;
7633 collectMapDataFromMapOperands(mapData, mapVars, moduleTranslation, dl,
7634 builder);
7635 genMapInfos(builder, moduleTranslation, dl, combinedInfo, mapData,
7636 targetDirective);
7637
7638 // Drop the mapping that is no longer necessary so that the same region
7639 // can be processed multiple times.
7640 moduleTranslation.forgetMapping(declMapperOp.getRegion());
7641 return combinedInfo;
7642 };
7643
7644 auto customMapperCB = [&](unsigned i) -> llvm::Expected<llvm::Function *> {
7645 if (!combinedInfo.Mappers[i])
7646 return nullptr;
7647 return getOrCreateUserDefinedMapperFunc(combinedInfo.Mappers[i], builder,
7648 moduleTranslation, targetDirective);
7649 };
7650
7651 llvm::Expected<llvm::Function *> newFn = ompBuilder->emitUserDefinedMapper(
7652 genMapInfoCB, varType, mapperFuncName, customMapperCB,
7653 /*PreserveMemberOfFlags=*/true);
7654 if (!newFn)
7655 return newFn.takeError();
7656 if ([[maybe_unused]] llvm::Function *mappedFunc =
7657 moduleTranslation.lookupFunction(mapperFuncName)) {
7658 assert(mappedFunc == *newFn &&
7659 "mapper function mapping disagrees with emitted function");
7660 } else {
7661 moduleTranslation.mapFunction(mapperFuncName, *newFn);
7662 }
7663 return *newFn;
7664}
7665
7666static LogicalResult
7667convertOmpTargetData(Operation *op, llvm::IRBuilderBase &builder,
7668 LLVM::ModuleTranslation &moduleTranslation) {
7669 llvm::Value *ifCond = nullptr;
7670 llvm::Value *deviceID = builder.getInt64(llvm::omp::OMP_DEVICEID_UNDEF);
7671 SmallVector<Value> mapVars;
7672 SmallVector<Value> useDevicePtrVars;
7673 SmallVector<Value> useDeviceAddrVars;
7674 llvm::omp::RuntimeFunction RTLFn;
7675 DataLayout DL = DataLayout(op->getParentOfType<ModuleOp>());
7676 TargetDirectiveEnumTy targetDirective = getTargetDirectiveEnumTyFromOp(op);
7677
7678 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
7679 llvm::OpenMPIRBuilder::TargetDataInfo info(
7680 /*RequiresDevicePointerInfo=*/true,
7681 /*SeparateBeginEndCalls=*/true);
7682 assert(!ompBuilder->Config.isTargetDevice() &&
7683 "target data/enter/exit/update are host ops");
7684 bool isOffloadEntry = !ompBuilder->Config.TargetTriples.empty();
7685
7686 auto getDeviceID = [&](mlir::Value dev) -> llvm::Value * {
7687 llvm::Value *v = moduleTranslation.lookupValue(dev);
7688 return builder.CreateIntCast(v, builder.getInt64Ty(), /*isSigned=*/true);
7689 };
7690
7691 LogicalResult result =
7693 .Case([&](omp::TargetDataOp dataOp) {
7694 if (failed(checkImplementationStatus(*dataOp)))
7695 return failure();
7696
7697 if (auto ifVar = dataOp.getIfExpr())
7698 ifCond = moduleTranslation.lookupValue(ifVar);
7699
7700 if (mlir::Value devId = dataOp.getDevice())
7701 deviceID = getDeviceID(devId);
7702
7703 mapVars = dataOp.getMapVars();
7704 useDevicePtrVars = dataOp.getUseDevicePtrVars();
7705 useDeviceAddrVars = dataOp.getUseDeviceAddrVars();
7706 return success();
7707 })
7708 .Case([&](omp::TargetEnterDataOp enterDataOp) -> LogicalResult {
7709 if (failed(checkImplementationStatus(*enterDataOp)))
7710 return failure();
7711
7712 if (auto ifVar = enterDataOp.getIfExpr())
7713 ifCond = moduleTranslation.lookupValue(ifVar);
7714
7715 if (mlir::Value devId = enterDataOp.getDevice())
7716 deviceID = getDeviceID(devId);
7717
7718 RTLFn =
7719 enterDataOp.getNowait()
7720 ? llvm::omp::OMPRTL___tgt_target_data_begin_nowait_mapper
7721 : llvm::omp::OMPRTL___tgt_target_data_begin_mapper;
7722 mapVars = enterDataOp.getMapVars();
7723 info.HasNoWait = enterDataOp.getNowait();
7724 return success();
7725 })
7726 .Case([&](omp::TargetExitDataOp exitDataOp) -> LogicalResult {
7727 if (failed(checkImplementationStatus(*exitDataOp)))
7728 return failure();
7729
7730 if (auto ifVar = exitDataOp.getIfExpr())
7731 ifCond = moduleTranslation.lookupValue(ifVar);
7732
7733 if (mlir::Value devId = exitDataOp.getDevice())
7734 deviceID = getDeviceID(devId);
7735
7736 RTLFn = exitDataOp.getNowait()
7737 ? llvm::omp::OMPRTL___tgt_target_data_end_nowait_mapper
7738 : llvm::omp::OMPRTL___tgt_target_data_end_mapper;
7739 mapVars = exitDataOp.getMapVars();
7740 info.HasNoWait = exitDataOp.getNowait();
7741 return success();
7742 })
7743 .Case([&](omp::TargetUpdateOp updateDataOp) -> LogicalResult {
7744 if (failed(checkImplementationStatus(*updateDataOp)))
7745 return failure();
7746
7747 if (auto ifVar = updateDataOp.getIfExpr())
7748 ifCond = moduleTranslation.lookupValue(ifVar);
7749
7750 if (mlir::Value devId = updateDataOp.getDevice())
7751 deviceID = getDeviceID(devId);
7752
7753 RTLFn =
7754 updateDataOp.getNowait()
7755 ? llvm::omp::OMPRTL___tgt_target_data_update_nowait_mapper
7756 : llvm::omp::OMPRTL___tgt_target_data_update_mapper;
7757 mapVars = updateDataOp.getMapVars();
7758 info.HasNoWait = updateDataOp.getNowait();
7759 return success();
7760 })
7761 .DefaultUnreachable("unexpected operation");
7762
7763 if (failed(result))
7764 return failure();
7765 // Pretend we have IF(false) if we're not doing offload.
7766 if (!isOffloadEntry)
7767 ifCond = builder.getFalse();
7768
7769 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
7770 MapInfoData mapData;
7771 collectMapDataFromMapOperands(mapData, mapVars, moduleTranslation, DL,
7772 builder, useDevicePtrVars, useDeviceAddrVars);
7773
7774 // Fill up the arrays with all the mapped variables.
7775 MapInfosTy combinedInfo;
7776 auto genMapInfoCB = [&](InsertPointTy codeGenIP) -> MapInfosTy & {
7777 builder.restoreIP(codeGenIP);
7778 genMapInfos(builder, moduleTranslation, DL, combinedInfo, mapData,
7779 targetDirective);
7780 return combinedInfo;
7781 };
7782
7783 // Define a lambda to apply mappings between use_device_addr and
7784 // use_device_ptr base pointers, and their associated block arguments.
7785 auto mapUseDevice =
7786 [&moduleTranslation](
7787 llvm::OpenMPIRBuilder::DeviceInfoTy type,
7789 llvm::SmallVectorImpl<Value> &useDeviceVars, MapInfoData &mapInfoData,
7790 llvm::function_ref<llvm::Value *(llvm::Value *)> mapper = nullptr) {
7791 for (auto [arg, useDevVar] :
7792 llvm::zip_equal(blockArgs, useDeviceVars)) {
7793
7794 auto getMapBasePtr = [](omp::MapInfoOp mapInfoOp) {
7795 return mapInfoOp.getVarPtrPtr() ? mapInfoOp.getVarPtrPtr()
7796 : mapInfoOp.getVarPtr();
7797 };
7798
7799 auto useDevMap = cast<omp::MapInfoOp>(useDevVar.getDefiningOp());
7800 for (auto [mapClause, devicePointer, basePointer] : llvm::zip_equal(
7801 mapInfoData.MapClause, mapInfoData.DevicePointers,
7802 mapInfoData.BasePointers)) {
7803 auto mapOp = cast<omp::MapInfoOp>(mapClause);
7804 if (getMapBasePtr(mapOp) != getMapBasePtr(useDevMap) ||
7805 devicePointer != type)
7806 continue;
7807
7808 if (llvm::Value *devPtrInfoMap =
7809 mapper ? mapper(basePointer) : basePointer) {
7810 moduleTranslation.mapValue(arg, devPtrInfoMap);
7811 break;
7812 }
7813 }
7814 }
7815 };
7816
7817 using BodyGenTy = llvm::OpenMPIRBuilder::BodyGenTy;
7818 auto bodyGenCB = [&](InsertPointTy codeGenIP, BodyGenTy bodyGenType)
7819 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
7820 // We must always restoreIP regardless of doing anything the caller
7821 // does not restore it, leading to incorrect (no) branch generation.
7822 builder.restoreIP(codeGenIP);
7823 assert(isa<omp::TargetDataOp>(op) &&
7824 "BodyGen requested for non TargetDataOp");
7825 auto blockArgIface = cast<omp::BlockArgOpenMPOpInterface>(op);
7826 Region &region = cast<omp::TargetDataOp>(op).getRegion();
7827 switch (bodyGenType) {
7828 case BodyGenTy::Priv:
7829 // Check if any device ptr/addr info is available
7830 if (!info.DevicePtrInfoMap.empty()) {
7831 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Address,
7832 blockArgIface.getUseDeviceAddrBlockArgs(),
7833 useDeviceAddrVars, mapData,
7834 [&](llvm::Value *basePointer) -> llvm::Value * {
7835 if (!info.DevicePtrInfoMap[basePointer].second)
7836 return nullptr;
7837 return builder.CreateLoad(
7838 builder.getPtrTy(),
7839 info.DevicePtrInfoMap[basePointer].second);
7840 });
7841 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer,
7842 blockArgIface.getUseDevicePtrBlockArgs(), useDevicePtrVars,
7843 mapData, [&](llvm::Value *basePointer) {
7844 return info.DevicePtrInfoMap[basePointer].second;
7845 });
7846
7847 if (failed(inlineConvertOmpRegions(region, "omp.data.region", builder,
7848 moduleTranslation)))
7849 return llvm::make_error<PreviouslyReportedError>();
7850 }
7851 break;
7852 case BodyGenTy::DupNoPriv:
7853 if (info.DevicePtrInfoMap.empty()) {
7854 // For host device we still need to do the mapping for codegen,
7855 // otherwise it may try to lookup a missing value.
7856 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Address,
7857 blockArgIface.getUseDeviceAddrBlockArgs(),
7858 useDeviceAddrVars, mapData);
7859 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer,
7860 blockArgIface.getUseDevicePtrBlockArgs(), useDevicePtrVars,
7861 mapData);
7862 }
7863 break;
7864 case BodyGenTy::NoPriv:
7865 // If device info is available then region has already been generated
7866 if (info.DevicePtrInfoMap.empty()) {
7867 if (failed(inlineConvertOmpRegions(region, "omp.data.region", builder,
7868 moduleTranslation)))
7869 return llvm::make_error<PreviouslyReportedError>();
7870 }
7871 break;
7872 }
7873 return builder.saveIP();
7874 };
7875
7876 auto customMapperCB =
7877 [&](unsigned int i) -> llvm::Expected<llvm::Function *> {
7878 if (!combinedInfo.Mappers[i])
7879 return nullptr;
7880 info.HasMapper = true;
7881 return getOrCreateUserDefinedMapperFunc(combinedInfo.Mappers[i], builder,
7882 moduleTranslation, targetDirective);
7883 };
7884
7885 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
7887 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
7888 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
7889 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP = [&]() {
7890 if (isa<omp::TargetDataOp>(op))
7891 return ompBuilder->createTargetData(ompLoc, allocaIP, builder.saveIP(),
7892 deallocBlocks, deviceID, ifCond, info,
7893 genMapInfoCB, customMapperCB,
7894 /*MapperFunc=*/nullptr, bodyGenCB,
7895 /*DeviceAddrCB=*/nullptr);
7896 return ompBuilder->createTargetData(ompLoc, allocaIP, builder.saveIP(),
7897 deallocBlocks, deviceID, ifCond, info,
7898 genMapInfoCB, customMapperCB, &RTLFn);
7899 }();
7900
7901 if (failed(handleError(afterIP, *op)))
7902 return failure();
7903
7904 builder.restoreIP(*afterIP);
7905 return success();
7906}
7907
7908static LogicalResult
7909convertOmpDistribute(Operation &opInst, llvm::IRBuilderBase &builder,
7910 LLVM::ModuleTranslation &moduleTranslation) {
7911 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
7912 auto distributeOp = cast<omp::DistributeOp>(opInst);
7913 if (failed(checkImplementationStatus(opInst)))
7914 return failure();
7915
7916 /// Process teams op reduction in distribute if the reduction is contained in
7917 /// this specific distribute op.
7918 omp::TeamsOp teamsOp = opInst.getParentOfType<omp::TeamsOp>();
7919 bool doDistributeReduction =
7920 teamsOp && getDistributeCapturingTeamsReduction(teamsOp) == distributeOp;
7921
7922 DenseMap<Value, llvm::Value *> reductionVariableMap;
7923 unsigned numReductionVars = teamsOp ? teamsOp.getNumReductionVars() : 0;
7925 SmallVector<llvm::Value *> privateReductionVariables(numReductionVars);
7926 llvm::ArrayRef<bool> isByRef;
7927
7928 if (doDistributeReduction) {
7929 isByRef = getIsByRef(teamsOp.getReductionByref());
7930 assert(isByRef.size() == teamsOp.getNumReductionVars());
7931
7932 collectReductionDecls(teamsOp, reductionDecls);
7933 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
7934 findAllocInsertPoints(builder, moduleTranslation);
7935
7936 MutableArrayRef<BlockArgument> reductionArgs =
7937 llvm::cast<omp::BlockArgOpenMPOpInterface>(*teamsOp)
7938 .getReductionBlockArgs();
7939
7941 teamsOp, reductionArgs, builder, moduleTranslation, allocaIP,
7942 reductionDecls, privateReductionVariables, reductionVariableMap,
7943 isByRef)))
7944 return failure();
7945 }
7946
7947 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
7948 auto bodyGenCB =
7949 [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
7950 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
7951 // Save the alloca insertion point on ModuleTranslation stack for use in
7952 // nested regions.
7954 moduleTranslation, allocaIP, deallocBlocks);
7955
7956 // DistributeOp has only one region associated with it.
7957 builder.restoreIP(codeGenIP);
7958 PrivateVarsInfo privVarsInfo(distributeOp);
7959
7961 distributeOp, builder, moduleTranslation, privVarsInfo, allocaIP);
7962 if (handleError(afterAllocas, opInst).failed())
7963 return llvm::make_error<PreviouslyReportedError>();
7964
7965 if (handleError(initPrivateVars(builder, moduleTranslation, privVarsInfo),
7966 opInst)
7967 .failed())
7968 return llvm::make_error<PreviouslyReportedError>();
7969
7970 if (failed(copyFirstPrivateVars(
7971 distributeOp, builder, moduleTranslation, privVarsInfo.mlirVars,
7972 privVarsInfo.llvmVars, privVarsInfo.privatizers,
7973 distributeOp.getPrivateNeedsBarrier())))
7974 return llvm::make_error<PreviouslyReportedError>();
7975
7976 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
7977 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
7979 convertOmpOpRegions(distributeOp.getRegion(), "omp.distribute.region",
7980 builder, moduleTranslation);
7981 if (!regionBlock)
7982 return regionBlock.takeError();
7983 builder.SetInsertPoint(*regionBlock, (*regionBlock)->begin());
7984
7985 // Skip applying a workshare loop below when translating 'distribute
7986 // parallel do' (it's been already handled by this point while translating
7987 // the nested omp.wsloop).
7988 if (!isa_and_present<omp::WsloopOp>(distributeOp.getNestedWrapper())) {
7989 // TODO: Add support for clauses which are valid for DISTRIBUTE
7990 // constructs. Static schedule is the default.
7991 bool hasDistSchedule = distributeOp.getDistScheduleStatic();
7992 auto schedule = hasDistSchedule ? omp::ClauseScheduleKind::Distribute
7993 : omp::ClauseScheduleKind::Static;
7994 // dist_schedule clauses are ordered - otherise this should be false
7995 bool isOrdered = hasDistSchedule;
7996 std::optional<omp::ScheduleModifier> scheduleMod;
7997 bool isSimd = false;
7998 llvm::omp::WorksharingLoopType workshareLoopType =
7999 llvm::omp::WorksharingLoopType::DistributeStaticLoop;
8000 bool loopNeedsBarrier = false;
8001 llvm::Value *chunk = moduleTranslation.lookupValue(
8002 distributeOp.getDistScheduleChunkSize());
8003 llvm::CanonicalLoopInfo *loopInfo =
8004 findCurrentLoopInfo(moduleTranslation);
8005 llvm::OpenMPIRBuilder::InsertPointOrErrorTy wsloopIP =
8006 ompBuilder->applyWorkshareLoop(
8007 ompLoc.DL, loopInfo, allocaIP, loopNeedsBarrier,
8008 convertToScheduleKind(schedule), chunk, isSimd,
8009 scheduleMod == omp::ScheduleModifier::monotonic,
8010 scheduleMod == omp::ScheduleModifier::nonmonotonic, isOrdered,
8011 workshareLoopType, false, hasDistSchedule, chunk);
8012
8013 if (!wsloopIP)
8014 return wsloopIP.takeError();
8015 }
8016 if (failed(cleanupPrivateVars(distributeOp, builder, moduleTranslation,
8017 distributeOp.getLoc(), privVarsInfo)))
8018 return llvm::make_error<PreviouslyReportedError>();
8019
8020 return llvm::Error::success();
8021 };
8022
8024 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
8025 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
8026 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
8027 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
8028 ompBuilder->createDistribute(ompLoc, allocaIP, deallocBlocks, bodyGenCB);
8029
8030 if (failed(handleError(afterIP, opInst)))
8031 return failure();
8032
8033 builder.restoreIP(*afterIP);
8034
8035 if (doDistributeReduction) {
8036 // Process the reductions if required.
8038 teamsOp, builder, moduleTranslation, allocaIP, reductionDecls,
8039 privateReductionVariables, isByRef,
8040 /*isNoWait*/ false, /*isTeamsReduction*/ true);
8041 }
8042 return success();
8043}
8044
8045/// Lowers the FlagsAttr which is applied to the module when offloading. This
8046/// attribute contains OpenMP RTL globals that can be passed as flags to the
8047/// frontend, otherwise they are set to default
8048static LogicalResult
8049convertFlagsAttr(Operation *op, mlir::omp::FlagsAttr attribute,
8050 LLVM::ModuleTranslation &moduleTranslation) {
8051 auto offloadMod = dyn_cast<omp::OffloadModuleInterface>(op);
8052 if (!offloadMod)
8053 return op->emitOpError() << "omp flags attached to non offload module op";
8054
8055 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8056
8057 if (offloadMod.getIsTargetDevice())
8058 ompBuilder->M.addModuleFlag(llvm::Module::Max, "openmp-device",
8059 attribute.getOpenmpDeviceVersion());
8060
8061 // The flags below are only intended to be emitted for GPU offload targets.
8062 if (!offloadMod.getIsGPU())
8063 return success();
8064
8065 if (attribute.getNoGpuLib())
8066 return success();
8067
8068 ompBuilder->createGlobalFlag(
8069 attribute.getDebugKind() /*LangOpts().OpenMPTargetDebug*/,
8070 "__omp_rtl_debug_kind");
8071 ompBuilder->createGlobalFlag(
8072 attribute
8073 .getAssumeTeamsOversubscription() /*LangOpts().OpenMPTeamSubscription*/
8074 ,
8075 "__omp_rtl_assume_teams_oversubscription");
8076 ompBuilder->createGlobalFlag(
8077 attribute
8078 .getAssumeThreadsOversubscription() /*LangOpts().OpenMPThreadSubscription*/
8079 ,
8080 "__omp_rtl_assume_threads_oversubscription");
8081 ompBuilder->createGlobalFlag(
8082 attribute.getAssumeNoThreadState() /*LangOpts().OpenMPNoThreadState*/,
8083 "__omp_rtl_assume_no_thread_state");
8084 ompBuilder->createGlobalFlag(
8085 attribute
8086 .getAssumeNoNestedParallelism() /*LangOpts().OpenMPNoNestedParallelism*/
8087 ,
8088 "__omp_rtl_assume_no_nested_parallelism");
8089 return success();
8090}
8091
8092static void getTargetEntryUniqueInfo(llvm::TargetRegionEntryInfo &targetInfo,
8093 omp::TargetOp targetOp,
8094 llvm::OpenMPIRBuilder &ompBuilder,
8095 llvm::vfs::FileSystem &vfs,
8096 llvm::StringRef parentName = "") {
8097 auto fileLoc = targetOp.getLoc()->findInstanceOf<FileLineColLoc>();
8098 assert(fileLoc && "No file found from location");
8099
8100 auto fileInfoCallBack = [&fileLoc]() {
8101 return std::pair<std::string, uint64_t>(
8102 llvm::StringRef(fileLoc.getFilename()), fileLoc.getLine());
8103 };
8104
8105 targetInfo =
8106 ompBuilder.getTargetEntryUniqueInfo(fileInfoCallBack, vfs, parentName);
8107}
8108
8109static void
8110handleDeclareTargetMapVar(MapInfoData &mapData,
8111 LLVM::ModuleTranslation &moduleTranslation,
8112 llvm::IRBuilderBase &builder, llvm::Function *func) {
8113 assert(moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
8114 "function only supported for target device codegen");
8115 llvm::IRBuilderBase::InsertPointGuard guard(builder);
8116 for (size_t i = 0; i < mapData.MapClause.size(); ++i) {
8117 // In the case of declare target mapped variables, the basePointer is
8118 // the reference pointer generated by the convertDeclareTargetAttr
8119 // method. Whereas the kernelValue is the original variable, so for
8120 // the device we must replace all uses of this original global variable
8121 // (stored in kernelValue) with the reference pointer (stored in
8122 // basePointer for declare target mapped variables), as for device the
8123 // data is mapped into this reference pointer and should be loaded
8124 // from it, the original variable is discarded. On host both exist and
8125 // metadata is generated (elsewhere in the convertDeclareTargetAttr)
8126 // function to link the two variables in the runtime and then both the
8127 // reference pointer and the pointer are assigned in the kernel argument
8128 // structure for the host.
8129 if (!mapData.IsDeclareTarget[i])
8130 continue;
8131 // If the original map value is a constant, then we have to make sure all
8132 // of it's uses within the current kernel/function that we are going to
8133 // rewrite are converted to instructions, as we will be altering the old
8134 // use (OriginalValue) from a constant to an instruction, which will be
8135 // illegal and ICE the compiler if the user is a constant expression of
8136 // some kind e.g. a constant GEP.
8137 if (auto *constant = dyn_cast<llvm::Constant>(mapData.OriginalValue[i]))
8138 convertUsersOfConstantsToInstructions(constant, func, false);
8139
8140 // The users iterator will get invalidated if we modify an element,
8141 // so we populate this vector of uses to alter each user on an
8142 // individual basis to emit its own load (rather than one load for
8143 // all).
8145 for (llvm::User *user : mapData.OriginalValue[i]->users())
8146 userVec.push_back(user);
8147
8148 for (llvm::User *user : userVec) {
8149 auto *insn = dyn_cast<llvm::Instruction>(user);
8150 if (!insn || insn->getFunction() != func)
8151 continue;
8152 auto mapOp = cast<omp::MapInfoOp>(mapData.MapClause[i]);
8153 llvm::Value *substitute = mapData.BasePointers[i];
8154 auto declTarPtr =
8155 mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtr() : mapOp.getVarPtr();
8156 if (isDeclareTargetLink(declTarPtr) ||
8157 (isDeclareTargetTo(declTarPtr) &&
8158 moduleTranslation.getOpenMPBuilder()
8159 ->Config.hasRequiresUnifiedSharedMemory())) {
8160 builder.SetCurrentDebugLocation(insn->getDebugLoc());
8161 substitute = builder.CreateLoad(mapData.BasePointers[i]->getType(),
8162 mapData.BasePointers[i]);
8163 cast<llvm::LoadInst>(substitute)->moveBefore(insn->getIterator());
8164 }
8165 user->replaceUsesOfWith(mapData.OriginalValue[i], substitute);
8166 }
8167 }
8168}
8169
8170// The createDeviceArgumentAccessor function generates
8171// instructions for retrieving (acessing) kernel
8172// arguments inside of the device kernel for use by
8173// the kernel. This enables different semantics such as
8174// the creation of temporary copies of data allowing
8175// semantics like read-only/no host write back kernel
8176// arguments.
8177//
8178// This currently implements a very light version of Clang's
8179// EmitParmDecl's handling of direct argument handling as well
8180// as a portion of the argument access generation based on
8181// capture types found at the end of emitOutlinedFunctionPrologue
8182// in Clang. The indirect path handling of EmitParmDecl's may be
8183// required for future work, but a direct 1-to-1 copy doesn't seem
8184// possible as the logic is rather scattered throughout Clang's
8185// lowering and perhaps we wish to deviate slightly.
8186//
8187// \param mapData - A container containing vectors of information
8188// corresponding to the input argument, which should have a
8189// corresponding entry in the MapInfoData containers
8190// OrigialValue's.
8191// \param arg - This is the generated kernel function argument that
8192// corresponds to the passed in input argument. We generated different
8193// accesses of this Argument, based on capture type and other Input
8194// related information.
8195// \param input - This is the host side value that will be passed to
8196// the kernel i.e. the kernel input, we rewrite all uses of this within
8197// the kernel (as we generate the kernel body based on the target's region
8198// which maintians references to the original input) to the retVal argument
8199// apon exit of this function inside of the OMPIRBuilder. This interlinks
8200// the kernel argument to future uses of it in the function providing
8201// appropriate "glue" instructions inbetween.
8202// \param retVal - This is the value that all uses of input inside of the
8203// kernel will be re-written to, the goal of this function is to generate
8204// an appropriate location for the kernel argument to be accessed from,
8205// e.g. ByRef will result in a temporary allocation location and then
8206// a store of the kernel argument into this allocated memory which
8207// will then be loaded from, ByCopy will use the allocated memory
8208// directly.
8209static llvm::IRBuilderBase::InsertPoint createDeviceArgumentAccessor(
8210 omp::TargetOp targetOp, MapInfoData &mapData, llvm::Argument &arg,
8211 llvm::Value *input, llvm::Value *&retVal, llvm::IRBuilderBase &builder,
8212 llvm::OpenMPIRBuilder &ompBuilder,
8213 LLVM::ModuleTranslation &moduleTranslation,
8214 llvm::IRBuilderBase::InsertPoint allocaIP,
8215 llvm::IRBuilderBase::InsertPoint codeGenIP,
8217 assert(ompBuilder.Config.isTargetDevice() &&
8218 "function only supported for target device codegen");
8219 builder.restoreIP(allocaIP);
8220
8221 omp::VariableCaptureKind capture = omp::VariableCaptureKind::ByRef;
8222 LLVM::TypeToLLVMIRTranslator typeToLLVMIRTranslator(
8223 ompBuilder.M.getContext());
8224 unsigned alignmentValue = 0;
8225 BlockArgument mlirArg;
8227 cast<omp::BlockArgOpenMPOpInterface>(*targetOp).getBlockArgsPairs(
8228 blockArgsPairs);
8229 // Find the associated MapInfoData entry for the current input
8230 for (size_t i = 0; i < mapData.MapClause.size(); ++i) {
8231 if (mapData.OriginalValue[i] == input) {
8232 auto mapOp = cast<omp::MapInfoOp>(mapData.MapClause[i]);
8233 capture = mapOp.getMapCaptureType();
8234 // Get information of alignment of mapped object
8235 alignmentValue = typeToLLVMIRTranslator.getPreferredAlignment(
8236 mapOp.getVarPtrType(), ompBuilder.M.getDataLayout());
8237
8238 // Find the corresponding entry block argument, which can be associated to
8239 // a map, use_device* or has_device* clause.
8240 for (auto &[val, arg] : blockArgsPairs) {
8241 if (mapOp.getResult() == val) {
8242 mlirArg = arg;
8243 break;
8244 }
8245 }
8246 assert(mlirArg && "expected to find entry block argument for map clause");
8247 break;
8248 }
8249 }
8250
8251 unsigned int allocaAS = ompBuilder.M.getDataLayout().getAllocaAddrSpace();
8252 unsigned int defaultAS =
8253 ompBuilder.M.getDataLayout().getProgramAddressSpace();
8254
8255 // Create the allocation for the argument.
8256 llvm::Value *v = nullptr;
8257 if (omp::opInSharedDeviceContext(*targetOp) &&
8259 // Use the beginning of the codeGenIP rather than the usual allocation point
8260 // for shared memory allocations because otherwise these would be done prior
8261 // to the target initialization call. Also, the exit block (where the
8262 // deallocation is placed) is only executed if the initialization call
8263 // succeeds.
8264 builder.SetInsertPoint(codeGenIP.getBlock()->getFirstInsertionPt());
8265 v = ompBuilder.createOMPAllocShared(builder, arg.getType());
8266
8267 // Create deallocations in all provided deallocation points and then restore
8268 // the insertion point to right after the new allocations.
8269 llvm::IRBuilderBase::InsertPointGuard guard(builder);
8270 for (auto deallocIP : deallocIPs) {
8271 builder.SetInsertPoint(deallocIP.getBlock(), deallocIP.getPoint());
8272 ompBuilder.createOMPFreeShared(builder, v, arg.getType());
8273 }
8274 } else {
8275 // Use the current point, which was previously set to allocaIP.
8276 v = builder.CreateAlloca(arg.getType(), allocaAS);
8277
8278 if (allocaAS != defaultAS && arg.getType()->isPointerTy())
8279 v = builder.CreateAddrSpaceCast(v, builder.getPtrTy(defaultAS));
8280 }
8281
8282 builder.CreateStore(&arg, v);
8283
8284 builder.restoreIP(codeGenIP);
8285
8286 switch (capture) {
8287 case omp::VariableCaptureKind::ByCopy: {
8288 retVal = v;
8289 break;
8290 }
8291 case omp::VariableCaptureKind::ByRef: {
8292 llvm::LoadInst *loadInst = builder.CreateAlignedLoad(
8293 v->getType(), v,
8294 ompBuilder.M.getDataLayout().getPrefTypeAlign(v->getType()));
8295 // CreateAlignedLoad function creates similar LLVM IR:
8296 // %res = load ptr, ptr %input, align 8
8297 // This LLVM IR does not contain information about alignment
8298 // of the loaded value. We need to add !align metadata to unblock
8299 // optimizer. The existence of the !align metadata on the instruction
8300 // tells the optimizer that the value loaded is known to be aligned to
8301 // a boundary specified by the integer value in the metadata node.
8302 // Example:
8303 // %res = load ptr, ptr %input, align 8, !align !align_md_node
8304 // ^ ^
8305 // | |
8306 // alignment of %input address |
8307 // |
8308 // alignment of %res object
8309 if (v->getType()->isPointerTy() && alignmentValue) {
8310 llvm::MDBuilder MDB(builder.getContext());
8311 loadInst->setMetadata(
8312 llvm::LLVMContext::MD_align,
8313 llvm::MDNode::get(builder.getContext(),
8314 MDB.createConstant(llvm::ConstantInt::get(
8315 llvm::Type::getInt64Ty(builder.getContext()),
8316 alignmentValue))));
8317 }
8318 retVal = loadInst;
8319
8320 break;
8321 }
8322 case omp::VariableCaptureKind::This:
8323 case omp::VariableCaptureKind::VLAType:
8324 // TODO: Consider returning error to use standard reporting for
8325 // unimplemented features.
8326 assert(false && "Currently unsupported capture kind");
8327 break;
8328 }
8329
8330 return builder.saveIP();
8331}
8332
8333/// Follow uses of `host_eval`-defined block arguments of the given `omp.target`
8334/// operation and populate output variables with their corresponding host value
8335/// (i.e. operand evaluated outside of the target region), based on their uses
8336/// inside of the target region.
8337///
8338/// Loop bounds and steps are only optionally populated, if output vectors are
8339/// provided.
8340static void
8341extractHostEvalClauses(omp::TargetOp targetOp, Value &numThreads,
8342 Value &numTeamsLower, Value &numTeamsUpper,
8343 Value &threadLimit,
8344 llvm::SmallVectorImpl<Value> *lowerBounds = nullptr,
8345 llvm::SmallVectorImpl<Value> *upperBounds = nullptr,
8346 llvm::SmallVectorImpl<Value> *steps = nullptr) {
8347 auto blockArgIface = llvm::cast<omp::BlockArgOpenMPOpInterface>(*targetOp);
8348 for (auto item : llvm::zip_equal(targetOp.getHostEvalVars(),
8349 blockArgIface.getHostEvalBlockArgs())) {
8350 Value hostEvalVar = std::get<0>(item), blockArg = std::get<1>(item);
8351
8352 for (Operation *user : blockArg.getUsers()) {
8354 .Case([&](omp::TeamsOp teamsOp) {
8355 if (teamsOp.getNumTeamsLower() == blockArg)
8356 numTeamsLower = hostEvalVar;
8357 else if (llvm::is_contained(teamsOp.getNumTeamsUpperVars(),
8358 blockArg))
8359 numTeamsUpper = hostEvalVar;
8360 else if (!teamsOp.getThreadLimitVars().empty() &&
8361 teamsOp.getThreadLimit(0) == blockArg)
8362 threadLimit = hostEvalVar;
8363 else
8364 llvm_unreachable("unsupported host_eval use");
8365 })
8366 .Case([&](omp::ParallelOp parallelOp) {
8367 if (!parallelOp.getNumThreadsVars().empty() &&
8368 parallelOp.getNumThreads(0) == blockArg)
8369 numThreads = hostEvalVar;
8370 else
8371 llvm_unreachable("unsupported host_eval use");
8372 })
8373 .Case([&](omp::LoopNestOp loopOp) {
8374 auto processBounds =
8375 [&](OperandRange opBounds,
8376 llvm::SmallVectorImpl<Value> *outBounds) -> bool {
8377 bool found = false;
8378 for (auto [i, lb] : llvm::enumerate(opBounds)) {
8379 if (lb == blockArg) {
8380 found = true;
8381 if (outBounds)
8382 (*outBounds)[i] = hostEvalVar;
8383 }
8384 }
8385 return found;
8386 };
8387 bool found =
8388 processBounds(loopOp.getLoopLowerBounds(), lowerBounds);
8389 found = processBounds(loopOp.getLoopUpperBounds(), upperBounds) ||
8390 found;
8391 found = processBounds(loopOp.getLoopSteps(), steps) || found;
8392 (void)found;
8393 assert(found && "unsupported host_eval use");
8394 })
8395 .DefaultUnreachable("unsupported host_eval use");
8396 }
8397 }
8398}
8399
8400/// If \p op is of the given type parameter, return it casted to that type.
8401/// Otherwise, if its immediate parent operation (or some other higher-level
8402/// parent, if \p immediateParent is false) is of that type, return that parent
8403/// casted to the given type.
8404///
8405/// If \p op is \c null or neither it or its parent(s) are of the specified
8406/// type, return a \c null operation.
8407template <typename OpTy>
8408static OpTy castOrGetParentOfType(Operation *op, bool immediateParent = false) {
8409 if (!op)
8410 return OpTy();
8411
8412 if (OpTy casted = dyn_cast<OpTy>(op))
8413 return casted;
8414
8415 if (immediateParent)
8416 return dyn_cast_if_present<OpTy>(op->getParentOp());
8417
8418 return op->getParentOfType<OpTy>();
8419}
8420
8421/// If the given \p value is defined by an \c llvm.mlir.constant operation and
8422/// it is of an integer type, return its value.
8423static std::optional<int64_t> extractConstInteger(Value value) {
8424 if (!value)
8425 return std::nullopt;
8426
8427 if (auto constOp = value.getDefiningOp<LLVM::ConstantOp>())
8428 if (auto constAttr = dyn_cast<IntegerAttr>(constOp.getValue()))
8429 return constAttr.getInt();
8430
8431 return std::nullopt;
8432}
8433
8434static uint64_t getTypeByteSize(mlir::Type type, const DataLayout &dl) {
8435 uint64_t sizeInBits = dl.getTypeSizeInBits(type);
8436 uint64_t sizeInBytes = sizeInBits / 8;
8437 return sizeInBytes;
8438}
8439
8440template <typename OpTy>
8441static uint64_t getReductionDataSize(OpTy &op) {
8442 if (op.getNumReductionVars() > 0) {
8444 collectReductionDecls(op, reductions);
8445
8447 members.reserve(reductions.size());
8448 for (omp::DeclareReductionOp &red : reductions) {
8449 // For by-ref reductions, use the actual element type rather than the
8450 // pointer type so that the buffer size matches the access pattern in
8451 // the copy/reduce callbacks generated by OMPIRBuilder.
8452 if (red.getByrefElementType())
8453 members.push_back(*red.getByrefElementType());
8454 else
8455 members.push_back(red.getType());
8456 }
8457 Operation *opp = op.getOperation();
8458 auto structType = mlir::LLVM::LLVMStructType::getLiteral(
8459 opp->getContext(), members, /*isPacked=*/false);
8460 DataLayout dl = DataLayout(opp->getParentOfType<ModuleOp>());
8461 return getTypeByteSize(structType, dl);
8462 }
8463 return 0;
8464}
8465
8466/// Populate default `MinTeams`, `MaxTeams` and `MaxThreads` to their default
8467/// values as stated by the corresponding clauses, if constant.
8468///
8469/// These default values must be set before the creation of the outlined LLVM
8470/// function for the target region, so that they can be used to initialize the
8471/// corresponding global `ConfigurationEnvironmentTy` structure.
8472static void
8473initTargetDefaultAttrs(omp::TargetOp targetOp, Operation *capturedOp,
8474 llvm::OpenMPIRBuilder::TargetKernelDefaultAttrs &attrs,
8475 bool isTargetDevice, bool isGPU) {
8476 // TODO: Handle constant 'if' clauses.
8477
8478 Value numThreads, numTeamsLower, numTeamsUpper, threadLimit;
8479 if (!isTargetDevice) {
8480 extractHostEvalClauses(targetOp, numThreads, numTeamsLower, numTeamsUpper,
8481 threadLimit);
8482 } else {
8483 // In the target device, values for these clauses are not passed as
8484 // host_eval, but instead evaluated prior to entry to the region. This
8485 // ensures values are mapped and available inside of the target region.
8486 if (auto teamsOp = castOrGetParentOfType<omp::TeamsOp>(capturedOp)) {
8487 numTeamsLower = teamsOp.getNumTeamsLower();
8488 // Handle num_teams upper bounds (only first value for now)
8489 if (!teamsOp.getNumTeamsUpperVars().empty())
8490 numTeamsUpper = teamsOp.getNumTeams(0);
8491 if (!teamsOp.getThreadLimitVars().empty())
8492 threadLimit = teamsOp.getThreadLimit(0);
8493 }
8494
8495 if (auto parallelOp = castOrGetParentOfType<omp::ParallelOp>(capturedOp)) {
8496 if (!parallelOp.getNumThreadsVars().empty())
8497 numThreads = parallelOp.getNumThreads(0);
8498 }
8499 }
8500
8501 // Handle clauses impacting the number of teams.
8502
8503 int32_t minTeamsVal = 1, maxTeamsVal = -1;
8504 if (castOrGetParentOfType<omp::TeamsOp>(capturedOp)) {
8505 // TODO: Use `hostNumTeamsLower` to initialize `minTeamsVal`. For now,
8506 // match clang and set min and max to the same value.
8507 if (numTeamsUpper) {
8508 if (auto val = extractConstInteger(numTeamsUpper))
8509 minTeamsVal = maxTeamsVal = *val;
8510 } else {
8511 minTeamsVal = maxTeamsVal = 0;
8512 }
8513 } else if (castOrGetParentOfType<omp::ParallelOp>(capturedOp,
8514 /*immediateParent=*/true) ||
8516 /*immediateParent=*/true)) {
8517 minTeamsVal = maxTeamsVal = 1;
8518 } else {
8519 minTeamsVal = maxTeamsVal = -1;
8520 }
8521
8522 // Handle clauses impacting the number of threads.
8523
8524 auto setMaxValueFromClause = [](Value clauseValue, int32_t &result) {
8525 if (!clauseValue)
8526 return;
8527
8528 if (auto val = extractConstInteger(clauseValue))
8529 result = *val;
8530
8531 // Found an applicable clause, so it's not undefined. Mark as unknown
8532 // because it's not constant.
8533 if (result < 0)
8534 result = 0;
8535 };
8536
8537 // Extract 'thread_limit' clause from 'target' and 'teams' directives.
8538 int32_t targetThreadLimitVal = -1, teamsThreadLimitVal = -1;
8539 if (!targetOp.getThreadLimitVars().empty())
8540 setMaxValueFromClause(targetOp.getThreadLimit(0), targetThreadLimitVal);
8541 setMaxValueFromClause(threadLimit, teamsThreadLimitVal);
8542
8543 // Extract 'max_threads' clause from 'parallel' or set to 1 if it's SIMD.
8544 int32_t maxThreadsVal = -1;
8546 setMaxValueFromClause(numThreads, maxThreadsVal);
8547 else if (castOrGetParentOfType<omp::SimdOp>(capturedOp,
8548 /*immediateParent=*/true))
8549 maxThreadsVal = 1;
8550
8551 // For max values, < 0 means unset, == 0 means set but unknown. Select the
8552 // minimum value between 'max_threads' and 'thread_limit' clauses that were
8553 // set.
8554 int32_t combinedMaxThreadsVal = targetThreadLimitVal;
8555 if (combinedMaxThreadsVal < 0 ||
8556 (teamsThreadLimitVal >= 0 && teamsThreadLimitVal < combinedMaxThreadsVal))
8557 combinedMaxThreadsVal = teamsThreadLimitVal;
8558
8559 if (combinedMaxThreadsVal < 0 ||
8560 (maxThreadsVal >= 0 && maxThreadsVal < combinedMaxThreadsVal))
8561 combinedMaxThreadsVal = maxThreadsVal;
8562
8563 int32_t reductionDataSize = 0;
8564 if (isGPU && capturedOp) {
8565 if (auto teamsOp = castOrGetParentOfType<omp::TeamsOp>(capturedOp))
8566 reductionDataSize = getReductionDataSize(teamsOp);
8567 }
8568
8569 // Update kernel bounds structure for the `OpenMPIRBuilder` to use.
8570 // Use the kernel_type attribute set by the frontend instead of analyzing IR.
8571 omp::TargetExecMode execMode = targetOp.getKernelType();
8572 switch (execMode) {
8573 case omp::TargetExecMode::bare:
8574 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_BARE;
8575 break;
8576 case omp::TargetExecMode::generic:
8577 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_GENERIC;
8578 break;
8579 case omp::TargetExecMode::spmd:
8580 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_SPMD;
8581 break;
8582 case omp::TargetExecMode::spmd_no_loop:
8583 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_SPMD_NO_LOOP;
8584 break;
8585 }
8586 attrs.MinTeams = minTeamsVal;
8587 attrs.MaxTeams.front() = maxTeamsVal;
8588 attrs.MinThreads = 1;
8589 attrs.MaxThreads.front() = combinedMaxThreadsVal;
8590 attrs.ReductionDataSize = reductionDataSize;
8591}
8592
8593/// Gather LLVM runtime values for all clauses evaluated in the host that are
8594/// passed to the kernel invocation.
8595///
8596/// This function must be called only when compiling for the host. Also, it will
8597/// only provide correct results if it's called after the body of \c targetOp
8598/// has been fully generated.
8599static void
8600initTargetRuntimeAttrs(llvm::IRBuilderBase &builder,
8601 LLVM::ModuleTranslation &moduleTranslation,
8602 omp::TargetOp targetOp, Operation *capturedOp,
8603 llvm::OpenMPIRBuilder::TargetKernelRuntimeAttrs &attrs) {
8604 omp::LoopNestOp loopOp = castOrGetParentOfType<omp::LoopNestOp>(capturedOp);
8605 unsigned numLoops = loopOp ? loopOp.getNumLoops() : 0;
8606
8607 Value numThreads, numTeamsLower, numTeamsUpper, teamsThreadLimit;
8608 llvm::SmallVector<Value> lowerBounds(numLoops), upperBounds(numLoops),
8609 steps(numLoops);
8610 extractHostEvalClauses(targetOp, numThreads, numTeamsLower, numTeamsUpper,
8611 teamsThreadLimit, &lowerBounds, &upperBounds, &steps);
8612
8613 // TODO: Handle constant 'if' clauses.
8614 if (!targetOp.getThreadLimitVars().empty()) {
8615 Value targetThreadLimit = targetOp.getThreadLimit(0);
8616 attrs.TargetThreadLimit.front() =
8617 moduleTranslation.lookupValue(targetThreadLimit);
8618 }
8619
8620 // The __kmpc_push_num_teams_51 function expects int32 as the arguments. So,
8621 // truncate or sign extend lower and upper num_teams bounds as well as
8622 // thread_limit to match int32 ABI requirements for the OpenMP runtime.
8623 if (numTeamsLower)
8624 attrs.MinTeams = builder.CreateSExtOrTrunc(
8625 moduleTranslation.lookupValue(numTeamsLower), builder.getInt32Ty());
8626
8627 if (numTeamsUpper)
8628 attrs.MaxTeams.front() = builder.CreateSExtOrTrunc(
8629 moduleTranslation.lookupValue(numTeamsUpper), builder.getInt32Ty());
8630
8631 if (teamsThreadLimit)
8632 attrs.TeamsThreadLimit.front() = builder.CreateSExtOrTrunc(
8633 moduleTranslation.lookupValue(teamsThreadLimit), builder.getInt32Ty());
8634
8635 if (numThreads)
8636 attrs.MaxThreads = moduleTranslation.lookupValue(numThreads);
8637
8638 if (targetOp.hasHostEvalTripCount()) {
8639 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8640 attrs.LoopTripCount = nullptr;
8641
8642 // To calculate the trip count, we multiply together the trip counts of
8643 // every collapsed canonical loop. We don't need to create the loop nests
8644 // here, since we're only interested in the trip count.
8645 for (auto [loopLower, loopUpper, loopStep] :
8646 llvm::zip_equal(lowerBounds, upperBounds, steps)) {
8647 llvm::Value *lowerBound = moduleTranslation.lookupValue(loopLower);
8648 llvm::Value *upperBound = moduleTranslation.lookupValue(loopUpper);
8649 llvm::Value *step = moduleTranslation.lookupValue(loopStep);
8650
8651 if (!lowerBound || !upperBound || !step) {
8652 attrs.LoopTripCount = nullptr;
8653 break;
8654 }
8655
8656 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
8657 llvm::Value *tripCount = ompBuilder->calculateCanonicalLoopTripCount(
8658 loc, lowerBound, upperBound, step, /*IsSigned=*/true,
8659 loopOp.getLoopInclusive());
8660
8661 if (!attrs.LoopTripCount) {
8662 attrs.LoopTripCount = tripCount;
8663 continue;
8664 }
8665
8666 // TODO: Enable UndefinedSanitizer to diagnose an overflow here.
8667 attrs.LoopTripCount = builder.CreateMul(attrs.LoopTripCount, tripCount,
8668 {}, /*HasNUW=*/true);
8669 }
8670 }
8671
8672 attrs.DeviceID = builder.getInt64(llvm::omp::OMP_DEVICEID_UNDEF);
8673 if (mlir::Value devId = targetOp.getDevice()) {
8674 attrs.DeviceID = moduleTranslation.lookupValue(devId);
8675 attrs.DeviceID =
8676 builder.CreateSExtOrTrunc(attrs.DeviceID, builder.getInt64Ty());
8677 }
8678}
8679
8680static llvm::omp::OMPDynGroupprivateFallbackType
8681getDynGroupprivateFallbackType(omp::FallbackModifierAttr fallbackAttr) {
8682 omp::FallbackModifier fb = fallbackAttr ? fallbackAttr.getValue()
8683 : omp::FallbackModifier::default_mem;
8684 switch (fb) {
8685 case omp::FallbackModifier::abort:
8686 return llvm::omp::OMPDynGroupprivateFallbackType::Abort;
8687 case omp::FallbackModifier::null:
8688 return llvm::omp::OMPDynGroupprivateFallbackType::Null;
8689 case omp::FallbackModifier::default_mem:
8690 return llvm::omp::OMPDynGroupprivateFallbackType::DefaultMem;
8691 }
8692
8693 llvm_unreachable("unexpected dyn_groupprivate fallback type");
8694}
8695
8696static LogicalResult
8697convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
8698 LLVM::ModuleTranslation &moduleTranslation) {
8699 auto targetOp = cast<omp::TargetOp>(opInst);
8700
8701 // The current debug location already has the DISubprogram for the outlined
8702 // function that will be created for the target op. We save it here so that
8703 // we can set it on the outlined function.
8704 llvm::DebugLoc outlinedFnLoc = builder.getCurrentDebugLocation();
8705 if (failed(checkImplementationStatus(opInst)))
8706 return failure();
8707
8708 // During the handling of target op, we will generate instructions in the
8709 // parent function like call to the oulined function or branch to a new
8710 // BasicBlock. We set the debug location here to parent function so that those
8711 // get the correct debug locations. For outlined functions, the normal MLIR op
8712 // conversion will automatically pick the correct location.
8713 llvm::BasicBlock *parentBB = builder.GetInsertBlock();
8714 assert(parentBB && "No insert block is set for the builder");
8715 llvm::Function *parentLLVMFn = parentBB->getParent();
8716 assert(parentLLVMFn && "Parent Function must be valid");
8717 if (llvm::DISubprogram *SP = parentLLVMFn->getSubprogram())
8718 builder.SetCurrentDebugLocation(llvm::DILocation::get(
8719 parentLLVMFn->getContext(), outlinedFnLoc.getLine(),
8720 outlinedFnLoc.getCol(), SP, outlinedFnLoc.getInlinedAt()));
8721
8722 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8723 bool isTargetDevice = ompBuilder->Config.isTargetDevice();
8724 bool isGPU = ompBuilder->Config.isGPU();
8725
8726 auto parentFn = opInst.getParentOfType<LLVM::LLVMFuncOp>();
8727 auto argIface = cast<omp::BlockArgOpenMPOpInterface>(opInst);
8728 auto &targetRegion = targetOp.getRegion();
8729 // Holds the private vars that have been mapped along with the block
8730 // argument that corresponds to the MapInfoOp corresponding to the private
8731 // var in question. So, for instance:
8732 //
8733 // %10 = omp.map.info var_ptr(%6#0 : !fir.ref<!fir.box<!fir.heap<i32>>>, ..)
8734 // omp.target map_entries(%10 -> %arg0) private(@box.privatizer %6#0-> %arg1)
8735 //
8736 // Then, %10 has been created so that the descriptor can be used by the
8737 // privatizer @box.privatizer on the device side. Here we'd record {%6#0,
8738 // %arg0} in the mappedPrivateVars map.
8739 llvm::DenseMap<Value, Value> mappedPrivateVars;
8740 DataLayout dl = DataLayout(opInst.getParentOfType<ModuleOp>());
8741 SmallVector<Value> mapVars = targetOp.getMapVars();
8742 SmallVector<Value> hdaVars = targetOp.getHasDeviceAddrVars();
8743 ArrayRef<BlockArgument> mapBlockArgs = argIface.getMapBlockArgs();
8744 ArrayRef<BlockArgument> hdaBlockArgs = argIface.getHasDeviceAddrBlockArgs();
8745 llvm::Function *llvmOutlinedFn = nullptr;
8746 TargetDirectiveEnumTy targetDirective =
8747 getTargetDirectiveEnumTyFromOp(&opInst);
8748
8749 // TODO: It can also be false if a compile-time constant `false` IF clause is
8750 // specified.
8751 bool isOffloadEntry =
8752 isTargetDevice || !ompBuilder->Config.TargetTriples.empty();
8753
8754 // Resolve in_reduction clauses on omp.target for the host. From the target
8755 // device's perspective an in_reduction list item behaves as a regular
8756 // map(tofrom) variable, so no special handling is needed there; only the
8757 // host redirects the mapped value to the per-task reduction-private storage
8758 // returned by __kmpc_task_reduction_get_th_data (emitted inside the
8759 // to-be-outlined target task body). This applies to both offloading and
8760 // non-offloading host modules.
8761 //
8762 // The target body has no dedicated in_reduction block argument: each
8763 // in_reduction variable is accessed through its map_entries block argument.
8764 // So each in_reduction variable must also be captured by a matching
8765 // map_entries entry (guaranteed by the verifier); without one the outlined
8766 // body would reference a value defined in the host function. Record, for each
8767 // in_reduction variable, the position of that map entry so the corresponding
8768 // map block argument can be redirected inside the body. The in_reduction
8769 // operand itself is used as the `orig` argument of the runtime lookup.
8770 SmallVector<llvm::Value *> inRedOrigPtrs;
8771 SmallVector<unsigned> inRedMapArgIdx;
8772 if (!targetOp.getInReductionVars().empty() && !isTargetDevice) {
8773 inRedOrigPtrs.reserve(targetOp.getInReductionVars().size());
8774 inRedMapArgIdx.reserve(targetOp.getInReductionVars().size());
8775 for (Value v : targetOp.getInReductionVars()) {
8776 // Select the map_entries entry that captures this in_reduction operand.
8777 // The verifier guarantees at least one match exists; more than one
8778 // matching entry is a lowering ambiguity (the redirect cannot pick which
8779 // map argument to rebind).
8780 std::optional<unsigned> matchIdx;
8781 for (auto [idx, mapV] : llvm::enumerate(targetOp.getMapVars())) {
8782 auto mapInfo = mapV.getDefiningOp<omp::MapInfoOp>();
8783 if (v != mapInfo.getVarPtr())
8784 continue;
8785 if (matchIdx)
8786 return targetOp.emitError()
8787 << "in_reduction variable on omp.target has multiple matching "
8788 "map_entries entries; the redirect target is ambiguous";
8789 matchIdx = idx;
8790 }
8791 // The verifier requires a capturing map entry for every in_reduction
8792 // operand, so a match must exist here.
8793 assert(matchIdx &&
8794 "TargetOp verifier guarantees a matching map_entries entry for "
8795 "each in_reduction variable");
8796 inRedMapArgIdx.push_back(*matchIdx);
8797 // The runtime `orig` pointer is the in_reduction operand itself, the
8798 // reduction variable the enclosing taskgroup registered.
8799 inRedOrigPtrs.push_back(moduleTranslation.lookupValue(v));
8800 }
8801 }
8802
8803 // For some private variables, the MapsForPrivatizedVariablesPass
8804 // creates MapInfoOp instances. Go through the private variables and
8805 // the mapped variables so that during codegeneration we are able
8806 // to quickly look up the corresponding map variable, if any for each
8807 // private variable.
8808 if (!targetOp.getPrivateVars().empty() && !targetOp.getMapVars().empty()) {
8809 OperandRange privateVars = targetOp.getPrivateVars();
8810 std::optional<ArrayAttr> privateSyms = targetOp.getPrivateSyms();
8811 std::optional<DenseI64ArrayAttr> privateMapIndices =
8812 targetOp.getPrivateMapsAttr();
8813
8814 for (auto [privVarIdx, privVarSymPair] :
8815 llvm::enumerate(llvm::zip_equal(privateVars, *privateSyms))) {
8816 auto privVar = std::get<0>(privVarSymPair);
8817 auto privSym = std::get<1>(privVarSymPair);
8818
8819 SymbolRefAttr privatizerName = llvm::cast<SymbolRefAttr>(privSym);
8820 omp::PrivateClauseOp privatizer =
8821 findPrivatizer(targetOp, privatizerName);
8822
8823 if (!privatizer.needsMap())
8824 continue;
8825
8826 mlir::Value mappedValue =
8827 targetOp.getMappedValueForPrivateVar(privVarIdx);
8828 assert(mappedValue && "Expected to find mapped value for a privatized "
8829 "variable that needs mapping");
8830
8831 // The MapInfoOp defining the map var isn't really needed later.
8832 // So, we don't store it in any datastructure. Instead, we just
8833 // do some sanity checks on it right now.
8834 auto mapInfoOp = mappedValue.getDefiningOp<omp::MapInfoOp>();
8835 [[maybe_unused]] Type varType = mapInfoOp.getVarPtrType();
8836
8837 // Check #1: Check that the type of the private variable matches
8838 // the type of the variable being mapped.
8839 if (!isa<LLVM::LLVMPointerType>(privVar.getType()))
8840 assert(
8841 varType == privVar.getType() &&
8842 "Type of private var doesn't match the type of the mapped value");
8843
8844 // Ok, only 1 sanity check for now.
8845 // Record the block argument corresponding to this mapvar.
8846 mappedPrivateVars.insert(
8847 {privVar,
8848 targetRegion.getArgument(argIface.getMapBlockArgsStart() +
8849 (*privateMapIndices)[privVarIdx])});
8850 }
8851 }
8852
8853 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
8854 auto bodyCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
8855 ArrayRef<llvm::BasicBlock *> deallocBlocks)
8856 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
8857 llvm::IRBuilderBase::InsertPointGuard guard(builder);
8858 builder.SetCurrentDebugLocation(llvm::DebugLoc());
8859 // Forward target-cpu and target-features function attributes from the
8860 // original function to the new outlined function.
8861 llvm::Function *llvmParentFn =
8862 moduleTranslation.lookupFunction(parentFn.getName());
8863 llvmOutlinedFn = codeGenIP.getBlock()->getParent();
8864 assert(llvmParentFn && llvmOutlinedFn &&
8865 "Both parent and outlined functions must exist at this point");
8866
8867 if (outlinedFnLoc && llvmParentFn->getSubprogram())
8868 llvmOutlinedFn->setSubprogram(outlinedFnLoc->getScope()->getSubprogram());
8869
8870 if (auto attr = llvmParentFn->getFnAttribute("target-cpu");
8871 attr.isStringAttribute())
8872 llvmOutlinedFn->addFnAttr(attr);
8873
8874 if (auto attr = llvmParentFn->getFnAttribute("target-features");
8875 attr.isStringAttribute())
8876 llvmOutlinedFn->addFnAttr(attr);
8877
8878 for (auto [idx, arg] : llvm::enumerate(mapBlockArgs)) {
8879 // in_reduction list items on omp.target are accessed through their
8880 // map_entries block argument, which is redirected below to the per-task
8881 // reduction-private storage returned by the runtime. Skip the default
8882 // host-value mapping for those block arguments so the write-once
8883 // mapValue mapping is free to be set to the private pointer.
8884 if (llvm::is_contained(inRedMapArgIdx, idx))
8885 continue;
8886 auto mapInfoOp = cast<omp::MapInfoOp>(mapVars[idx].getDefiningOp());
8887 llvm::Value *mapOpValue =
8888 moduleTranslation.lookupValue(mapInfoOp.getVarPtr());
8889 moduleTranslation.mapValue(arg, mapOpValue);
8890 }
8891 for (auto [arg, mapOp] : llvm::zip_equal(hdaBlockArgs, hdaVars)) {
8892 auto mapInfoOp = cast<omp::MapInfoOp>(mapOp.getDefiningOp());
8893 llvm::Value *mapOpValue =
8894 moduleTranslation.lookupValue(mapInfoOp.getVarPtr());
8895 moduleTranslation.mapValue(arg, mapOpValue);
8896 }
8897
8898 // Do privatization after moduleTranslation has already recorded
8899 // mapped values.
8900 PrivateVarsInfo privateVarsInfo(targetOp);
8901
8903 allocatePrivateVars(targetOp, builder, moduleTranslation,
8904 privateVarsInfo, allocaIP, &mappedPrivateVars);
8905
8906 if (failed(handleError(afterAllocas, *targetOp)))
8907 return llvm::make_error<PreviouslyReportedError>();
8908
8909 builder.restoreIP(codeGenIP);
8910 if (handleError(initPrivateVars(builder, moduleTranslation, privateVarsInfo,
8911 &mappedPrivateVars),
8912 *targetOp)
8913 .failed())
8914 return llvm::make_error<PreviouslyReportedError>();
8915
8916 if (failed(copyFirstPrivateVars(
8917 targetOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
8918 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
8919 targetOp.getPrivateNeedsBarrier(), &mappedPrivateVars)))
8920 return llvm::make_error<PreviouslyReportedError>();
8921
8922 // The target body accesses each in_reduction variable through its
8923 // map_entries block argument. Redirect that block argument to the per-task
8924 // private storage returned by __kmpc_task_reduction_get_th_data so the body
8925 // accumulates into the reduction-private copy rather than the mapped
8926 // original. The lookup must run inside the target task body so the gtid
8927 // corresponds to the executing thread. The descriptor argument is NULL: the
8928 // runtime walks enclosing taskgroups to locate the matching task_reduction
8929 // registration for `origPtr`. Mirrors the in_reduction handling on
8930 // omp.taskloop.context.
8931 if (!inRedOrigPtrs.empty()) {
8932 // Collect, per item, the type the private pointer must have (the map
8933 // block argument's type), and, through the callback, rebind the map block
8934 // argument that stands in for each in_reduction list item to the per-task
8935 // reduction-private storage the runtime returns.
8936 SmallVector<llvm::Type *> inRedResultPtrTys;
8937 inRedResultPtrTys.reserve(inRedMapArgIdx.size());
8938 for (unsigned mapArgIdx : inRedMapArgIdx)
8939 inRedResultPtrTys.push_back(
8940 moduleTranslation.convertType(mapBlockArgs[mapArgIdx].getType()));
8941
8942 llvm::OpenMPIRBuilder::LocationDescription bodyLoc(builder);
8943 llvm::OpenMPIRBuilder::InsertPointTy redIP =
8944 ompBuilder->createTargetInReduction(
8945 bodyLoc, inRedOrigPtrs, inRedResultPtrTys,
8946 [&](unsigned idx, llvm::Value *priv) {
8947 moduleTranslation.mapValue(mapBlockArgs[inRedMapArgIdx[idx]],
8948 priv);
8949 });
8950 builder.restoreIP(redIP);
8951 }
8952
8954 moduleTranslation, allocaIP, deallocBlocks);
8956 targetRegion, "omp.target", builder, moduleTranslation);
8957
8958 if (failed(handleError(exitBlock, *targetOp)))
8959 return llvm::make_error<PreviouslyReportedError>();
8960
8961 builder.SetInsertPoint(exitBlock.get()->getTerminator());
8962
8963 if (failed(cleanupPrivateVars(targetOp, builder, moduleTranslation,
8964 targetOp.getLoc(), privateVarsInfo)))
8965 return llvm::make_error<PreviouslyReportedError>();
8966
8967 return builder.saveIP();
8968 };
8969
8970 StringRef parentName = parentFn.getName();
8971
8972 llvm::TargetRegionEntryInfo entryInfo;
8973
8974 getTargetEntryUniqueInfo(entryInfo, targetOp,
8975 *moduleTranslation.getOpenMPBuilder(),
8976 moduleTranslation.getFileSystem(), parentName);
8977
8978 MapInfoData mapData;
8979 collectMapDataFromMapOperands(mapData, mapVars, moduleTranslation, dl,
8980 builder, /*useDevPtrOperands=*/{},
8981 /*useDevAddrOperands=*/{}, hdaVars);
8982
8983 MapInfosTy combinedInfos;
8984 auto genMapInfoCB =
8985 [&](llvm::OpenMPIRBuilder::InsertPointTy codeGenIP) -> MapInfosTy & {
8986 builder.restoreIP(codeGenIP);
8987 genMapInfos(builder, moduleTranslation, dl, combinedInfos, mapData,
8988 targetDirective);
8989
8990 // Append a null entry for the implicit dyn_ptr argument so the argument
8991 // count sent to the runtime already includes it.
8992 auto *nullPtr = llvm::Constant::getNullValue(builder.getPtrTy());
8993 combinedInfos.BasePointers.push_back(nullPtr);
8994 combinedInfos.Pointers.push_back(nullPtr);
8995 combinedInfos.DevicePointers.push_back(
8996 llvm::OpenMPIRBuilder::DeviceInfoTy::None);
8997 combinedInfos.Sizes.push_back(builder.getInt64(0));
8998 combinedInfos.Types.push_back(
8999 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TARGET_PARAM |
9000 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL);
9001 if (!combinedInfos.Names.empty())
9002 combinedInfos.Names.push_back(nullPtr);
9003 combinedInfos.Mappers.push_back(nullptr);
9004
9005 return combinedInfos;
9006 };
9007
9008 auto argAccessorCB = [&](llvm::Argument &arg, llvm::Value *input,
9009 llvm::Value *&retVal, InsertPointTy allocaIP,
9010 InsertPointTy codeGenIP,
9012 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
9013 llvm::IRBuilderBase::InsertPointGuard guard(builder);
9014 builder.SetCurrentDebugLocation(llvm::DebugLoc());
9015 // We just return the unaltered argument for the host function
9016 // for now, some alterations may be required in the future to
9017 // keep host fallback functions working identically to the device
9018 // version (e.g. pass ByCopy values should be treated as such on
9019 // host and device, currently not always the case)
9020 if (!isTargetDevice) {
9021 retVal = cast<llvm::Value>(&arg);
9022 return codeGenIP;
9023 }
9024
9025 return createDeviceArgumentAccessor(targetOp, mapData, arg, input, retVal,
9026 builder, *ompBuilder, moduleTranslation,
9027 allocaIP, codeGenIP, deallocIPs);
9028 };
9029
9030 llvm::OpenMPIRBuilder::TargetKernelRuntimeAttrs runtimeAttrs;
9031 llvm::OpenMPIRBuilder::TargetKernelDefaultAttrs defaultAttrs;
9032 Operation *targetCapturedOp =
9033 cast<omp::ComposableOpInterface>(*targetOp).findCapturedOp();
9034 initTargetDefaultAttrs(targetOp, targetCapturedOp, defaultAttrs,
9035 isTargetDevice, isGPU);
9036
9037 // Collect host-evaluated values needed to properly launch the kernel from the
9038 // host.
9039 if (!isTargetDevice)
9040 initTargetRuntimeAttrs(builder, moduleTranslation, targetOp,
9041 targetCapturedOp, runtimeAttrs);
9042
9043 // Pass host-evaluated values as parameters to the kernel / host fallback,
9044 // except if they are constants. In any case, map the MLIR block argument to
9045 // the corresponding LLVM values.
9047 SmallVector<Value> hostEvalVars = targetOp.getHostEvalVars();
9048 ArrayRef<BlockArgument> hostEvalBlockArgs = argIface.getHostEvalBlockArgs();
9049 for (auto [arg, var] : llvm::zip_equal(hostEvalBlockArgs, hostEvalVars)) {
9050 llvm::Value *value = moduleTranslation.lookupValue(var);
9051 moduleTranslation.mapValue(arg, value);
9052
9053 if (!llvm::isa<llvm::Constant>(value))
9054 kernelInput.push_back(value);
9055 }
9056
9057 for (size_t i = 0, e = mapData.OriginalValue.size(); i != e; ++i) {
9058 // 1) Declare target arguments are not passed to kernels as arguments.
9059 // 2) Attach maps are not passed in as arguments to kernels.
9060 // 3) Children of record objects are not passed in as arguments.
9061 // TODO: We currently do not handle cases where a member is explicitly
9062 // passed in as an argument, this will likley need to be handled in
9063 // the near future, rather than using IsAMember, it may be better to
9064 // test if the relevant BlockArg is used within the target region and
9065 // then use that as a basis for exclusion in the kernel inputs.
9066 bool isAttachMap = (mapData.Types[i] &
9067 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) ==
9068 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH;
9069 if (!mapData.IsDeclareTarget[i] && !mapData.IsAMember[i] && !isAttachMap)
9070 kernelInput.push_back(mapData.OriginalValue[i]);
9071 }
9072
9074 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
9075 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
9076
9077 llvm::OpenMPIRBuilder::DependenciesInfo dds;
9078 if (failed(buildDependData(
9079 targetOp.getDependVars(), targetOp.getDependKinds(),
9080 targetOp.getDependIterated(), targetOp.getDependIteratedKinds(),
9081 builder, moduleTranslation, dds)))
9082 return failure();
9083
9084 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
9085
9086 llvm::OpenMPIRBuilder::TargetDataInfo info(
9087 /*RequiresDevicePointerInfo=*/false,
9088 /*SeparateBeginEndCalls=*/true);
9089
9090 auto customMapperCB =
9091 [&](unsigned int i) -> llvm::Expected<llvm::Function *> {
9092 if (!combinedInfos.Mappers[i])
9093 return nullptr;
9094 info.HasMapper = true;
9095 return getOrCreateUserDefinedMapperFunc(combinedInfos.Mappers[i], builder,
9096 moduleTranslation, targetDirective);
9097 };
9098
9099 llvm::Value *ifCond = nullptr;
9100 if (Value targetIfCond = targetOp.getIfExpr())
9101 ifCond = moduleTranslation.lookupValue(targetIfCond);
9102
9103 Value dynGroupPrivateSize = targetOp.getDynGroupprivateSize();
9104 llvm::Value *dynSizeVal = nullptr;
9105 if (dynGroupPrivateSize) {
9106 dynSizeVal = moduleTranslation.lookupValue(dynGroupPrivateSize);
9107 dynSizeVal = builder.CreateIntCast(dynSizeVal, builder.getInt32Ty(),
9108 /*isSigned=*/false);
9109 }
9110
9111 llvm::omp::OMPDynGroupprivateFallbackType fallbackType =
9112 getDynGroupprivateFallbackType(targetOp.getDynGroupprivateFallbackAttr());
9113
9114 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
9115 moduleTranslation.getOpenMPBuilder()->createTarget(
9116 ompLoc, isOffloadEntry, allocaIP, builder.saveIP(), deallocBlocks,
9117 info, entryInfo, defaultAttrs, runtimeAttrs, ifCond, kernelInput,
9118 genMapInfoCB, bodyCB, argAccessorCB, customMapperCB, dds,
9119 targetOp.getNowait(), dynSizeVal, fallbackType);
9120
9121 if (failed(handleError(afterIP, opInst)))
9122 return failure();
9123
9124 builder.restoreIP(*afterIP);
9125
9126 if (dds.DepArray)
9127 builder.CreateFree(dds.DepArray);
9128
9129 // Remap access operations to declare target reference pointers for the
9130 // device, essentially generating extra loadop's as necessary
9131 if (moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice())
9132 handleDeclareTargetMapVar(mapData, moduleTranslation, builder,
9133 llvmOutlinedFn);
9134
9135 return success();
9136}
9137
9138static LogicalResult
9139convertDeclareTargetAttr(Operation *op, mlir::omp::DeclareTargetAttr attribute,
9140 llvm::OpenMPIRBuilder *ompBuilder,
9141 LLVM::ModuleTranslation &moduleTranslation) {
9142 // Amend omp.declare_target by deleting the IR of the outlined functions
9143 // created for target regions. They cannot be filtered out from MLIR earlier
9144 // because the omp.target operation inside must be translated to LLVM, but
9145 // the wrapper functions themselves must not remain at the end of the
9146 // process. We know that functions where omp.declare_target does not match
9147 // omp.is_target_device at this stage can only be wrapper functions because
9148 // those that aren't are removed earlier as an MLIR transformation pass.
9149 if (FunctionOpInterface funcOp = dyn_cast<FunctionOpInterface>(op)) {
9150 if (auto offloadMod = dyn_cast<omp::OffloadModuleInterface>(
9151 op->getParentOfType<ModuleOp>().getOperation())) {
9152 if (!offloadMod.getIsTargetDevice())
9153 return success();
9154
9155 omp::DeclareTargetDeviceType declareType =
9156 attribute.getDeviceType().getValue();
9157
9158 if (declareType == omp::DeclareTargetDeviceType::host) {
9159 llvm::Function *llvmFunc =
9160 moduleTranslation.lookupFunction(funcOp.getName());
9161 llvmFunc->dropAllReferences();
9162 llvmFunc->eraseFromParent();
9163
9164 // Invalidate the builder's current insertion point, as it now points to
9165 // a deleted block.
9166 ompBuilder->Builder.ClearInsertionPoint();
9167 ompBuilder->Builder.SetCurrentDebugLocation(llvm::DebugLoc());
9168 } else if (llvm::Function *llvmFunc =
9169 moduleTranslation.lookupFunction(funcOp.getName())) {
9170 // Device-side declare target functions are externally visible by
9171 // default so they can be referenced from other device translation
9172 // units. That also prevents the offload LTO from internalizing and
9173 // deleting them when they end up unused in the final device image.
9174 // Such dead functions can still reference internal LDS and trigger
9175 // spurious "local memory global used by non-kernel function" backend
9176 // warnings. Marking them hidden keeps the symbol usable within the
9177 // device image's linkage unit while letting LTO drop it when nothing
9178 // references it; symbols that must stay reachable (e.g. via an offload
9179 // entry that takes their address) are kept alive by that reference.
9180 if (!llvmFunc->isDeclaration() && llvmFunc->hasExternalLinkage() &&
9181 llvmFunc->getVisibility() == llvm::GlobalValue::DefaultVisibility)
9182 llvmFunc->setVisibility(llvm::GlobalValue::HiddenVisibility);
9183 }
9184 }
9185 return success();
9186 }
9187
9188 if (LLVM::GlobalOp gOp = dyn_cast<LLVM::GlobalOp>(op)) {
9189 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
9190 if (auto *gVal = llvmModule->getNamedValue(gOp.getSymName())) {
9191 auto *gVar = cast<llvm::GlobalVariable>(gVal);
9192 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9193 bool isDeclaration = gOp.isDeclaration();
9194 bool isExternallyVisible =
9195 gOp.getVisibility() != mlir::SymbolTable::Visibility::Private;
9196 auto loc = op->getLoc()->findInstanceOf<FileLineColLoc>();
9197 llvm::StringRef mangledName = gOp.getSymName();
9198 mlir::omp::DeclareTargetCaptureClause captureClause =
9199 attribute.getCaptureClause().getValue();
9200 auto captureClauseKind = convertToCaptureClauseKind(captureClause);
9201 auto deviceClause =
9202 convertToDeviceClauseKind(attribute.getDeviceType().getValue());
9203 llvm::StringRef entryMangledName = mangledName;
9204 llvm::Constant *entryAddr = llvm::cast<llvm::Constant>(gVal);
9205 std::function<llvm::GlobalValue::LinkageTypes()> variableLinkage;
9206 llvm::SmallString<128> entryNameStorage;
9207 bool requiresUSM = ompBuilder->Config.hasRequiresUnifiedSharedMemory();
9208 bool isToOrEnter =
9209 captureClause == omp::DeclareTargetCaptureClause::to ||
9210 captureClause == omp::DeclareTargetCaptureClause::enter;
9211 bool isHostOnly = attribute.getDeviceType().getValue() ==
9212 omp::DeclareTargetDeviceType::host;
9213
9214 // A to/enter declare-target variable needs a device-resident,
9215 // name-resolvable copy and a host offloading entry. A local-linkage
9216 // global provides neither, so we promote it to external.
9217 if (isToOrEnter && !isHostOnly && !requiresUSM &&
9218 gVar->hasLocalLinkage()) {
9219 gVar->setLinkage(llvm::GlobalValue::ExternalLinkage);
9220 isExternallyVisible = true;
9221
9222 // Clear the stale dso_local flag so it is referenced like a
9223 // module-scope declare target global.
9224 if (ompBuilder->Config.isTargetDevice())
9225 gVar->setDSOLocal(false);
9226 }
9227
9228 if (isToOrEnter &&
9229 deviceClause ==
9230 llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseAny &&
9231 !requiresUSM && !isDeclaration &&
9232 (gVal->hasLocalLinkage() || gVal->hasHiddenVisibility())) {
9233 // Keep the original symbol as-is for target code, but create a visible
9234 // alias for the offload entry so libomptarget can associate the host
9235 // global with the actual device global.
9236 entryNameStorage = (mangledName + llvm::Twine("_decl_tgt_entry")).str();
9237 entryMangledName = entryNameStorage;
9238 if (llvm::GlobalValue *existing =
9239 llvmModule->getNamedValue(entryMangledName)) {
9240 entryAddr = llvm::cast<llvm::Constant>(existing);
9241 } else {
9242 entryAddr = llvm::GlobalAlias::create(
9243 gVal->getValueType(), gVal->getAddressSpace(),
9244 llvm::GlobalValue::WeakAnyLinkage, entryMangledName, entryAddr,
9245 llvmModule);
9246 llvm::cast<llvm::GlobalAlias>(entryAddr)->setVisibility(
9247 llvm::GlobalValue::DefaultVisibility);
9248 }
9249 variableLinkage = [] { return llvm::GlobalValue::WeakAnyLinkage; };
9250 }
9251 // unused for MLIR at the moment, required in Clang for book
9252 // keeping
9253 std::vector<llvm::GlobalVariable *> generatedRefs;
9254
9255 std::vector<llvm::Triple> targetTriple;
9256 auto targetTripleAttr = dyn_cast_or_null<mlir::StringAttr>(
9257 op->getParentOfType<mlir::ModuleOp>()->getAttr(
9258 LLVM::LLVMDialect::getTargetTripleAttrName()));
9259 if (targetTripleAttr)
9260 targetTriple.emplace_back(targetTripleAttr.data());
9261
9262 auto fileInfoCallBack = [&loc]() {
9263 std::string filename = "";
9264 std::uint64_t lineNo = 0;
9265
9266 if (loc) {
9267 filename = loc.getFilename().str();
9268 lineNo = loc.getLine();
9269 }
9270
9271 return std::pair<std::string, std::uint64_t>(llvm::StringRef(filename),
9272 lineNo);
9273 };
9274
9275 llvm::vfs::FileSystem &vfs = moduleTranslation.getFileSystem();
9276 ompBuilder->registerTargetGlobalVariable(
9277 captureClauseKind, deviceClause, isDeclaration, isExternallyVisible,
9278 ompBuilder->getTargetEntryUniqueInfo(fileInfoCallBack, vfs),
9279 entryMangledName, generatedRefs, /*OpenMPSimd*/ false, targetTriple,
9280 /*GlobalInitializer*/ nullptr, variableLinkage, gVal->getType(),
9281 entryAddr);
9282
9283 if (ompBuilder->Config.isTargetDevice() &&
9284 (captureClause == omp::DeclareTargetCaptureClause::link ||
9285 requiresUSM)) {
9286 llvm::Type *ptrTy = gVal->getType();
9287 // For USM the global type becomes a pointer handle, as opposed to the
9288 // globals original type.
9289 if (requiresUSM)
9290 ptrTy = llvm::PointerType::get(llvmModule->getContext(), 0);
9291 bool addrGlobalCreated = ompBuilder->getAddrOfDeclareTargetVar(
9292 captureClauseKind, deviceClause, isDeclaration, isExternallyVisible,
9293 ompBuilder->getTargetEntryUniqueInfo(fileInfoCallBack, vfs),
9294 mangledName, generatedRefs, /*OpenMPSimd*/ false, targetTriple,
9295 ptrTy, /*GlobalInitializer*/ nullptr,
9296 /*VariableLinkage*/ nullptr);
9297
9298 // For indirectly-accessed global pointers, we rely on "internal"
9299 // linkage to optimize out the unneeded full-variable storage later,
9300 // since we can't prevent the LLVM dialect from generating globals
9301 // without also breaking target lowering.
9302 if (addrGlobalCreated)
9303 gVar->setLinkage(llvm::GlobalValue::InternalLinkage);
9304 }
9305
9306 // Mark 'device_type(host) enter(...)' variables as external in the device
9307 // since they're not supposed to have their own copy. This will cause
9308 // linker errors if accesses are attempted from the target device.
9309 if (ompBuilder->Config.isTargetDevice() && isHostOnly && isToOrEnter) {
9310 gVar->setLinkage(llvm::GlobalValue::ExternalLinkage);
9311 gVar->setInitializer(nullptr);
9312 }
9313 }
9314 }
9315
9316 return success();
9317}
9318
9319namespace {
9320
9321/// Implementation of the dialect interface that converts operations belonging
9322/// to the OpenMP dialect to LLVM IR.
9323class OpenMPDialectLLVMIRTranslationInterface
9324 : public LLVMTranslationDialectInterface {
9325public:
9326 using LLVMTranslationDialectInterface::LLVMTranslationDialectInterface;
9327
9328 /// Translates the given operation to LLVM IR using the provided IR builder
9329 /// and saving the state in `moduleTranslation`.
9330 LogicalResult
9331 convertOperation(Operation *op, llvm::IRBuilderBase &builder,
9332 LLVM::ModuleTranslation &moduleTranslation) const final;
9333
9334 /// Given an OpenMP MLIR attribute, create the corresponding LLVM-IR,
9335 /// runtime calls, or operation amendments
9336 LogicalResult
9337 amendOperation(Operation *op, ArrayRef<llvm::Instruction *> instructions,
9338 NamedAttribute attribute,
9339 LLVM::ModuleTranslation &moduleTranslation) const final;
9340
9341 /// Records the LLVM alloc pointer produced for an OMP ALLOCATE variable so
9342 /// that the paired omp.allocate_free op can generate the matching
9343 /// __kmpc_free call.
9344 void registerAllocatedPtr(Value var, llvm::Value *ptr) const {
9345 ompAllocatedPtrs[var] = ptr;
9346 }
9347
9348 /// Returns the LLVM alloc pointer previously registered for var, or
9349 /// nullptr if no allocation was recorded.
9350 llvm::Value *lookupAllocatedPtr(Value var) const {
9351 auto it = ompAllocatedPtrs.find(var);
9352 return it != ompAllocatedPtrs.end() ? it->second : nullptr;
9353 }
9354
9355private:
9356 /// Maps each MLIR variable value that appeared in an omp.allocate_dir op to
9357 /// the LLVM pointer returned by the corresponding __kmpc_alloc call. The
9358 /// paired omp.allocate_free op looks up these pointers to emit __kmpc_free.
9359 mutable DenseMap<Value, llvm::Value *> ompAllocatedPtrs;
9360};
9361
9362} // namespace
9363
9364LogicalResult OpenMPDialectLLVMIRTranslationInterface::amendOperation(
9365 Operation *op, ArrayRef<llvm::Instruction *> instructions,
9366 NamedAttribute attribute,
9367 LLVM::ModuleTranslation &moduleTranslation) const {
9368 return llvm::StringSwitch<llvm::function_ref<LogicalResult(Attribute)>>(
9369 attribute.getName())
9370 .Case("omp.is_target_device",
9371 [&](Attribute attr) {
9372 if (auto deviceAttr = dyn_cast<BoolAttr>(attr)) {
9373 llvm::OpenMPIRBuilderConfig &config =
9374 moduleTranslation.getOpenMPBuilder()->Config;
9375 config.setIsTargetDevice(deviceAttr.getValue());
9376 return success();
9377 }
9378 return failure();
9379 })
9380 .Case("omp.is_gpu",
9381 [&](Attribute attr) {
9382 if (auto gpuAttr = dyn_cast<BoolAttr>(attr)) {
9383 llvm::OpenMPIRBuilderConfig &config =
9384 moduleTranslation.getOpenMPBuilder()->Config;
9385 config.setIsGPU(gpuAttr.getValue());
9386 return success();
9387 }
9388 return failure();
9389 })
9390 .Case("omp.host_ir_filepath",
9391 [&](Attribute attr) {
9392 if (auto filepathAttr = dyn_cast<StringAttr>(attr)) {
9393 llvm::OpenMPIRBuilder *ompBuilder =
9394 moduleTranslation.getOpenMPBuilder();
9395 ompBuilder->loadOffloadInfoMetadata(
9396 moduleTranslation.getFileSystem(), filepathAttr.getValue());
9397 return success();
9398 }
9399 return failure();
9400 })
9401 .Case("omp.flags",
9402 [&](Attribute attr) {
9403 if (auto rtlAttr = dyn_cast<omp::FlagsAttr>(attr))
9404 return convertFlagsAttr(op, rtlAttr, moduleTranslation);
9405 return failure();
9406 })
9407 .Case("omp.version",
9408 [&](Attribute attr) {
9409 if (auto versionAttr = dyn_cast<omp::VersionAttr>(attr)) {
9410 llvm::OpenMPIRBuilder *ompBuilder =
9411 moduleTranslation.getOpenMPBuilder();
9412 ompBuilder->M.addModuleFlag(llvm::Module::Max, "openmp",
9413 versionAttr.getVersion());
9414 return success();
9415 }
9416 return failure();
9417 })
9418 .Case("omp.declare_target",
9419 [&](Attribute attr) {
9420 if (auto declareTargetAttr =
9421 dyn_cast<omp::DeclareTargetAttr>(attr)) {
9422 llvm::OpenMPIRBuilder *ompBuilder =
9423 moduleTranslation.getOpenMPBuilder();
9424 return convertDeclareTargetAttr(op, declareTargetAttr,
9425 ompBuilder, moduleTranslation);
9426 }
9427 return failure();
9428 })
9429 .Case("omp.requires",
9430 [&](Attribute attr) {
9431 if (auto requiresAttr = dyn_cast<omp::ClauseRequiresAttr>(attr)) {
9432 using Requires = omp::ClauseRequires;
9433 Requires flags = requiresAttr.getValue();
9434 llvm::OpenMPIRBuilderConfig &config =
9435 moduleTranslation.getOpenMPBuilder()->Config;
9436 config.setHasRequiresReverseOffload(
9437 bitEnumContainsAll(flags, Requires::reverse_offload));
9438 config.setHasRequiresUnifiedAddress(
9439 bitEnumContainsAll(flags, Requires::unified_address));
9440 config.setHasRequiresUnifiedSharedMemory(
9441 bitEnumContainsAll(flags, Requires::unified_shared_memory));
9442 config.setHasRequiresDynamicAllocators(
9443 bitEnumContainsAll(flags, Requires::dynamic_allocators));
9444 return success();
9445 }
9446 return failure();
9447 })
9448 .Case("omp.target_triples",
9449 [&](Attribute attr) {
9450 if (auto triplesAttr = dyn_cast<ArrayAttr>(attr)) {
9451 llvm::OpenMPIRBuilderConfig &config =
9452 moduleTranslation.getOpenMPBuilder()->Config;
9453 config.TargetTriples.clear();
9454 config.TargetTriples.reserve(triplesAttr.size());
9455 for (Attribute tripleAttr : triplesAttr) {
9456 if (auto tripleStrAttr = dyn_cast<StringAttr>(tripleAttr))
9457 config.TargetTriples.emplace_back(tripleStrAttr.getValue());
9458 else
9459 return failure();
9460 }
9461 return success();
9462 }
9463 return failure();
9464 })
9465 .Default([](Attribute) {
9466 // Fall through for omp attributes that do not require lowering.
9467 return success();
9468 })(attribute.getValue());
9469
9470 return failure();
9471}
9472
9473// Returns true if the operation is not inside a TargetOp, it is part of a
9474// function and that function is not declare target.
9475static bool isHostDeviceOp(Operation *op) {
9476 // Assumes no reverse offloading
9477 if (op->getParentOfType<omp::TargetOp>())
9478 return false;
9479
9480 if (auto parentFn = op->getParentOfType<LLVM::LLVMFuncOp>()) {
9481 if (auto declareTargetIface =
9482 llvm::dyn_cast<mlir::omp::DeclareTargetInterface>(
9483 parentFn.getOperation()))
9484 if (declareTargetIface.isDeclareTarget() &&
9485 declareTargetIface.getDeclareTargetDeviceType() !=
9486 mlir::omp::DeclareTargetDeviceType::host)
9487 return false;
9488
9489 return true;
9490 }
9491
9492 return false;
9493}
9494
9495static llvm::Function *getOmpTargetAlloc(llvm::IRBuilderBase &builder,
9496 llvm::Module *llvmModule) {
9497 llvm::Type *i64Ty = builder.getInt64Ty();
9498 llvm::Type *i32Ty = builder.getInt32Ty();
9499 llvm::Type *returnType = builder.getPtrTy(0);
9500 llvm::FunctionType *fnType =
9501 llvm::FunctionType::get(returnType, {i64Ty, i32Ty}, false);
9502 llvm::Function *func = cast<llvm::Function>(
9503 llvmModule->getOrInsertFunction("omp_target_alloc", fnType).getCallee());
9504 return func;
9505}
9506
9507template <typename T>
9508static llvm::Value *
9509getAllocationSize(llvm::IRBuilderBase &builder,
9510 LLVM::ModuleTranslation &moduleTranslation, T op) {
9511 llvm::DataLayout dataLayout =
9512 moduleTranslation.getLLVMModule()->getDataLayout();
9513 llvm::Type *llvmHeapTy =
9514 moduleTranslation.convertType(op.getMemElemTypeAttr().getValue());
9515
9516 auto alignment = op.getMemAlignment();
9517 llvm::TypeSize typeSize = llvm::alignTo(
9518 dataLayout.getTypeStoreSize(llvmHeapTy),
9519 alignment ? *alignment : dataLayout.getABITypeAlign(llvmHeapTy).value());
9520
9521 llvm::Value *allocSize = builder.getInt64(typeSize.getFixedValue());
9522 return builder.CreateMul(
9523 allocSize,
9524 builder.CreateIntCast(moduleTranslation.lookupValue(op.getMemArraySize()),
9525 builder.getInt64Ty(),
9526 /*isSigned=*/false));
9527}
9528
9529template <>
9530llvm::Value *getAllocationSize(llvm::IRBuilderBase &builder,
9531 LLVM::ModuleTranslation &moduleTranslation,
9532 omp::TargetAllocMemOp op) {
9533 llvm::DataLayout dataLayout =
9534 moduleTranslation.getLLVMModule()->getDataLayout();
9535 llvm::Type *llvmHeapTy = moduleTranslation.convertType(op.getAllocatedType());
9536 llvm::TypeSize typeSize = dataLayout.getTypeAllocSize(llvmHeapTy);
9537 llvm::Value *allocSize = builder.getInt64(typeSize.getFixedValue());
9538 for (auto typeParam : op.getTypeparams()) {
9539 allocSize = builder.CreateMul(
9540 allocSize,
9541 builder.CreateIntCast(moduleTranslation.lookupValue(typeParam),
9542 builder.getInt64Ty(),
9543 /*isSigned=*/false));
9544 }
9545 return allocSize;
9546}
9547
9548static LogicalResult
9549convertTargetAllocMemOp(Operation &opInst, llvm::IRBuilderBase &builder,
9550 LLVM::ModuleTranslation &moduleTranslation) {
9551 auto allocMemOp = cast<omp::TargetAllocMemOp>(opInst);
9552 if (!allocMemOp)
9553 return failure();
9554
9555 // Get "omp_target_alloc" function
9556 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
9557 llvm::Function *ompTargetAllocFunc = getOmpTargetAlloc(builder, llvmModule);
9558 // Get the corresponding device value in llvm
9559 mlir::Value deviceNum = allocMemOp.getDevice();
9560 llvm::Value *llvmDeviceNum = moduleTranslation.lookupValue(deviceNum);
9561 // Get the allocation size.
9562 llvm::Value *allocSize =
9563 getAllocationSize(builder, moduleTranslation, allocMemOp);
9564 // Create call to "omp_target_alloc" with the args as translated llvm values.
9565 llvm::CallInst *call =
9566 builder.CreateCall(ompTargetAllocFunc, {allocSize, llvmDeviceNum});
9567 llvm::Value *resultI64 = builder.CreatePtrToInt(call, builder.getInt64Ty());
9568
9569 // Map the result
9570 moduleTranslation.mapValue(allocMemOp.getResult(), resultI64);
9571 return success();
9572}
9573
9574static LogicalResult
9575convertAllocSharedMemOp(omp::AllocSharedMemOp allocMemOp,
9576 llvm::IRBuilderBase &builder,
9577 LLVM::ModuleTranslation &moduleTranslation) {
9578 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9579 llvm::Value *size = getAllocationSize(builder, moduleTranslation, allocMemOp);
9580 moduleTranslation.mapValue(allocMemOp.getResult(),
9581 ompBuilder->createOMPAllocShared(builder, size));
9582 return success();
9583}
9584
9585static LogicalResult
9586convertAllocateDirOp(Operation &opInst, llvm::IRBuilderBase &builder,
9587 LLVM::ModuleTranslation &moduleTranslation,
9588 const OpenMPDialectLLVMIRTranslationInterface &ompIface) {
9589 auto allocateDirOp = cast<omp::AllocateDirOp>(opInst);
9590 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9591
9592 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
9593 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
9594 llvm::DataLayout dataLayout = llvmModule->getDataLayout();
9595 SmallVector<Value> vars = allocateDirOp.getVarList();
9596 std::optional<int64_t> alignAttr = allocateDirOp.getAlign();
9597
9598 llvm::Value *allocator;
9599 if (auto allocatorVar = allocateDirOp.getAllocator()) {
9600 allocator = moduleTranslation.lookupValue(allocatorVar);
9601 if (allocator->getType()->isIntegerTy())
9602 allocator = builder.CreateIntToPtr(allocator, builder.getPtrTy());
9603 else if (allocator->getType()->isPointerTy())
9604 allocator = builder.CreatePointerBitCastOrAddrSpaceCast(
9605 allocator, builder.getPtrTy());
9606 } else {
9607 allocator = llvm::ConstantPointerNull::get(builder.getPtrTy());
9608 }
9609
9610 for (Value var : vars) {
9611 llvm::Type *llvmVarTy = moduleTranslation.convertType(var.getType());
9612
9613 // Opaque pointers lose element type. Trace to GlobalOp for type
9614 // Falls back to llvmVarTy when not from a global.
9615 llvm::Type *typeToInspect = llvmVarTy;
9616 if (llvmVarTy->isPointerTy()) {
9617 Value baseVar = getBaseValueForTypeLookup(var);
9618 if (Operation *globalOp = getGlobalOpFromValue(baseVar)) {
9619 if (auto gop = dyn_cast<LLVM::GlobalOp>(globalOp))
9620 typeToInspect = moduleTranslation.convertType(gop.getGlobalType());
9621 }
9622 }
9623
9624 llvm::Value *size;
9625 if (auto arrTy = llvm::dyn_cast<llvm::ArrayType>(typeToInspect)) {
9626 llvm::Value *elementCount = builder.getInt64(1);
9627 llvm::Type *currentType = arrTy;
9628 while (auto nestedArrTy = llvm::dyn_cast<llvm::ArrayType>(currentType)) {
9629 elementCount = builder.CreateMul(
9630 elementCount, builder.getInt64(nestedArrTy->getNumElements()));
9631 currentType = nestedArrTy->getElementType();
9632 }
9633 uint64_t elemSizeInBits = dataLayout.getTypeSizeInBits(currentType);
9634 size =
9635 builder.CreateMul(elementCount, builder.getInt64(elemSizeInBits / 8));
9636 } else {
9637 size = builder.getInt64(
9638 dataLayout.getTypeStoreSize(typeToInspect).getFixedValue());
9639 }
9640
9641 uint64_t alignValue =
9642 alignAttr ? alignAttr.value()
9643 : dataLayout.getABITypeAlign(typeToInspect).value();
9644 llvm::Value *alignConst = builder.getInt64(alignValue);
9645 // Align the size: ((size + align - 1) / align) * align
9646 size = builder.CreateAdd(size, builder.getInt64(alignValue - 1), "", true);
9647 size = builder.CreateUDiv(size, alignConst);
9648 size = builder.CreateMul(size, alignConst, "", true);
9649
9650 std::string allocName =
9651 ompBuilder->createPlatformSpecificName({".void.addr"});
9652 llvm::CallInst *allocCall;
9653 if (alignAttr.has_value()) {
9654 allocCall = ompBuilder->createOMPAlignedAlloc(
9655 ompLoc, builder.getInt64(alignAttr.value()), size, allocator,
9656 allocName);
9657 } else {
9658 allocCall =
9659 ompBuilder->createOMPAlloc(ompLoc, size, allocator, allocName);
9660 }
9661 // Record the alloc pointer keyed by the MLIR variable value.
9662 ompIface.registerAllocatedPtr(var, allocCall);
9663 }
9664
9665 return success();
9666}
9667
9668static LogicalResult
9669convertAllocateFreeOp(Operation &opInst, llvm::IRBuilderBase &builder,
9670 LLVM::ModuleTranslation &moduleTranslation,
9671 const OpenMPDialectLLVMIRTranslationInterface &ompIface) {
9672 auto freeOp = cast<omp::AllocateFreeOp>(opInst);
9673 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9674 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
9675
9676 llvm::Value *allocator;
9677 if (auto allocatorVar = freeOp.getAllocator()) {
9678 allocator = moduleTranslation.lookupValue(allocatorVar);
9679 if (allocator->getType()->isIntegerTy())
9680 allocator = builder.CreateIntToPtr(allocator, builder.getPtrTy());
9681 else if (allocator->getType()->isPointerTy())
9682 allocator = builder.CreatePointerBitCastOrAddrSpaceCast(
9683 allocator, builder.getPtrTy());
9684 } else {
9685 allocator = llvm::ConstantPointerNull::get(builder.getPtrTy());
9686 }
9687
9688 // Emit __kmpc_free for each variable in reverse allocation order.
9689 SmallVector<Value> vars = freeOp.getVarList();
9690 for (Value var : llvm::reverse(vars)) {
9691 llvm::Value *allocPtr = ompIface.lookupAllocatedPtr(var);
9692 if (!allocPtr)
9693 return opInst.emitError("omp.allocate_free: no allocation recorded");
9694 ompBuilder->createOMPFree(ompLoc, allocPtr, allocator, "");
9695 }
9696
9697 return success();
9698}
9699
9700static llvm::Function *getOmpTargetFree(llvm::IRBuilderBase &builder,
9701 llvm::Module *llvmModule) {
9702 llvm::Type *ptrTy = builder.getPtrTy(0);
9703 llvm::Type *i32Ty = builder.getInt32Ty();
9704 llvm::Type *voidTy = builder.getVoidTy();
9705 llvm::FunctionType *fnType =
9706 llvm::FunctionType::get(voidTy, {ptrTy, i32Ty}, false);
9707 llvm::Function *func = dyn_cast<llvm::Function>(
9708 llvmModule->getOrInsertFunction("omp_target_free", fnType).getCallee());
9709 return func;
9710}
9711
9712static LogicalResult
9713convertTargetFreeMemOp(Operation &opInst, llvm::IRBuilderBase &builder,
9714 LLVM::ModuleTranslation &moduleTranslation) {
9715 auto freeMemOp = cast<omp::TargetFreeMemOp>(opInst);
9716 if (!freeMemOp)
9717 return failure();
9718
9719 // Get "omp_target_free" function
9720 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
9721 llvm::Function *ompTragetFreeFunc = getOmpTargetFree(builder, llvmModule);
9722 // Get the corresponding device value in llvm
9723 mlir::Value deviceNum = freeMemOp.getDevice();
9724 llvm::Value *llvmDeviceNum = moduleTranslation.lookupValue(deviceNum);
9725 // Get the corresponding heapref value in llvm
9726 mlir::Value heapref = freeMemOp.getHeapref();
9727 llvm::Value *llvmHeapref = moduleTranslation.lookupValue(heapref);
9728 // Convert heapref int to ptr and call "omp_target_free"
9729 llvm::Value *intToPtr =
9730 builder.CreateIntToPtr(llvmHeapref, builder.getPtrTy(0));
9731 builder.CreateCall(ompTragetFreeFunc, {intToPtr, llvmDeviceNum});
9732 return success();
9733}
9734
9735static LogicalResult
9736convertFreeSharedMemOp(omp::FreeSharedMemOp freeMemOp,
9737 llvm::IRBuilderBase &builder,
9738 LLVM::ModuleTranslation &moduleTranslation) {
9739 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9740 llvm::Value *size = getAllocationSize(builder, moduleTranslation, freeMemOp);
9741 ompBuilder->createOMPFreeShared(
9742 builder, moduleTranslation.lookupValue(freeMemOp.getHeapref()), size);
9743 return success();
9744}
9745
9746/// Converts an OpenMP groupprivate operation into LLVM IR.
9747static LogicalResult
9748convertOmpGroupprivate(Operation &opInst, llvm::IRBuilderBase &builder,
9749 LLVM::ModuleTranslation &moduleTranslation) {
9750 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9751 auto groupprivateOp = cast<omp::GroupprivateOp>(opInst);
9752
9753 if (failed(checkImplementationStatus(opInst)))
9754 return failure();
9755
9756 bool isTargetDevice = ompBuilder->Config.isTargetDevice();
9757
9758 // Determine whether group-private storage should be allocated based on
9759 // device_type. When not specified, default to 'any' (allocate on both).
9760 bool shouldAllocate = true;
9761 switch (groupprivateOp.getDeviceType().value_or(
9762 mlir::omp::DeclareTargetDeviceType::any)) {
9763 case mlir::omp::DeclareTargetDeviceType::host:
9764 shouldAllocate = !isTargetDevice;
9765 break;
9766 case mlir::omp::DeclareTargetDeviceType::nohost:
9767 shouldAllocate = isTargetDevice;
9768 break;
9769 case mlir::omp::DeclareTargetDeviceType::any:
9770 shouldAllocate = true;
9771 break;
9772 }
9773
9774 // Look up the global variable directly by symbol name.
9776 &opInst, groupprivateOp.getSymNameAttr());
9777 if (!global)
9778 return opInst.emitError()
9779 << "expected symbol '" << groupprivateOp.getSymName()
9780 << "' to reference an LLVM global variable";
9781
9782 llvm::GlobalValue *globalValue = moduleTranslation.lookupGlobal(global);
9783 llvm::Type *varType = moduleTranslation.convertType(global.getType());
9784 std::string varName = globalValue->getName().str();
9785
9786 llvm::Value *resultPtr;
9787 if (shouldAllocate && isTargetDevice) {
9788 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
9789 llvm::Triple targetTriple(llvmModule->getTargetTriple());
9790 unsigned sharedAddressSpace;
9791 if (targetTriple.isAMDGCN())
9792 sharedAddressSpace = llvm::AMDGPUAS::LOCAL_ADDRESS;
9793 else if (targetTriple.isNVPTX())
9794 sharedAddressSpace = llvm::NVPTXAS::ADDRESS_SPACE_SHARED;
9795 else
9796 return opInst.emitError() << "groupprivate is not supported for target: "
9797 << targetTriple.str();
9798 llvm::GlobalVariable *sharedVar = new llvm::GlobalVariable(
9799 *llvmModule, varType, /*isConstant=*/false,
9800 llvm::GlobalValue::InternalLinkage, llvm::PoisonValue::get(varType),
9801 varName, /*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal,
9802 sharedAddressSpace,
9803 /*isExternallyInitialized=*/false);
9804 resultPtr = sharedVar;
9805 } else {
9806 if (shouldAllocate && !isTargetDevice)
9807 opInst.emitWarning("groupprivate directive is currently ignored on the "
9808 "host, using original global");
9809 resultPtr = globalValue;
9810 }
9811
9812 moduleTranslation.mapValue(opInst.getResult(0), resultPtr);
9813 return success();
9814}
9815
9816/// Given an OpenMP MLIR operation, create the corresponding LLVM IR (including
9817/// OpenMP runtime calls).
9818LogicalResult OpenMPDialectLLVMIRTranslationInterface::convertOperation(
9819 Operation *op, llvm::IRBuilderBase &builder,
9820 LLVM::ModuleTranslation &moduleTranslation) const {
9821 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9822
9823 if (ompBuilder->Config.isTargetDevice() &&
9824 !isa<omp::TargetOp, omp::MapInfoOp, omp::TerminatorOp, omp::YieldOp>(
9825 op) &&
9826 isHostDeviceOp(op))
9827 return op->emitOpError() << "unsupported host op found in device";
9828
9829 // For each loop, introduce one stack frame to hold loop information. Ensure
9830 // this is only done for the outermost loop wrapper to prevent introducing
9831 // multiple stack frames for a single loop. Initially set to null, the loop
9832 // information structure is initialized during translation of the nested
9833 // omp.loop_nest operation, making it available to translation of all loop
9834 // wrappers after their body has been successfully translated.
9835 bool isOutermostLoopWrapper =
9836 isa_and_present<omp::LoopWrapperInterface>(op) &&
9837 !dyn_cast_if_present<omp::LoopWrapperInterface>(op->getParentOp());
9838
9839 // The TASKLOOP construct is implemented with an outer taskloop.context
9840 // operation which is not a loop wrapper, containing an inner taskloop
9841 // operation which is a loop wrapper. The stack frame should be pushed when
9842 // translating the outer taskloop.context and popped when translating the
9843 // inner taskloop which is a loop wrapper. We need access to the loop
9844 // information in the outer taskloop context so we need to create it and pop
9845 // it around the taskloop context not the inner loop wrapper.
9846 if (isa<omp::TaskloopContextOp>(op))
9847 isOutermostLoopWrapper = true;
9848 else if (isa<omp::TaskloopWrapperOp>(op))
9849 isOutermostLoopWrapper = false;
9850
9851 if (isOutermostLoopWrapper)
9852 moduleTranslation.stackPush<OpenMPLoopInfoStackFrame>();
9853
9854 auto result =
9855 llvm::TypeSwitch<Operation *, LogicalResult>(op)
9856 .Case([&](omp::BarrierOp op) -> LogicalResult {
9858 return failure();
9859
9860 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
9861 ompBuilder->createBarrier(builder, llvm::omp::OMPD_barrier);
9862 LogicalResult res = handleError(afterIP, *op);
9863 if (res.succeeded()) {
9864 // If the barrier generated a cancellation check, the insertion
9865 // point might now need to be changed to a new continuation block
9866 builder.restoreIP(*afterIP);
9867 }
9868 return res;
9869 })
9870 .Case([&](omp::TaskyieldOp op) {
9872 return failure();
9873
9874 ompBuilder->createTaskyield(builder);
9875 return success();
9876 })
9877 .Case([&](omp::FlushOp op) {
9879 return failure();
9880
9881 // No support in Openmp runtime function (__kmpc_flush) to accept
9882 // the argument list.
9883 // OpenMP standard states the following:
9884 // "An implementation may implement a flush with a list by ignoring
9885 // the list, and treating it the same as a flush without a list."
9886 //
9887 // The argument list is discarded so that, flush with a list is
9888 // treated same as a flush without a list.
9889 ompBuilder->createFlush(builder);
9890 return success();
9891 })
9892 .Case([&](omp::ErrorOp op) {
9894 return failure();
9895
9896 llvm::Value *message = nullptr;
9897 if (mlir::Value messageExpr = op.getMessageExpr())
9898 message = moduleTranslation.lookupValue(messageExpr);
9899 else if (std::optional<StringRef> msg = op.getMessage();
9900 msg && !msg->empty())
9901 message = builder.CreateGlobalString(*msg);
9902 ompBuilder->createError(
9903 llvm::OpenMPIRBuilder::LocationDescription(builder),
9904 op.getSeverity() == omp::ClauseSeverity::fatal, message);
9905 return success();
9906 })
9907 .Case([&](omp::ParallelOp op) {
9908 return convertOmpParallel(op, builder, moduleTranslation);
9909 })
9910 .Case([&](omp::MaskedOp) {
9911 return convertOmpMasked(*op, builder, moduleTranslation);
9912 })
9913 .Case([&](omp::MasterOp) {
9914 return convertOmpMaster(*op, builder, moduleTranslation);
9915 })
9916 .Case([&](omp::CriticalOp) {
9917 return convertOmpCritical(*op, builder, moduleTranslation);
9918 })
9919 .Case([&](omp::OrderedRegionOp) {
9920 return convertOmpOrderedRegion(*op, builder, moduleTranslation);
9921 })
9922 .Case([&](omp::OrderedOp) {
9923 return convertOmpOrdered(*op, builder, moduleTranslation);
9924 })
9925 .Case([&](omp::WsloopOp) {
9926 return convertOmpWsloop(*op, builder, moduleTranslation);
9927 })
9928 .Case([&](omp::SimdOp) {
9929 return convertOmpSimd(*op, builder, moduleTranslation);
9930 })
9931 .Case([&](omp::AtomicReadOp) {
9932 return convertOmpAtomicRead(*op, builder, moduleTranslation);
9933 })
9934 .Case([&](omp::AtomicWriteOp) {
9935 return convertOmpAtomicWrite(*op, builder, moduleTranslation);
9936 })
9937 .Case([&](omp::AtomicUpdateOp op) {
9938 return convertOmpAtomicUpdate(op, builder, moduleTranslation);
9939 })
9940 .Case([&](omp::AtomicCaptureOp op) {
9941 return convertOmpAtomicCapture(op, builder, moduleTranslation);
9942 })
9943 .Case([&](omp::AtomicCompareOp op) {
9944 return convertOmpAtomicCompare(op, builder, moduleTranslation);
9945 })
9946 .Case([&](omp::CancelOp op) {
9947 return convertOmpCancel(op, builder, moduleTranslation);
9948 })
9949 .Case([&](omp::CancellationPointOp op) {
9950 return convertOmpCancellationPoint(op, builder, moduleTranslation);
9951 })
9952 .Case([&](omp::SectionsOp) {
9953 return convertOmpSections(*op, builder, moduleTranslation);
9954 })
9955 .Case([&](omp::ScopeOp op) {
9956 return convertOmpScope(op, builder, moduleTranslation);
9957 })
9958 .Case([&](omp::SingleOp op) {
9959 return convertOmpSingle(op, builder, moduleTranslation);
9960 })
9961 .Case([&](omp::TeamsOp op) {
9962 return convertOmpTeams(op, builder, moduleTranslation);
9963 })
9964 .Case([&](omp::TaskOp op) {
9965 return convertOmpTaskOp(op, builder, moduleTranslation);
9966 })
9967 .Case([&](omp::TaskloopWrapperOp op) {
9968 return convertOmpTaskloopWrapperOp(op, builder, moduleTranslation);
9969 })
9970 .Case([&](omp::TaskloopContextOp op) {
9971 return convertOmpTaskloopContextOp(op, builder, moduleTranslation);
9972 })
9973 .Case([&](omp::TaskgroupOp op) {
9974 return convertOmpTaskgroupOp(op, builder, moduleTranslation);
9975 })
9976 .Case([&](omp::TaskwaitOp op) {
9977 return convertOmpTaskwaitOp(op, builder, moduleTranslation);
9978 })
9979 .Case([&](omp::InteropInitOp op) {
9980 return convertOmpInteropInitOp(op, builder, moduleTranslation);
9981 })
9982 .Case([&](omp::InteropDestroyOp op) {
9983 return convertOmpInteropDestroyOp(op, builder, moduleTranslation);
9984 })
9985 .Case([&](omp::InteropUseOp op) {
9986 return convertOmpInteropUseOp(op, builder, moduleTranslation);
9987 })
9988 .Case<omp::YieldOp, omp::TerminatorOp, omp::DeclareMapperOp,
9989 omp::DeclareMapperInfoOp, omp::DeclareReductionOp,
9990 omp::CriticalDeclareOp>([](auto op) {
9991 // `yield` and `terminator` can be just omitted. The block structure
9992 // was created in the region that handles their parent operation.
9993 // `declare_reduction` will be used by reductions and is not
9994 // converted directly, skip it.
9995 // `declare_mapper` and `declare_mapper.info` are handled whenever
9996 // they are referred to through a `map` clause.
9997 // `critical.declare` is only used to declare names of critical
9998 // sections which will be used by `critical` ops and hence can be
9999 // ignored for lowering. The OpenMP IRBuilder will create unique
10000 // name for critical section names.
10001 return success();
10002 })
10003 .Case([&](omp::ThreadprivateOp) {
10004 return convertOmpThreadprivate(*op, builder, moduleTranslation);
10005 })
10006 .Case<omp::TargetDataOp, omp::TargetEnterDataOp,
10007 omp::TargetExitDataOp, omp::TargetUpdateOp>([&](auto op) {
10008 return convertOmpTargetData(op, builder, moduleTranslation);
10009 })
10010 .Case([&](omp::TargetOp) {
10011 return convertOmpTarget(*op, builder, moduleTranslation);
10012 })
10013 .Case([&](omp::DistributeOp) {
10014 return convertOmpDistribute(*op, builder, moduleTranslation);
10015 })
10016 .Case([&](omp::LoopNestOp) {
10017 return convertOmpLoopNest(*op, builder, moduleTranslation);
10018 })
10019 .Case<omp::MapInfoOp, omp::MapBoundsOp, omp::PrivateClauseOp,
10020 omp::AffinityEntryOp, omp::IteratorOp>([&](auto op) {
10021 // No-op, should be handled by relevant owning operations e.g.
10022 // TargetOp, TargetEnterDataOp, TargetExitDataOp, TargetDataOp
10023 // etc. and then discarded
10024 return success();
10025 })
10026 .Case([&](omp::NewCliOp op) {
10027 // Meta-operation: Doesn't do anything by itself, but used to
10028 // identify a loop.
10029 return success();
10030 })
10031 .Case([&](omp::CanonicalLoopOp op) {
10032 return convertOmpCanonicalLoopOp(op, builder, moduleTranslation);
10033 })
10034 .Case([&](omp::UnrollHeuristicOp op) {
10035 // FIXME: Handling omp.unroll_heuristic as an executable requires
10036 // that the generator (e.g. omp.canonical_loop) has been seen first.
10037 // For construct that require all codegen to occur inside a callback
10038 // (e.g. OpenMPIRBilder::createParallel), all codegen of that
10039 // contained region including their transformations must occur at
10040 // the omp.canonical_loop.
10041 return applyUnrollHeuristic(op, builder, moduleTranslation);
10042 })
10043 .Case([&](omp::UnrollPartialOp op) {
10044 return applyUnrollPartial(op, builder, moduleTranslation);
10045 })
10046 .Case([&](omp::TileOp op) {
10047 return applyTile(op, builder, moduleTranslation);
10048 })
10049 .Case([&](omp::FuseOp op) {
10050 return applyFuse(op, builder, moduleTranslation);
10051 })
10052 .Case([&](omp::TargetAllocMemOp) {
10053 return convertTargetAllocMemOp(*op, builder, moduleTranslation);
10054 })
10055 .Case([&](omp::TargetFreeMemOp) {
10056 return convertTargetFreeMemOp(*op, builder, moduleTranslation);
10057 })
10058 .Case([&](omp::AllocateDirOp) {
10059 return convertAllocateDirOp(*op, builder, moduleTranslation, *this);
10060 })
10061 .Case([&](omp::AllocateFreeOp) {
10062 return convertAllocateFreeOp(*op, builder, moduleTranslation,
10063 *this);
10064 })
10065 .Case([&](omp::AllocSharedMemOp op) {
10066 return convertAllocSharedMemOp(op, builder, moduleTranslation);
10067 })
10068 .Case([&](omp::FreeSharedMemOp op) {
10069 return convertFreeSharedMemOp(op, builder, moduleTranslation);
10070 })
10071 .Case([&](omp::GroupprivateOp) {
10072 return convertOmpGroupprivate(*op, builder, moduleTranslation);
10073 })
10074 .Default([&](Operation *inst) {
10075 return inst->emitError()
10076 << "not yet implemented: " << inst->getName();
10077 });
10078
10079 if (isOutermostLoopWrapper)
10080 moduleTranslation.stackPop();
10081
10082 return result;
10083}
10084
10086 registry.insert<omp::OpenMPDialect>();
10087 registry.addExtension(+[](MLIRContext *ctx, omp::OpenMPDialect *dialect) {
10088 dialect->addInterfaces<OpenMPDialectLLVMIRTranslationInterface>();
10089 });
10090}
10091
10093 DialectRegistry registry;
10095 context.appendDialectRegistry(registry);
10096}
for(Operation *op :ops)
return success()
lhs
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
ArrayAttr()
if(!isCopyOut)
static mlir::LogicalResult buildDependData(OperandRange dependVars, std::optional< ArrayAttr > dependKinds, OperandRange dependIterated, std::optional< ArrayAttr > dependIteratedKinds, llvm::IRBuilderBase &builder, mlir::LLVM::ModuleTranslation &moduleTranslation, llvm::OpenMPIRBuilder::DependenciesInfo &taskDeps)
static void handleDeclareTargetMapVar(MapInfoData &mapData, LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder, llvm::Function *func)
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 llvm::Value * findAssociatedValue(Value privateVar, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, llvm::DenseMap< Value, Value > *mappedPrivateVars=nullptr)
Return the llvm::Value * corresponding to the privateVar that is being privatized....
static ArrayRef< bool > getIsByRef(std::optional< ArrayRef< bool > > attr)
static llvm::Expected< llvm::Value * > lookupOrTranslatePureValue(Value value, LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder)
Look up the given value in the mapping, and if it's not there, translate its defining operation at th...
static LogicalResult allocReductionVars(T op, ArrayRef< BlockArgument > reductionArgs, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, const llvm::OpenMPIRBuilder::InsertPointTy &allocaIP, SmallVectorImpl< omp::DeclareReductionOp > &reductionDecls, SmallVectorImpl< llvm::Value * > &privateReductionVariables, DenseMap< Value, llvm::Value * > &reductionVariableMap, SmallVectorImpl< DeferredStore > &deferredStores, llvm::ArrayRef< bool > isByRefs)
Allocate space for privatized reduction variables.
static void emitTaskReductionModifierFini(bool isWorksharing, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Emits __kmpc_task_reduction_modifier_fini(loc, gtid, is_ws) at the current builder insertion point,...
static LogicalResult convertOmpInteropUseOp(omp::InteropUseOp useOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static LogicalResult convertOmpTaskwaitOp(omp::TaskwaitOp twOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static LogicalResult collectAndValidateTaskloopRedDecls(Operation *contextOp, std::optional< ArrayAttr > syms, StringRef opName, StringRef clauseName, SmallVectorImpl< omp::DeclareReductionOp > &out)
Look up and validate the declare_reduction ops referenced by a reduction-like clause on the omp....
static LogicalResult convertOmpLoopNest(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP loop nest into LLVM IR using OpenMPIRBuilder.
static mlir::LogicalResult fillIteratorLoop(mlir::omp::IteratorOp itersOp, llvm::IRBuilderBase &builder, mlir::LLVM::ModuleTranslation &moduleTranslation, IteratorInfo &iterInfo, llvm::StringRef loopName, IteratorStoreEntryTy genStoreEntry)
static llvm::Expected< llvm::BasicBlock * > allocatePrivateVars(T op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, PrivateVarsInfo &privateVarsInfo, const llvm::OpenMPIRBuilder::InsertPointTy &allocaIP, llvm::DenseMap< Value, Value > *mappedPrivateVars=nullptr)
Allocate and initialize delayed private variables. Returns the basic block which comes after all of t...
static void createAlteredByCaptureMap(MapInfoData &mapData, LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder)
static LogicalResult convertOmpTaskOp(omp::TaskOp taskOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP task construct into LLVM IR using OpenMPIRBuilder.
static void genMapInfos(llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, DataLayout &dl, MapInfosTy &combinedInfo, MapInfoData &mapData, TargetDirectiveEnumTy targetDirective)
static llvm::AtomicOrdering convertAtomicOrdering(std::optional< omp::ClauseMemoryOrderKind > ao)
Convert an Atomic Ordering attribute to llvm::AtomicOrdering.
static LogicalResult convertOmpInteropInitOp(omp::InteropInitOp initOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static void setInsertPointForPossiblyEmptyBlock(llvm::IRBuilderBase &builder, llvm::BasicBlock *block=nullptr)
llvm::function_ref< void(llvm::Value *linearIV, mlir::omp::YieldOp yield)> IteratorStoreEntryTy
static llvm::Function * emitTaskReductionInitFn(omp::DeclareReductionOp decl, StringRef baseName, LLVM::ModuleTranslation &moduleTranslation)
Build an outlined init helper for a task_reduction declare_reduction op. Signature: void(ptr priv,...
static LogicalResult convertOmpSections(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static LogicalResult applyUnrollPartial(omp::UnrollPartialOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Apply a #pragma omp unroll partial / !$omp unroll partial transformation using the OpenMPIRBuilder.
static LogicalResult convertOmpCritical(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP 'critical' operation into LLVM IR using OpenMPIRBuilder.
static LogicalResult convertTargetAllocMemOp(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static omp::DistributeOp getDistributeCapturingTeamsReduction(omp::TeamsOp teamsOp)
static LogicalResult convertOmpCanonicalLoopOp(omp::CanonicalLoopOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Convert an omp.canonical_loop to LLVM-IR.
static LogicalResult convertOmpTargetData(Operation *op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static std::optional< int64_t > extractConstInteger(Value value)
If the given value is defined by an llvm.mlir.constant operation and it is of an integer type,...
static llvm::Expected< llvm::Value * > initPrivateVar(llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, omp::PrivateClauseOp &privDecl, llvm::Value *nonPrivateVar, BlockArgument &blockArg, llvm::Value *llvmPrivateVar, llvm::BasicBlock *privInitBlock, llvm::DenseMap< Value, Value > *mappedPrivateVars=nullptr)
Initialize a single (first)private variable. You probably want to use allocateAndInitPrivateVars inst...
static mlir::LogicalResult buildAffinityData(mlir::omp::TaskOp &taskOp, llvm::IRBuilderBase &builder, mlir::LLVM::ModuleTranslation &moduleTranslation, llvm::OpenMPIRBuilder::AffinityData &ad)
static LogicalResult allocAndInitializeReductionVars(OP op, ArrayRef< BlockArgument > reductionArgs, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, llvm::OpenMPIRBuilder::InsertPointTy &allocaIP, SmallVectorImpl< omp::DeclareReductionOp > &reductionDecls, SmallVectorImpl< llvm::Value * > &privateReductionVariables, DenseMap< Value, llvm::Value * > &reductionVariableMap, llvm::ArrayRef< bool > isByRef)
static LogicalResult convertOmpSimd(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP simd loop into LLVM IR using OpenMPIRBuilder.
static LogicalResult convertOmpInteropDestroyOp(omp::InteropDestroyOp destroyOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static LogicalResult convertOmpDistribute(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static llvm::Value * getAllocationSize(llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, T op)
static llvm::Function * getOmpTargetAlloc(llvm::IRBuilderBase &builder, llvm::Module *llvmModule)
static llvm::omp::OMPDynGroupprivateFallbackType getDynGroupprivateFallbackType(omp::FallbackModifierAttr fallbackAttr)
static llvm::Expected< llvm::Function * > emitUserDefinedMapper(Operation *declMapperOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, llvm::StringRef mapperFuncName, TargetDirectiveEnumTy targetDirective)
static LogicalResult convertOmpOrdered(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP 'ordered' operation into LLVM IR using OpenMPIRBuilder.
static LogicalResult cleanupPrivateVars(T op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, Location loc, PrivateVarsInfo &privateVarsInfo)
static void processMapWithMembersOf(LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder &ompBuilder, DataLayout &dl, MapInfosTy &combinedInfo, MapInfoData &mapData, uint64_t mapDataIndex, TargetDirectiveEnumTy targetDirective)
static LogicalResult convertOmpMasked(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP 'masked' operation into LLVM IR using OpenMPIRBuilder.
static llvm::AtomicRMWInst::BinOp convertBinOpToAtomic(Operation &op)
Converts an LLVM dialect binary operation to the corresponding enum value for atomicrmw supported bin...
static LogicalResult convertOmpCancel(omp::CancelOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static int getMapDataMemberIdx(MapInfoData &mapData, omp::MapInfoOp memberOp)
allocatedType moduleTranslation static convertType(allocatedType) LogicalResult inlineOmpRegionCleanup(llvm::SmallVectorImpl< Region * > &cleanupRegions, llvm::ArrayRef< llvm::Value * > privateVariables, LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder, StringRef regionName, bool shouldLoadCleanupRegionArg=true)
handling of DeclareReductionOp's cleanup region
static LogicalResult applyFuse(omp::FuseOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Apply a #pragma omp fuse / !$omp fuse transformation using the OpenMPIRBuilder.
static llvm::Value * materializeRegionArgValue(llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, BlockArgument regionArg, llvm::Value *value)
static LogicalResult convertOmpScope(omp::ScopeOp &scopeOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP scope construct into LLVM IR.
static llvm::Value * getSizeInBytes(DataLayout &dl, const mlir::Type &type, Operation *clauseOp, llvm::Value *basePointer, llvm::Type *baseType, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static llvm::Error initPrivateVars(llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, PrivateVarsInfo &privateVarsInfo, llvm::DenseMap< Value, Value > *mappedPrivateVars=nullptr)
static LogicalResult convertAllocSharedMemOp(omp::AllocSharedMemOp allocMemOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static llvm::CanonicalLoopInfo * findCurrentLoopInfo(LLVM::ModuleTranslation &moduleTranslation)
Find the loop information structure for the loop nest being translated.
static OwningReductionGen makeReductionGen(omp::DeclareReductionOp decl, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Create an OpenMPIRBuilder-compatible reduction generator for the given reduction declaration.
static std::vector< llvm::Value * > calculateBoundsOffset(LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder, bool isArrayTy, OperandRange bounds)
This function calculates the array/pointer offset for map data provided with bounds operations,...
static void storeAffinityEntry(llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder &ompBuilder, llvm::Value *affinityList, llvm::Value *index, llvm::Value *addr, llvm::Value *len)
static LogicalResult convertOmpParallel(omp::ParallelOp opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts the OpenMP parallel operation to LLVM IR.
static void pushCancelFinalizationCB(SmallVectorImpl< llvm::UncondBrInst * > &cancelTerminators, llvm::IRBuilderBase &llvmBuilder, llvm::OpenMPIRBuilder &ompBuilder, mlir::Operation *op, llvm::omp::Directive cancelDirective)
Shared implementation of a callback which adds a termiator for the new block created for the branch t...
static LogicalResult inlineConvertOmpRegions(Region &region, StringRef blockName, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, SmallVectorImpl< llvm::Value * > *continuationBlockArgs=nullptr)
Translates the blocks contained in the given region and appends them to at the current insertion poin...
static void getTargetEntryUniqueInfo(llvm::TargetRegionEntryInfo &targetInfo, omp::TargetOp targetOp, llvm::OpenMPIRBuilder &ompBuilder, llvm::vfs::FileSystem &vfs, llvm::StringRef parentName="")
static LogicalResult convertOmpThreadprivate(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP Threadprivate operation into LLVM IR using OpenMPIRBuilder.
static omp::PrivateClauseOp findPrivatizer(Operation *from, SymbolRefAttr symbolName)
Looks up from the operation from and returns the PrivateClauseOp with name symbolName.
static LogicalResult 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 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::omp::RTLDependenceKindTy convertDependKind(mlir::omp::ClauseTaskDepend kind)
static void initTargetRuntimeAttrs(llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, omp::TargetOp targetOp, Operation *capturedOp, llvm::OpenMPIRBuilder::TargetKernelRuntimeAttrs &attrs)
Gather LLVM runtime values for all clauses evaluated in the host that are passed to the kernel invoca...
static LogicalResult convertOmpTeams(omp::TeamsOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static Value getBaseValueForTypeLookup(Value value)
static bool isHostDeviceOp(Operation *op)
static LogicalResult convertDeclareTargetAttr(Operation *op, mlir::omp::DeclareTargetAttr attribute, llvm::OpenMPIRBuilder *ompBuilder, LLVM::ModuleTranslation &moduleTranslation)
static bool isDeclareTargetLink(Value value)
static LogicalResult convertFlagsAttr(Operation *op, mlir::omp::FlagsAttr attribute, LLVM::ModuleTranslation &moduleTranslation)
Lowers the FlagsAttr which is applied to the module when offloading. This attribute contains OpenMP R...
static bool checkIfPointerMap(omp::MapInfoOp mapOp)
static LogicalResult applyTile(omp::TileOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Apply a #pragma omp tile / !$omp tile transformation using the OpenMPIRBuilder.
static LogicalResult convertAllocateFreeOp(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, const OpenMPDialectLLVMIRTranslationInterface &ompIface)
static llvm::Function * getOmpTargetFree(llvm::IRBuilderBase &builder, llvm::Module *llvmModule)
static LogicalResult convertOmpTaskgroupOp(omp::TaskgroupOp tgOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP taskgroup construct into LLVM IR using OpenMPIRBuilder.
static void collectMapDataFromMapOperands(MapInfoData &mapData, SmallVectorImpl< Value > &mapVars, LLVM::ModuleTranslation &moduleTranslation, DataLayout &dl, llvm::IRBuilderBase &builder, ArrayRef< Value > useDevPtrOperands={}, ArrayRef< Value > useDevAddrOperands={}, ArrayRef< Value > hasDevAddrOperands={})
static void extractAtomicControlFlags(omp::AtomicUpdateOp atomicUpdateOp, bool &isIgnoreDenormalMode, bool &isFineGrainedMemory, bool &isRemoteMemory)
static Operation * genLoop(CodegenEnv &env, OpBuilder &builder, LoopId curr, unsigned numCases, bool needsUniv, ArrayRef< TensorLevel > tidLvls)
Generates a for-loop or a while-loop, depending on whether it implements singleton iteration or co-it...
#define MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(CLASS_NAME)
Definition TypeID.h:331
#define div(a, b)
Attributes are known-constant values of operations.
Definition Attributes.h:25
This class represents an argument of a Block.
Definition Value.h:306
Block represents an ordered list of Operations.
Definition Block.h:33
BlockArgument getArgument(unsigned i)
Definition Block.h:153
unsigned getNumArguments()
Definition Block.h:152
OpListType & getOperations()
Definition Block.h:161
Operation & front()
Definition Block.h:177
Operation & back()
Definition Block.h:176
Operation * getTerminator()
Get the terminator operation of this block.
Definition Block.cpp:249
iterator_range< iterator > without_terminator()
Return an iterator range over the operation within this block excluding the terminator operation at t...
Definition Block.h:236
iterator begin()
Definition Block.h:167
The main mechanism for performing data layout queries.
llvm::TypeSize getTypeSize(Type t) const
Returns the size of the given type in the current scope.
llvm::TypeSize getTypeSizeInBits(Type t) const
Returns the size in bits of the given type in the current scope.
The DialectRegistry maps a dialect namespace to a constructor for the matching dialect.
bool addExtension(TypeID extensionID, std::unique_ptr< DialectExtensionBase > extension)
Add the given extension to the registry.
An instance of this location represents a tuple of file, line number, and column number.
Definition Location.h:174
Implementation class for module translation.
llvm::BasicBlock * lookupBlock(Block *block) const
Finds an LLVM IR basic block that corresponds to the given MLIR block.
WalkResult stackWalk(llvm::function_ref< WalkResult(T &)> callback)
Calls callback for every ModuleTranslation stack frame of type T starting from the top of the stack.
void stackPush(Args &&...args)
Creates a stack frame of type T on ModuleTranslation stack.
LogicalResult convertBlock(Block &bb, bool ignoreArguments, llvm::IRBuilderBase &builder)
Translates the contents of the given block to LLVM IR using this translator.
SmallVector< llvm::Value * > lookupValues(ValueRange values)
Looks up remapped a list of remapped values.
void mapFunction(StringRef name, llvm::Function *func)
Stores the mapping between a function name and its LLVM IR representation.
llvm::Value * lookupValue(Value value) const
Finds an LLVM IR value corresponding to the given MLIR value.
void invalidateOmpLoop(omp::NewCliOp mlir)
Mark an OpenMP loop as having been consumed.
SymbolTableCollection & symbolTable()
llvm::Type * convertType(Type type)
Converts the type from MLIR LLVM dialect to LLVM.
llvm::OpenMPIRBuilder * getOpenMPBuilder()
Returns the OpenMP IR builder associated with the LLVM IR module being constructed.
llvm::vfs::FileSystem & getFileSystem()
Returns the virtual filesystem to use for file operations.
void mapOmpLoop(omp::NewCliOp mlir, llvm::CanonicalLoopInfo *llvm)
Map an MLIR OpenMP dialect CanonicalLoopInfo to its lowered LLVM-IR OpenMPIRBuilder CanonicalLoopInfo...
llvm::GlobalValue * lookupGlobal(Operation *op)
Finds an LLVM IR global value that corresponds to the given MLIR operation defining a global value.
SaveStateStack< T, ModuleTranslation > SaveStack
RAII object calling stackPush/stackPop on construction/destruction.
LogicalResult convertOperation(Operation &op, llvm::IRBuilderBase &builder)
Converts the given MLIR operation into LLVM IR using this translator.
llvm::Function * lookupFunction(StringRef name) const
Finds an LLVM IR function by its name.
void mapBlock(Block *mlir, llvm::BasicBlock *llvm)
Stores the mapping between an MLIR block and LLVM IR basic block.
llvm::Module * getLLVMModule()
Returns the LLVM module in which the IR is being constructed.
void stackPop()
Pops the last element from the ModuleTranslation stack.
void forgetMapping(Region &region)
Removes the mapping for blocks contained in the region and values defined in these blocks.
void mapValue(Value mlir, llvm::Value *llvm)
Stores the mapping between an MLIR value and its LLVM IR counterpart.
llvm::CanonicalLoopInfo * lookupOMPLoop(omp::NewCliOp mlir) const
Find the LLVM-IR loop that represents an MLIR loop.
llvm::LLVMContext & getLLVMContext() const
Returns the LLVM context in which the IR is being constructed.
Utility class to translate MLIR LLVM dialect types to LLVM IR.
Definition TypeToLLVM.h:39
unsigned getPreferredAlignment(Type type, const llvm::DataLayout &layout)
Returns the preferred alignment for the type given the data layout.
T findInstanceOf()
Return an instance of the given location type if one is nested under the current location.
Definition Location.h:45
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
void appendDialectRegistry(const DialectRegistry &registry)
Append the contents of the given dialect registry to the registry associated with this context.
StringAttr getName() const
Return the name of the attribute.
Attribute getValue() const
Return the value of the attribute.
Definition Attributes.h:179
This class implements the operand iterators for the Operation class.
Definition ValueRange.h:44
StringAttr getIdentifier() const
Return the name of this operation as a StringAttr.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Region & getRegion(unsigned index)
Returns the region held by this operation at position 'index'.
Definition Operation.h:711
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:699
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:822
user_range getUsers()
Returns a range of all users.
Definition Operation.h:898
result_range getResults()
Definition Operation.h:440
MLIRContext * getContext()
Return the context this operation is associated with.
Definition Operation.h:233
InFlightDiagnostic emitOpError(const Twine &message={})
Emit an error with the op name prefixed, like "'dim' op " which is convenient for verifiers.
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:429
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
Block & front()
Definition Region.h:65
BlockArgListType getArguments()
Definition Region.h:94
bool empty()
Definition Region.h:60
unsigned getNumArguments()
Definition Region.h:136
iterator begin()
Definition Region.h:55
Operation * getParentOp()
Return the parent operation this region is attached to.
Definition Region.h:213
BlockListType & getBlocks()
Definition Region.h:45
bool hasOneBlock()
Return true if this region has exactly one block.
Definition Region.h:68
Concrete CRTP base class for StateStack frames.
Definition StateStack.h:47
@ Private
The symbol is private and may only be referenced by SymbolRefAttrs local to the operations within the...
Definition SymbolTable.h:97
static Operation * lookupNearestSymbolFrom(Operation *from, StringAttr symbol)
Returns the operation registered with the given symbol name within the closest parent operation of,...
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class 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:66
bool allocaUsesRequireSharedMem(Value alloc)
Check whether the value representing an allocation, assumed to have been defined in a shared device c...
Definition Utils.cpp:51
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
auto getDims(VectorType vType)
Returns a range over the dims (size and scalability) of a VectorType.
Include the generated interface declarations.
SetVector< Block * > getBlocksSortedByDominance(Region &region)
Gets a list of blocks that is sorted according to dominance.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:307
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
bool isPure(Operation *op)
Returns true if the given operation is pure, i.e., is speculatable that does not touch memory.
void registerOpenMPDialectTranslation(DialectRegistry &registry)
Register the OpenMP dialect and the translation from it to the LLVM IR in the given registry;.
llvm::SetVector< T, Vector, Set, N > SetVector
Definition LLVM.h:125
SmallVector< Loops, 8 > tile(ArrayRef< scf::ForOp > forOps, ArrayRef< Value > sizes, ArrayRef< scf::ForOp > targets)
Performs tiling fo imperfectly nested loops (with interchange) by strip-mining the forOps by sizes an...
Definition Utils.cpp:1330
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
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
SmallVector< llvm::Value * > llvmVars
Eliminates variable at the specified position using Fourier-Motzkin variable elimination.