MLIR 23.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
154public:
155 // Register type for the linear variables
156 void registerType(LLVM::ModuleTranslation &moduleTranslation,
157 mlir::Attribute &ty) {
158 linearVarTypes.push_back(moduleTranslation.convertType(
159 mlir::cast<mlir::TypeAttr>(ty).getValue()));
160 }
161
162 // Allocate space for linear variabes
163 void createLinearVar(llvm::IRBuilderBase &builder,
164 LLVM::ModuleTranslation &moduleTranslation,
165 llvm::Value *linearVar, int idx) {
166 linearPreconditionVars.push_back(
167 builder.CreateAlloca(linearVarTypes[idx], nullptr, ".linear_var"));
168 llvm::Value *linearLoopBodyTemp =
169 builder.CreateAlloca(linearVarTypes[idx], nullptr, ".linear_result");
170 linearOrigVal.push_back(linearVar);
171 linearLoopBodyTemps.push_back(linearLoopBodyTemp);
172 }
173
174 // Initialize linear step
175 inline void initLinearStep(LLVM::ModuleTranslation &moduleTranslation,
176 mlir::Value &linearStep) {
177 linearSteps.push_back(moduleTranslation.lookupValue(linearStep));
178 }
179
180 // Emit IR for initialization of linear variables
181 void initLinearVar(llvm::IRBuilderBase &builder,
182 LLVM::ModuleTranslation &moduleTranslation,
183 llvm::BasicBlock *loopPreHeader) {
184 builder.SetInsertPoint(loopPreHeader->getTerminator());
185 for (size_t index = 0; index < linearOrigVal.size(); index++) {
186 llvm::LoadInst *linearVarLoad =
187 builder.CreateLoad(linearVarTypes[index], linearOrigVal[index]);
188 builder.CreateStore(linearVarLoad, linearPreconditionVars[index]);
189 }
190 }
191
192 // Emit IR for updating Linear variables
193 void updateLinearVar(llvm::IRBuilderBase &builder, llvm::BasicBlock *loopBody,
194 llvm::Value *loopInductionVar) {
195 builder.SetInsertPoint(loopBody->getTerminator());
196 for (size_t index = 0; index < linearPreconditionVars.size(); index++) {
197 llvm::Type *linearVarType = linearVarTypes[index];
198 llvm::Value *iv = loopInductionVar;
199 llvm::Value *step = linearSteps[index];
200
201 if (!iv->getType()->isIntegerTy())
202 llvm_unreachable("OpenMP loop induction variable must be an integer "
203 "type");
204
205 if (linearVarType->isIntegerTy()) {
206 // Integer path: normalize all arithmetic to linearVarType
207 iv = builder.CreateSExtOrTrunc(iv, linearVarType);
208 step = builder.CreateSExtOrTrunc(step, linearVarType);
209
210 llvm::LoadInst *linearVarStart =
211 builder.CreateLoad(linearVarType, linearPreconditionVars[index]);
212 llvm::Value *mulInst = builder.CreateMul(iv, step);
213 llvm::Value *addInst = builder.CreateAdd(linearVarStart, mulInst);
214 builder.CreateStore(addInst, linearLoopBodyTemps[index]);
215 } else if (linearVarType->isFloatingPointTy()) {
216 // Float path: perform multiply in integer, then convert to float
217 step = builder.CreateSExtOrTrunc(step, iv->getType());
218 llvm::Value *mulInst = builder.CreateMul(iv, step);
219
220 llvm::LoadInst *linearVarStart =
221 builder.CreateLoad(linearVarType, linearPreconditionVars[index]);
222 llvm::Value *mulFp = builder.CreateSIToFP(mulInst, linearVarType);
223 llvm::Value *addInst = builder.CreateFAdd(linearVarStart, mulFp);
224 builder.CreateStore(addInst, linearLoopBodyTemps[index]);
225 } else {
226 llvm_unreachable(
227 "Linear variable must be of integer or floating-point type");
228 }
229 }
230 }
231
232 // Linear variable finalization is conditional on the last logical iteration.
233 // Create BB splits to manage the same.
234 void splitLinearFiniBB(llvm::IRBuilderBase &builder,
235 llvm::BasicBlock *loopExit) {
236 linearFinalizationBB = loopExit->splitBasicBlock(
237 loopExit->getTerminator(), "omp_loop.linear_finalization");
238 linearExitBB = linearFinalizationBB->splitBasicBlock(
239 linearFinalizationBB->getTerminator(), "omp_loop.linear_exit");
240 linearLastIterExitBB = linearFinalizationBB->splitBasicBlock(
241 linearFinalizationBB->getTerminator(), "omp_loop.linear_lastiter_exit");
242 }
243
244 // Finalize the linear vars
245 llvm::OpenMPIRBuilder::InsertPointOrErrorTy
246 finalizeLinearVar(llvm::IRBuilderBase &builder,
247 LLVM::ModuleTranslation &moduleTranslation,
248 llvm::Value *lastIter) {
249 // Emit condition to check whether last logical iteration is being executed
250 builder.SetInsertPoint(linearFinalizationBB->getTerminator());
251 llvm::Value *loopLastIterLoad = builder.CreateLoad(
252 llvm::Type::getInt32Ty(builder.getContext()), lastIter);
253 llvm::Value *isLast =
254 builder.CreateCmp(llvm::CmpInst::ICMP_NE, loopLastIterLoad,
255 llvm::ConstantInt::get(
256 llvm::Type::getInt32Ty(builder.getContext()), 0));
257 // Store the linear variable values to original variables.
258 builder.SetInsertPoint(linearLastIterExitBB->getTerminator());
259 for (size_t index = 0; index < linearOrigVal.size(); index++) {
260 llvm::LoadInst *linearVarTemp =
261 builder.CreateLoad(linearVarTypes[index], linearLoopBodyTemps[index]);
262 builder.CreateStore(linearVarTemp, linearOrigVal[index]);
263 }
264
265 // Create conditional branch such that the linear variable
266 // values are stored to original variables only at the
267 // last logical iteration
268 builder.SetInsertPoint(linearFinalizationBB->getTerminator());
269 builder.CreateCondBr(isLast, linearLastIterExitBB, linearExitBB);
270 linearFinalizationBB->getTerminator()->eraseFromParent();
271 // Emit barrier
272 builder.SetInsertPoint(linearExitBB->getTerminator());
273 return moduleTranslation.getOpenMPBuilder()->createBarrier(
274 builder.saveIP(), llvm::omp::OMPD_barrier);
275 }
276
277 // Emit stores for linear variables. Useful in case of SIMD
278 // construct.
279 void emitStoresForLinearVar(llvm::IRBuilderBase &builder) {
280 for (size_t index = 0; index < linearOrigVal.size(); index++) {
281 llvm::LoadInst *linearVarTemp =
282 builder.CreateLoad(linearVarTypes[index], linearLoopBodyTemps[index]);
283 builder.CreateStore(linearVarTemp, linearOrigVal[index]);
284 }
285 }
286
287 // Rewrite all uses of the original variable, in the basic blocks in the
288 // [startBB, endBB] interval, with the linear variable in-place.
289 void rewriteInPlace(llvm::IRBuilderBase &builder, llvm::BasicBlock *startBB,
290 llvm::BasicBlock *endBB, size_t varIndex) {
291 llvm::SmallVector<llvm::BasicBlock *, 32> worklist;
292 llvm::SmallPtrSet<llvm::BasicBlock *, 32> collectedBBs;
293
294 assert(startBB && endBB && "Invalid startBB/endBB");
295
296 // Collect basic blocks from startBB to endBB.
297 worklist.push_back(startBB);
298 collectedBBs.insert(startBB);
299
300 while (!worklist.empty()) {
301 llvm::BasicBlock *bb = worklist.pop_back_val();
302
303 if (bb == endBB)
304 continue;
305
306 for (llvm::BasicBlock *succ : llvm::successors(bb)) {
307 if (collectedBBs.insert(succ).second)
308 worklist.push_back(succ);
309 }
310 }
311
312 // Rewrite all uses in the collected BBs.
313 llvm::SmallVector<llvm::User *> users(linearOrigVal[varIndex]->users());
314 for (auto *user : users) {
315 if (auto *userInst = dyn_cast<llvm::Instruction>(user)) {
316 if (collectedBBs.contains(userInst->getParent()))
317 user->replaceUsesOfWith(linearOrigVal[varIndex],
318 linearLoopBodyTemps[varIndex]);
319 }
320 }
321 }
322};
323
324} // namespace
325
326/// Looks up from the operation from and returns the PrivateClauseOp with
327/// name symbolName
328static omp::PrivateClauseOp findPrivatizer(Operation *from,
329 SymbolRefAttr symbolName) {
330 omp::PrivateClauseOp privatizer =
332 symbolName);
333 assert(privatizer && "privatizer not found in the symbol table");
334 return privatizer;
335}
336
337/// Check whether translation to LLVM IR for the given operation is currently
338/// supported. If not, descriptive diagnostics will be emitted to let users know
339/// this is a not-yet-implemented feature.
340///
341/// \returns success if no unimplemented features are needed to translate the
342/// given operation.
343static LogicalResult checkImplementationStatus(Operation &op) {
344 auto todo = [&op](StringRef clauseName) {
345 return op.emitError() << "not yet implemented: Unhandled clause "
346 << clauseName << " in " << op.getName()
347 << " operation";
348 };
349
350 auto checkAllocate = [&todo](auto op, LogicalResult &result) {
351 if (!op.getAllocateVars().empty() || !op.getAllocatorVars().empty())
352 result = todo("allocate");
353 };
354 auto checkBare = [&todo](auto op, LogicalResult &result) {
355 if (op.getKernelType() == omp::TargetExecMode::bare)
356 result = todo("ompx_bare");
357 };
358 auto checkDepend = [&todo](auto op, LogicalResult &result) {
359 if (!op.getDependVars().empty() || op.getDependKinds())
360 result = todo("depend");
361 };
362 auto checkHint = [](auto op, LogicalResult &) {
363 if (op.getHint())
364 op.emitWarning("hint clause discarded");
365 };
366 auto checkInReduction = [&todo](auto op, LogicalResult &result) {
367 if (!op.getInReductionVars().empty() || op.getInReductionByref() ||
368 op.getInReductionSyms())
369 result = todo("in_reduction");
370 };
371 auto checkNowait = [&todo](auto op, LogicalResult &result) {
372 if (op.getNowait())
373 result = todo("nowait");
374 };
375 auto checkOrder = [&todo](auto op, LogicalResult &result) {
376 if (op.getOrder() || op.getOrderMod())
377 result = todo("order");
378 };
379 auto checkPrivate = [&todo](auto op, LogicalResult &result) {
380 if (!op.getPrivateVars().empty() || op.getPrivateSyms())
381 result = todo("privatization");
382 };
383 auto checkReduction = [&todo](auto op, LogicalResult &result) {
384 if (isa<omp::TeamsOp>(op))
385 if (!op.getReductionVars().empty() || op.getReductionByref() ||
386 op.getReductionSyms())
387 result = todo("reduction");
388 if (op.getReductionMod() &&
389 op.getReductionMod().value() != omp::ReductionModifier::defaultmod) {
390 omp::ReductionModifier mod = op.getReductionMod().value();
391 // The `task` reduction modifier is supported on the parallel and
392 // worksharing (do/for and sections) constructs. Other modifiers, and the
393 // `task` modifier on other constructs, are not yet implemented.
394 bool taskModifierSupported =
395 mod == omp::ReductionModifier::task &&
396 isa<omp::ParallelOp, omp::WsloopOp, omp::SectionsOp>(op);
397 if (!taskModifierSupported) {
398 result = todo("reduction with modifier");
399 } else if (auto byref = op.getReductionByref()) {
400 // The task reduction modifier lowering only handles non-byref
401 // reductions for now.
402 for (bool isByRef : *byref)
403 if (isByRef) {
404 result = todo("task reduction modifier with by-ref reduction");
405 break;
406 }
407 }
408 }
409 };
410 auto checkTaskReductionByref = [&todo](auto op, LogicalResult &result) {
411 if (auto byrefAttr = op.getTaskReductionByref())
412 for (bool isByRef : *byrefAttr)
413 if (isByRef) {
414 result = todo("task_reduction with byref modifier");
415 return;
416 }
417 };
418 auto checkReductionByref = [&todo](auto op, LogicalResult &result) {
419 if (auto byrefAttr = op.getReductionByref())
420 for (bool isByRef : *byrefAttr)
421 if (isByRef) {
422 result = todo("reduction with byref modifier");
423 return;
424 }
425 };
426 auto checkInReductionByref = [&todo](auto op, LogicalResult &result) {
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 auto checkNumTeams = [&todo](auto op, LogicalResult &result) {
435 if (op.hasNumTeamsMultiDim())
436 result = todo("num_teams with multi-dimensional values");
437 };
438 auto checkNumThreads = [&todo](auto op, LogicalResult &result) {
439 if (op.hasNumThreadsMultiDim())
440 result = todo("num_threads with multi-dimensional values");
441 };
442
443 auto checkThreadLimit = [&todo](auto op, LogicalResult &result) {
444 if (op.hasThreadLimitMultiDim())
445 result = todo("thread_limit with multi-dimensional values");
446 };
447 auto checkMap = [&todo](auto op, LogicalResult &result) {
448 if (!op.getMapIterated().empty())
449 result = todo("map/motion clause with iterator modifier");
450 };
451
452 auto checkDynGroupprivate = [&todo](auto op, LogicalResult &result) {
453 if (op.getDynGroupprivateSize())
454 result = todo("dyn_groupprivate");
455 };
456
457 LogicalResult result = success();
459 .Case([&](omp::DistributeOp op) {
460 checkAllocate(op, result);
461 checkOrder(op, result);
462 })
463 .Case([&](omp::SectionsOp op) {
464 checkAllocate(op, result);
465 checkPrivate(op, result);
466 checkReduction(op, result);
467 })
468 .Case([&](omp::ScopeOp op) {
469 checkAllocate(op, result);
470 checkReduction(op, result);
471 })
472 .Case([&](omp::SingleOp op) {
473 checkAllocate(op, result);
474 checkPrivate(op, result);
475 })
476 .Case([&](omp::TeamsOp op) {
477 checkAllocate(op, result);
478 checkPrivate(op, result);
479 checkNumTeams(op, result);
480 checkThreadLimit(op, result);
481 checkDynGroupprivate(op, result);
482 })
483 .Case([&](omp::TaskOp op) {
484 checkAllocate(op, result);
485 checkInReductionByref(op, result);
486 })
487 .Case([&](omp::TaskgroupOp op) {
488 checkAllocate(op, result);
489 checkTaskReductionByref(op, result);
490 })
491 .Case([&](omp::TaskwaitOp op) {
492 checkDepend(op, result);
493 checkNowait(op, result);
494 })
495 .Case([&](omp::TaskloopContextOp op) {
496 checkAllocate(op, result);
497 checkInReductionByref(op, result);
498 checkReduction(op, result);
499 checkReductionByref(op, result);
500 })
501 .Case([&](omp::WsloopOp op) {
502 checkAllocate(op, result);
503 checkOrder(op, result);
504 checkReduction(op, result);
505 })
506 .Case([&](omp::ParallelOp op) {
507 checkAllocate(op, result);
508 checkReduction(op, result);
509 checkNumThreads(op, result);
510 })
511 .Case([&](omp::SimdOp op) { checkReduction(op, result); })
512 .Case<omp::AtomicReadOp, omp::AtomicWriteOp, omp::AtomicUpdateOp,
513 omp::AtomicCaptureOp>([&](auto op) { checkHint(op, result); })
514 .Case([&](omp::AtomicCompareOp op) {
515 checkHint(op, result);
516 Region &region = op.getRegion();
517 if (region.empty())
518 return;
519 mlir::Type argType = region.front().getArgument(0).getType();
520 auto structTy = dyn_cast<LLVM::LLVMStructType>(argType);
521 if (!structTy)
522 return;
523 DataLayout dl = DataLayout(op->getParentOfType<ModuleOp>());
524 unsigned totalBits = dl.getTypeSizeInBits(structTy);
525 if (totalBits > 128)
526 result = todo("compare for complex types wider than 128 bits");
527 })
528 .Case<omp::TargetEnterDataOp, omp::TargetExitDataOp>([&](auto op) {
529 checkDepend(op, result);
530 checkMap(op, result);
531 })
532 .Case([&](omp::TargetUpdateOp op) {
533 checkDepend(op, result);
534 checkMap(op, result);
535 })
536 .Case([&](omp::TargetOp op) {
537 checkAllocate(op, result);
538 checkBare(op, result);
539 checkInReduction(op, result);
540 checkMap(op, result);
541 checkThreadLimit(op, result);
542 })
543 .Case([&](omp::TargetDataOp op) { checkMap(op, result); })
544 .Case([&](omp::DeclareMapperInfoOp op) { checkMap(op, result); })
545 .Default([](Operation &) {
546 // Assume all clauses for an operation can be translated unless they are
547 // checked above.
548 });
549 return result;
550}
551
552static LogicalResult handleError(llvm::Error error, Operation &op) {
553 LogicalResult result = success();
554 if (error) {
555 llvm::handleAllErrors(
556 std::move(error),
557 [&](const PreviouslyReportedError &) { result = failure(); },
558 [&](const llvm::ErrorInfoBase &err) {
559 result = op.emitError(err.message());
560 });
561 }
562 return result;
563}
564
565template <typename T>
566static LogicalResult handleError(llvm::Expected<T> &result, Operation &op) {
567 if (!result)
568 return handleError(result.takeError(), op);
569
570 return success();
571}
572
573/// Find the insertion point for allocas given the current insertion point for
574/// normal operations in the builder.
575static llvm::OpenMPIRBuilder::InsertPointTy findAllocInsertPoints(
576 llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation,
577 llvm::SmallVectorImpl<llvm::BasicBlock *> *deallocBlocks = nullptr) {
578 // If there is an allocation insertion point on stack, i.e. we are in a nested
579 // operation and a specific point was provided by some surrounding operation,
580 // use it.
581 llvm::OpenMPIRBuilder::InsertPointTy allocInsertPoint;
582 llvm::ArrayRef<llvm::BasicBlock *> deallocInsertPoints;
583 WalkResult walkResult = moduleTranslation.stackWalk<OpenMPAllocStackFrame>(
584 [&](OpenMPAllocStackFrame &frame) {
585 allocInsertPoint = frame.allocInsertPoint;
586 deallocInsertPoints = frame.deallocBlocks;
587 return WalkResult::interrupt();
588 });
589 // In cases with multiple levels of outlining, the tree walk might find an
590 // insertion point that is inside the original function while the builder
591 // insertion point is inside the outlined function. We need to make sure that
592 // we do not use it in those cases.
593 if (walkResult.wasInterrupted() &&
594 allocInsertPoint.getBlock()->getParent() ==
595 builder.GetInsertBlock()->getParent()) {
596 if (deallocBlocks)
597 deallocBlocks->insert(deallocBlocks->end(), deallocInsertPoints.begin(),
598 deallocInsertPoints.end());
599 return allocInsertPoint;
600 }
601
602 // Otherwise, insert to the entry block of the surrounding function.
603 // If the current IRBuilder InsertPoint is the function's entry, it cannot
604 // also be used for alloca insertion which would result in insertion order
605 // confusion. Create a new BasicBlock for the Builder and use the entry block
606 // for the allocs.
607 // TODO: Create a dedicated alloca BasicBlock at function creation such that
608 // we do not need to move the current InsertPoint here.
609 if (builder.GetInsertBlock() ==
610 &builder.GetInsertBlock()->getParent()->getEntryBlock()) {
611 assert(builder.GetInsertPoint() == builder.GetInsertBlock()->end() &&
612 "Assuming end of basic block");
613 llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(
614 builder.getContext(), "entry", builder.GetInsertBlock()->getParent(),
615 builder.GetInsertBlock()->getNextNode());
616 builder.CreateBr(entryBB);
617 builder.SetInsertPoint(entryBB);
618 }
619
620 // Collect exit blocks, which is where explicit deallocations should happen in
621 // this case.
622 if (deallocBlocks) {
623 for (llvm::BasicBlock &block : *builder.GetInsertBlock()->getParent()) {
624 // TODO: This currently results in no blocks being added to the list when
625 // all exit blocks of the enclosing function have not been lowered before
626 // this is reached.
627 llvm::Instruction *terminator = block.getTerminatorOrNull();
628 if (isa_and_present<llvm::ReturnInst>(terminator))
629 deallocBlocks->emplace_back(&block);
630 }
631 }
632
633 llvm::BasicBlock &funcEntryBlock =
634 builder.GetInsertBlock()->getParent()->getEntryBlock();
635 return llvm::OpenMPIRBuilder::InsertPointTy(
636 &funcEntryBlock, funcEntryBlock.getFirstInsertionPt());
637}
638
639/// Find the loop information structure for the loop nest being translated. It
640/// will return a `null` value unless called from the translation function for
641/// a loop wrapper operation after successfully translating its body.
642static llvm::CanonicalLoopInfo *
644 llvm::CanonicalLoopInfo *loopInfo = nullptr;
645 moduleTranslation.stackWalk<OpenMPLoopInfoStackFrame>(
646 [&](OpenMPLoopInfoStackFrame &frame) {
647 loopInfo = frame.loopInfo;
648 return WalkResult::interrupt();
649 });
650 return loopInfo;
651}
652
653/// Converts the given region that appears within an OpenMP dialect operation to
654/// LLVM IR, creating a branch from the `sourceBlock` to the entry block of the
655/// region, and a branch from any block with an successor-less OpenMP terminator
656/// to `continuationBlock`. Populates `continuationBlockPHIs` with the PHI nodes
657/// of the continuation block if provided.
659 Region &region, StringRef blockName, llvm::IRBuilderBase &builder,
660 LLVM::ModuleTranslation &moduleTranslation,
661 SmallVectorImpl<llvm::PHINode *> *continuationBlockPHIs = nullptr) {
662 bool isLoopWrapper = isa<omp::LoopWrapperInterface>(region.getParentOp());
663
664 llvm::BasicBlock *continuationBlock =
665 splitBB(builder, true, "omp.region.cont");
666 llvm::BasicBlock *sourceBlock = builder.GetInsertBlock();
667
668 llvm::LLVMContext &llvmContext = builder.getContext();
669 for (Block &bb : region) {
670 llvm::BasicBlock *llvmBB = llvm::BasicBlock::Create(
671 llvmContext, blockName, builder.GetInsertBlock()->getParent(),
672 builder.GetInsertBlock()->getNextNode());
673 moduleTranslation.mapBlock(&bb, llvmBB);
674 }
675
676 llvm::Instruction *sourceTerminator = sourceBlock->getTerminator();
677
678 // Terminators (namely YieldOp) may be forwarding values to the region that
679 // need to be available in the continuation block. Collect the types of these
680 // operands in preparation of creating PHI nodes. This is skipped for loop
681 // wrapper operations, for which we know in advance they have no terminators.
682 SmallVector<llvm::Type *> continuationBlockPHITypes;
683 unsigned numYields = 0;
684
685 if (!isLoopWrapper) {
686 bool operandsProcessed = false;
687 for (Block &bb : region.getBlocks()) {
688 if (omp::YieldOp yield = dyn_cast<omp::YieldOp>(bb.getTerminator())) {
689 if (!operandsProcessed) {
690 for (unsigned i = 0, e = yield->getNumOperands(); i < e; ++i) {
691 continuationBlockPHITypes.push_back(
692 moduleTranslation.convertType(yield->getOperand(i).getType()));
693 }
694 operandsProcessed = true;
695 } else {
696 assert(continuationBlockPHITypes.size() == yield->getNumOperands() &&
697 "mismatching number of values yielded from the region");
698 for (unsigned i = 0, e = yield->getNumOperands(); i < e; ++i) {
699 llvm::Type *operandType =
700 moduleTranslation.convertType(yield->getOperand(i).getType());
701 (void)operandType;
702 assert(continuationBlockPHITypes[i] == operandType &&
703 "values of mismatching types yielded from the region");
704 }
705 }
706 numYields++;
707 }
708 }
709 }
710
711 // Insert PHI nodes in the continuation block for any values forwarded by the
712 // terminators in this region.
713 if (!continuationBlockPHITypes.empty())
714 assert(
715 continuationBlockPHIs &&
716 "expected continuation block PHIs if converted regions yield values");
717 if (continuationBlockPHIs) {
718 llvm::IRBuilderBase::InsertPointGuard guard(builder);
719 continuationBlockPHIs->reserve(continuationBlockPHITypes.size());
720 builder.SetInsertPoint(continuationBlock, continuationBlock->begin());
721 for (llvm::Type *ty : continuationBlockPHITypes)
722 continuationBlockPHIs->push_back(builder.CreatePHI(ty, numYields));
723 }
724
725 // Convert blocks one by one in topological order to ensure
726 // defs are converted before uses.
728 for (Block *bb : blocks) {
729 llvm::BasicBlock *llvmBB = moduleTranslation.lookupBlock(bb);
730 // Retarget the branch of the entry block to the entry block of the
731 // converted region (regions are single-entry).
732 if (bb->isEntryBlock()) {
733 assert(sourceTerminator->getNumSuccessors() == 1 &&
734 "provided entry block has multiple successors");
735 assert(sourceTerminator->getSuccessor(0) == continuationBlock &&
736 "ContinuationBlock is not the successor of the entry block");
737 sourceTerminator->setSuccessor(0, llvmBB);
738 }
739
740 llvm::IRBuilderBase::InsertPointGuard guard(builder);
741 if (failed(
742 moduleTranslation.convertBlock(*bb, bb->isEntryBlock(), builder)))
743 return llvm::make_error<PreviouslyReportedError>();
744
745 // Create a direct branch here for loop wrappers to prevent their lack of a
746 // terminator from causing a crash below.
747 if (isLoopWrapper) {
748 builder.CreateBr(continuationBlock);
749 continue;
750 }
751
752 // Special handling for `omp.yield` and `omp.terminator` (we may have more
753 // than one): they return the control to the parent OpenMP dialect operation
754 // so replace them with the branch to the continuation block. We handle this
755 // here to avoid relying inter-function communication through the
756 // ModuleTranslation class to set up the correct insertion point. This is
757 // also consistent with MLIR's idiom of handling special region terminators
758 // in the same code that handles the region-owning operation.
759 Operation *terminator = bb->getTerminator();
760 if (isa<omp::TerminatorOp, omp::YieldOp>(terminator)) {
761 builder.CreateBr(continuationBlock);
762
763 for (unsigned i = 0, e = terminator->getNumOperands(); i < e; ++i)
764 (*continuationBlockPHIs)[i]->addIncoming(
765 moduleTranslation.lookupValue(terminator->getOperand(i)), llvmBB);
766 }
767 }
768 // After all blocks have been traversed and values mapped, connect the PHI
769 // nodes to the results of preceding blocks.
770 LLVM::detail::connectPHINodes(region, moduleTranslation);
771
772 // Remove the blocks and values defined in this region from the mapping since
773 // they are not visible outside of this region. This allows the same region to
774 // be converted several times, that is cloned, without clashes, and slightly
775 // speeds up the lookups.
776 moduleTranslation.forgetMapping(region);
777
778 return continuationBlock;
779}
780
781/// Convert ProcBindKind from MLIR-generated enum to LLVM enum.
782static llvm::omp::ProcBindKind getProcBindKind(omp::ClauseProcBindKind kind) {
783 switch (kind) {
784 case omp::ClauseProcBindKind::Close:
785 return llvm::omp::ProcBindKind::OMP_PROC_BIND_close;
786 case omp::ClauseProcBindKind::Master:
787 return llvm::omp::ProcBindKind::OMP_PROC_BIND_master;
788 case omp::ClauseProcBindKind::Primary:
789 return llvm::omp::ProcBindKind::OMP_PROC_BIND_primary;
790 case omp::ClauseProcBindKind::Spread:
791 return llvm::omp::ProcBindKind::OMP_PROC_BIND_spread;
792 }
793 llvm_unreachable("Unknown ClauseProcBindKind kind");
794}
795
796/// Converts an OpenMP 'masked' operation into LLVM IR using OpenMPIRBuilder.
797static LogicalResult
798convertOmpMasked(Operation &opInst, llvm::IRBuilderBase &builder,
799 LLVM::ModuleTranslation &moduleTranslation) {
800 auto maskedOp = cast<omp::MaskedOp>(opInst);
801 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
802
803 if (failed(checkImplementationStatus(opInst)))
804 return failure();
805
806 auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
808 // MaskedOp has only one region associated with it.
809 auto &region = maskedOp.getRegion();
810 builder.restoreIP(codeGenIP);
811 return convertOmpOpRegions(region, "omp.masked.region", builder,
812 moduleTranslation)
813 .takeError();
814 };
815
816 // TODO: Perform finalization actions for variables. This has to be
817 // called for variables which have destructors/finalizers.
818 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
819
820 llvm::Value *filterVal = nullptr;
821 if (auto filterVar = maskedOp.getFilteredThreadId()) {
822 filterVal = moduleTranslation.lookupValue(filterVar);
823 } else {
824 llvm::LLVMContext &llvmContext = builder.getContext();
825 filterVal =
826 llvm::ConstantInt::get(llvm::Type::getInt32Ty(llvmContext), /*V=*/0);
827 }
828 assert(filterVal != nullptr);
829 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
830 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
831 moduleTranslation.getOpenMPBuilder()->createMasked(ompLoc, bodyGenCB,
832 finiCB, filterVal);
833
834 if (failed(handleError(afterIP, opInst)))
835 return failure();
836
837 builder.restoreIP(*afterIP);
838 return success();
839}
840
841/// Converts an OpenMP 'master' operation into LLVM IR using OpenMPIRBuilder.
842static LogicalResult
843convertOmpMaster(Operation &opInst, llvm::IRBuilderBase &builder,
844 LLVM::ModuleTranslation &moduleTranslation) {
845 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
846 auto masterOp = cast<omp::MasterOp>(opInst);
847
848 if (failed(checkImplementationStatus(opInst)))
849 return failure();
850
851 auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
853 // MasterOp has only one region associated with it.
854 auto &region = masterOp.getRegion();
855 builder.restoreIP(codeGenIP);
856 return convertOmpOpRegions(region, "omp.master.region", builder,
857 moduleTranslation)
858 .takeError();
859 };
860
861 // TODO: Perform finalization actions for variables. This has to be
862 // called for variables which have destructors/finalizers.
863 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
864
865 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
866 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
867 moduleTranslation.getOpenMPBuilder()->createMaster(ompLoc, bodyGenCB,
868 finiCB);
869
870 if (failed(handleError(afterIP, opInst)))
871 return failure();
872
873 builder.restoreIP(*afterIP);
874 return success();
875}
876
877/// Converts an OpenMP 'critical' operation into LLVM IR using OpenMPIRBuilder.
878static LogicalResult
879convertOmpCritical(Operation &opInst, llvm::IRBuilderBase &builder,
880 LLVM::ModuleTranslation &moduleTranslation) {
881 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
882 auto criticalOp = cast<omp::CriticalOp>(opInst);
883
884 if (failed(checkImplementationStatus(opInst)))
885 return failure();
886
887 auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
889 // CriticalOp has only one region associated with it.
890 auto &region = cast<omp::CriticalOp>(opInst).getRegion();
891 builder.restoreIP(codeGenIP);
892 return convertOmpOpRegions(region, "omp.critical.region", builder,
893 moduleTranslation)
894 .takeError();
895 };
896
897 // TODO: Perform finalization actions for variables. This has to be
898 // called for variables which have destructors/finalizers.
899 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
900
901 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
902 llvm::LLVMContext &llvmContext = moduleTranslation.getLLVMContext();
903 llvm::Constant *hint = nullptr;
904
905 // If it has a name, it probably has a hint too.
906 if (criticalOp.getNameAttr()) {
907 // The verifiers in OpenMP Dialect guarentee that all the pointers are
908 // non-null
909 auto symbolRef = cast<SymbolRefAttr>(criticalOp.getNameAttr());
910 auto criticalDeclareOp =
912 symbolRef);
913 hint =
914 llvm::ConstantInt::get(llvm::Type::getInt32Ty(llvmContext),
915 static_cast<int>(criticalDeclareOp.getHint()));
916 }
917 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
918 moduleTranslation.getOpenMPBuilder()->createCritical(
919 ompLoc, bodyGenCB, finiCB, criticalOp.getName().value_or(""), hint);
920
921 if (failed(handleError(afterIP, opInst)))
922 return failure();
923
924 builder.restoreIP(*afterIP);
925 return success();
926}
927
928/// A util to collect info needed to convert delayed privatizers from MLIR to
929/// LLVM.
931 template <typename OP>
933 : blockArgs(
934 cast<omp::BlockArgOpenMPOpInterface>(*op).getPrivateBlockArgs()) {
935 mlirVars.reserve(blockArgs.size());
936 llvmVars.reserve(blockArgs.size());
937 collectPrivatizationDecls<OP>(op);
938
939 for (mlir::Value privateVar : op.getPrivateVars())
940 mlirVars.push_back(privateVar);
941 }
942
947
948private:
949 /// Populates `privatizations` with privatization declarations used for the
950 /// given op.
951 template <class OP>
952 void collectPrivatizationDecls(OP op) {
953 std::optional<ArrayAttr> attr = op.getPrivateSyms();
954 if (!attr)
955 return;
956
957 privatizers.reserve(privatizers.size() + attr->size());
958 for (auto symbolRef : attr->getAsRange<SymbolRefAttr>()) {
959 privatizers.push_back(findPrivatizer(op, symbolRef));
960 }
961 }
962};
963
964/// Populates `reductions` with reduction declarations used in the given op.
965template <typename T>
966static void
969 std::optional<ArrayAttr> attr = op.getReductionSyms();
970 if (!attr)
971 return;
972
973 reductions.reserve(reductions.size() + op.getNumReductionVars());
974 for (auto symbolRef : attr->getAsRange<SymbolRefAttr>()) {
975 reductions.push_back(
977 op, symbolRef));
978 }
979}
980
981/// Look up and validate the declare_reduction ops referenced by a
982/// reduction-like clause on the omp.taskloop.context translation path. Only
983/// the non-byref, single-init-arg, no-cleanup form is supported in this
984/// initial cut; richer shapes are rejected here with a diagnostic. \p syms
985/// is the clause's symbol list (e.g. `getReductionSyms()` or
986/// `getInReductionSyms()`), \p opName is the textual op name used in
987/// diagnostics, and \p clauseName distinguishes "reduction" from
988/// "in_reduction" in those diagnostics.
990 Operation *contextOp, std::optional<ArrayAttr> syms, StringRef opName,
991 StringRef clauseName, SmallVectorImpl<omp::DeclareReductionOp> &out) {
992 if (!syms)
993 return success();
994 out.reserve(out.size() + syms->size());
995 for (auto sym : syms->getAsRange<SymbolRefAttr>()) {
997 contextOp, sym);
998 if (!decl)
999 return contextOp->emitError()
1000 << "failed to resolve " << clauseName
1001 << " declare_reduction symbol " << sym.getRootReference() << " in "
1002 << opName;
1003 if (decl.getInitializerRegion().front().getNumArguments() != 1)
1004 return contextOp->emitError()
1005 << "not yet implemented: " << clauseName
1006 << " with two-argument initializer in " << opName;
1007 if (!decl.getCleanupRegion().empty())
1008 return contextOp->emitError() << "not yet implemented: " << clauseName
1009 << " with cleanup region in " << opName;
1010 if (decl.getReductionRegion().empty())
1011 return contextOp->emitError()
1012 << clauseName << " declare_reduction is missing a combiner region";
1013 out.push_back(decl);
1014 }
1015 return success();
1016}
1017
1018/// Translates the blocks contained in the given region and appends them to at
1019/// the current insertion point of `builder`. The operations of the entry block
1020/// are appended to the current insertion block. If set, `continuationBlockArgs`
1021/// is populated with translated values that correspond to the values
1022/// omp.yield'ed from the region.
1023static LogicalResult inlineConvertOmpRegions(
1024 Region &region, StringRef blockName, llvm::IRBuilderBase &builder,
1025 LLVM::ModuleTranslation &moduleTranslation,
1026 SmallVectorImpl<llvm::Value *> *continuationBlockArgs = nullptr) {
1027 if (region.empty())
1028 return success();
1029
1030 // Special case for single-block regions that don't create additional blocks:
1031 // insert operations without creating additional blocks.
1032 if (region.hasOneBlock()) {
1033 llvm::Instruction *potentialTerminator =
1034 builder.GetInsertBlock()->empty() ? nullptr
1035 : &builder.GetInsertBlock()->back();
1036
1037 if (potentialTerminator && potentialTerminator->isTerminator())
1038 potentialTerminator->removeFromParent();
1039 moduleTranslation.mapBlock(&region.front(), builder.GetInsertBlock());
1040
1041 if (failed(moduleTranslation.convertBlock(
1042 region.front(), /*ignoreArguments=*/true, builder)))
1043 return failure();
1044
1045 // The continuation arguments are simply the translated terminator operands.
1046 if (continuationBlockArgs)
1047 llvm::append_range(
1048 *continuationBlockArgs,
1049 moduleTranslation.lookupValues(region.front().back().getOperands()));
1050
1051 // Drop the mapping that is no longer necessary so that the same region can
1052 // be processed multiple times.
1053 moduleTranslation.forgetMapping(region);
1054
1055 if (potentialTerminator && potentialTerminator->isTerminator()) {
1056 llvm::BasicBlock *block = builder.GetInsertBlock();
1057 if (block->empty()) {
1058 // this can happen for really simple reduction init regions e.g.
1059 // %0 = llvm.mlir.constant(0 : i32) : i32
1060 // omp.yield(%0 : i32)
1061 // because the llvm.mlir.constant (MLIR op) isn't converted into any
1062 // llvm op
1063 potentialTerminator->insertInto(block, block->begin());
1064 } else {
1065 potentialTerminator->insertAfter(&block->back());
1066 }
1067 }
1068
1069 return success();
1070 }
1071
1073 llvm::Expected<llvm::BasicBlock *> continuationBlock =
1074 convertOmpOpRegions(region, blockName, builder, moduleTranslation, &phis);
1075
1076 if (failed(handleError(continuationBlock, *region.getParentOp())))
1077 return failure();
1078
1079 if (continuationBlockArgs)
1080 llvm::append_range(*continuationBlockArgs, phis);
1081 builder.SetInsertPoint(*continuationBlock,
1082 (*continuationBlock)->getFirstInsertionPt());
1083 return success();
1084}
1085
1086namespace {
1087/// Owning equivalents of OpenMPIRBuilder::(Atomic)ReductionGen that are used to
1088/// store lambdas with capture.
1089using OwningReductionGen =
1090 std::function<llvm::OpenMPIRBuilder::InsertPointOrErrorTy(
1091 llvm::OpenMPIRBuilder::InsertPointTy, llvm::Value *, llvm::Value *,
1092 llvm::Value *&)>;
1093using OwningAtomicReductionGen =
1094 std::function<llvm::OpenMPIRBuilder::InsertPointOrErrorTy(
1095 llvm::OpenMPIRBuilder::InsertPointTy, llvm::Type *, llvm::Value *,
1096 llvm::Value *)>;
1097using OwningDataPtrPtrReductionGen =
1098 std::function<llvm::OpenMPIRBuilder::InsertPointOrErrorTy(
1099 llvm::OpenMPIRBuilder::InsertPointTy, llvm::Value *, llvm::Value *&)>;
1100} // namespace
1101
1102/// Create an OpenMPIRBuilder-compatible reduction generator for the given
1103/// reduction declaration. The generator uses `builder` but ignores its
1104/// insertion point.
1105static OwningReductionGen
1106makeReductionGen(omp::DeclareReductionOp decl, llvm::IRBuilderBase &builder,
1107 LLVM::ModuleTranslation &moduleTranslation) {
1108 // The lambda is mutable because we need access to non-const methods of decl
1109 // (which aren't actually mutating it), and we must capture decl by-value to
1110 // avoid the dangling reference after the parent function returns.
1111 OwningReductionGen gen =
1112 [&, decl](llvm::OpenMPIRBuilder::InsertPointTy insertPoint,
1113 llvm::Value *lhs, llvm::Value *rhs,
1114 llvm::Value *&result) mutable
1115 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
1116 moduleTranslation.mapValue(decl.getReductionLhsArg(), lhs);
1117 moduleTranslation.mapValue(decl.getReductionRhsArg(), rhs);
1118 builder.restoreIP(insertPoint);
1120 if (failed(inlineConvertOmpRegions(decl.getReductionRegion(),
1121 "omp.reduction.nonatomic.body", builder,
1122 moduleTranslation, &phis)))
1123 return llvm::createStringError(
1124 "failed to inline `combiner` region of `omp.declare_reduction`");
1125 result = llvm::getSingleElement(phis);
1126 return builder.saveIP();
1127 };
1128 return gen;
1129}
1130
1131/// Create an OpenMPIRBuilder-compatible atomic reduction generator for the
1132/// given reduction declaration. The generator uses `builder` but ignores its
1133/// insertion point. Returns null if there is no atomic region available in the
1134/// reduction declaration.
1135static OwningAtomicReductionGen
1136makeAtomicReductionGen(omp::DeclareReductionOp decl,
1137 llvm::IRBuilderBase &builder,
1138 LLVM::ModuleTranslation &moduleTranslation) {
1139 if (decl.getAtomicReductionRegion().empty())
1140 return OwningAtomicReductionGen();
1141
1142 // The lambda is mutable because we need access to non-const methods of decl
1143 // (which aren't actually mutating it), and we must capture decl by-value to
1144 // avoid the dangling reference after the parent function returns.
1145 OwningAtomicReductionGen atomicGen =
1146 [&, decl](llvm::OpenMPIRBuilder::InsertPointTy insertPoint, llvm::Type *,
1147 llvm::Value *lhs, llvm::Value *rhs) mutable
1148 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
1149 moduleTranslation.mapValue(decl.getAtomicReductionLhsArg(), lhs);
1150 moduleTranslation.mapValue(decl.getAtomicReductionRhsArg(), rhs);
1151 builder.restoreIP(insertPoint);
1153 if (failed(inlineConvertOmpRegions(decl.getAtomicReductionRegion(),
1154 "omp.reduction.atomic.body", builder,
1155 moduleTranslation, &phis)))
1156 return llvm::createStringError(
1157 "failed to inline `atomic` region of `omp.declare_reduction`");
1158 assert(phis.empty());
1159 return builder.saveIP();
1160 };
1161 return atomicGen;
1162}
1163
1164/// Create an OpenMPIRBuilder-compatible `data_ptr_ptr` reduction generator for
1165/// the given reduction declaration. The generator uses `builder` but ignores
1166/// its insertion point. Returns null if there is no `data_ptr_ptr` region
1167/// available in the reduction declaration.
1168static OwningDataPtrPtrReductionGen
1169makeRefDataPtrGen(omp::DeclareReductionOp decl, llvm::IRBuilderBase &builder,
1170 LLVM::ModuleTranslation &moduleTranslation, bool isByRef) {
1171 if (!isByRef || decl.getDataPtrPtrRegion().empty())
1172 return OwningDataPtrPtrReductionGen();
1173
1174 OwningDataPtrPtrReductionGen refDataPtrGen =
1175 [&, decl](llvm::OpenMPIRBuilder::InsertPointTy insertPoint,
1176 llvm::Value *byRefVal, llvm::Value *&result) mutable
1177 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
1178 moduleTranslation.mapValue(decl.getDataPtrPtrRegionArg(), byRefVal);
1179 builder.restoreIP(insertPoint);
1181 if (failed(inlineConvertOmpRegions(decl.getDataPtrPtrRegion(),
1182 "omp.data_ptr_ptr.body", builder,
1183 moduleTranslation, &phis)))
1184 return llvm::createStringError(
1185 "failed to inline `data_ptr_ptr` region of `omp.declare_reduction`");
1186 result = llvm::getSingleElement(phis);
1187 return builder.saveIP();
1188 };
1189
1190 return refDataPtrGen;
1191}
1192
1193/// Converts an OpenMP 'ordered' operation into LLVM IR using OpenMPIRBuilder.
1194static LogicalResult
1195convertOmpOrdered(Operation &opInst, llvm::IRBuilderBase &builder,
1196 LLVM::ModuleTranslation &moduleTranslation) {
1197 auto orderedOp = cast<omp::OrderedOp>(opInst);
1198
1199 if (failed(checkImplementationStatus(opInst)))
1200 return failure();
1201
1202 omp::ClauseDepend dependType = *orderedOp.getDoacrossDependType();
1203 bool isDependSource = dependType == omp::ClauseDepend::dependsource;
1204 unsigned numLoops = *orderedOp.getDoacrossNumLoops();
1205 SmallVector<llvm::Value *> vecValues =
1206 moduleTranslation.lookupValues(orderedOp.getDoacrossDependVars());
1207
1208 size_t indexVecValues = 0;
1209 while (indexVecValues < vecValues.size()) {
1210 SmallVector<llvm::Value *> storeValues;
1211 storeValues.reserve(numLoops);
1212 for (unsigned i = 0; i < numLoops; i++) {
1213 storeValues.push_back(vecValues[indexVecValues]);
1214 indexVecValues++;
1215 }
1216 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
1217 findAllocInsertPoints(builder, moduleTranslation);
1218 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
1219 builder.restoreIP(moduleTranslation.getOpenMPBuilder()->createOrderedDepend(
1220 ompLoc, allocaIP, numLoops, storeValues, ".cnt.addr", isDependSource));
1221 }
1222 return success();
1223}
1224
1225/// Converts an OpenMP 'ordered_region' operation into LLVM IR using
1226/// OpenMPIRBuilder.
1227static LogicalResult
1228convertOmpOrderedRegion(Operation &opInst, llvm::IRBuilderBase &builder,
1229 LLVM::ModuleTranslation &moduleTranslation) {
1230 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
1231 auto orderedRegionOp = cast<omp::OrderedRegionOp>(opInst);
1232
1233 if (failed(checkImplementationStatus(opInst)))
1234 return failure();
1235
1236 auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
1237 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) {
1238 // OrderedOp has only one region associated with it.
1239 auto &region = cast<omp::OrderedRegionOp>(opInst).getRegion();
1240 builder.restoreIP(codeGenIP);
1241 return convertOmpOpRegions(region, "omp.ordered.region", builder,
1242 moduleTranslation)
1243 .takeError();
1244 };
1245
1246 // TODO: Perform finalization actions for variables. This has to be
1247 // called for variables which have destructors/finalizers.
1248 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
1249
1250 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
1251 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
1252 moduleTranslation.getOpenMPBuilder()->createOrderedThreadsSimd(
1253 ompLoc, bodyGenCB, finiCB, !orderedRegionOp.getParLevelSimd());
1254
1255 if (failed(handleError(afterIP, opInst)))
1256 return failure();
1257
1258 builder.restoreIP(*afterIP);
1259 return success();
1260}
1261
1262namespace {
1263/// Contains the arguments for an LLVM store operation
1264struct DeferredStore {
1265 DeferredStore(llvm::Value *value, llvm::Value *address)
1266 : value(value), address(address) {}
1267
1268 llvm::Value *value;
1269 llvm::Value *address;
1270};
1271} // namespace
1272
1273/// Allocate space for privatized reduction variables.
1274/// `deferredStores` contains information to create store operations which needs
1275/// to be inserted after all allocas
1276template <typename T>
1277static LogicalResult
1279 llvm::IRBuilderBase &builder,
1280 LLVM::ModuleTranslation &moduleTranslation,
1281 const llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
1283 SmallVectorImpl<llvm::Value *> &privateReductionVariables,
1284 DenseMap<Value, llvm::Value *> &reductionVariableMap,
1285 SmallVectorImpl<DeferredStore> &deferredStores,
1286 llvm::ArrayRef<bool> isByRefs) {
1287 llvm::IRBuilderBase::InsertPointGuard guard(builder);
1288 builder.SetInsertPoint(allocaIP.getBlock()->getTerminator());
1289
1290 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
1291 bool useDeviceSharedMem = omp::opInSharedDeviceContext(*op);
1292
1293 // delay creating stores until after all allocas
1294 deferredStores.reserve(op.getNumReductionVars());
1295
1296 for (std::size_t i = 0; i < op.getNumReductionVars(); ++i) {
1297 Region &allocRegion = reductionDecls[i].getAllocRegion();
1298 if (isByRefs[i]) {
1299 if (allocRegion.empty())
1300 continue;
1301
1303 if (failed(inlineConvertOmpRegions(allocRegion, "omp.reduction.alloc",
1304 builder, moduleTranslation, &phis)))
1305 return op.emitError(
1306 "failed to inline `alloc` region of `omp.declare_reduction`");
1307
1308 assert(phis.size() == 1 && "expected one allocation to be yielded");
1309 builder.SetInsertPoint(allocaIP.getBlock()->getTerminator());
1310
1311 // Allocate reduction variable (which is a pointer to the real reduction
1312 // variable allocated in the inlined region)
1313 llvm::Type *ptrTy = builder.getPtrTy();
1314 llvm::Type *varTy =
1315 moduleTranslation.convertType(reductionDecls[i].getType());
1316 llvm::Value *var;
1317 if (useDeviceSharedMem) {
1318 var = ompBuilder->createOMPAllocShared(builder, varTy);
1319 } else {
1320 var = builder.CreateAlloca(varTy);
1321 var = builder.CreatePointerBitCastOrAddrSpaceCast(var, ptrTy);
1322 }
1323
1324 llvm::Value *castPhi =
1325 builder.CreatePointerBitCastOrAddrSpaceCast(phis[0], ptrTy);
1326
1327 deferredStores.emplace_back(castPhi, var);
1328
1329 privateReductionVariables[i] = var;
1330 moduleTranslation.mapValue(reductionArgs[i], castPhi);
1331 reductionVariableMap.try_emplace(op.getReductionVars()[i], castPhi);
1332 } else {
1333 assert(allocRegion.empty() &&
1334 "allocaction is implicit for by-val reduction");
1335
1336 llvm::Type *ptrTy = builder.getPtrTy();
1337 llvm::Type *varTy =
1338 moduleTranslation.convertType(reductionDecls[i].getType());
1339 llvm::Value *var;
1340 if (useDeviceSharedMem) {
1341 var = ompBuilder->createOMPAllocShared(builder, varTy);
1342 } else {
1343 var = builder.CreateAlloca(varTy);
1344 var = builder.CreatePointerBitCastOrAddrSpaceCast(var, ptrTy);
1345 }
1346
1347 moduleTranslation.mapValue(reductionArgs[i], var);
1348 privateReductionVariables[i] = var;
1349 reductionVariableMap.try_emplace(op.getReductionVars()[i], var);
1350 }
1351 }
1352
1353 return success();
1354}
1355
1356/// Map input arguments to reduction initialization region
1357template <typename T>
1358static void
1360 llvm::IRBuilderBase &builder,
1362 DenseMap<Value, llvm::Value *> &reductionVariableMap,
1363 unsigned i) {
1364 // map input argument to the initialization region
1365 mlir::omp::DeclareReductionOp &reduction = reductionDecls[i];
1366 Region &initializerRegion = reduction.getInitializerRegion();
1367 Block &entry = initializerRegion.front();
1368
1369 mlir::Value mlirSource = loop.getReductionVars()[i];
1370 llvm::Value *llvmSource = moduleTranslation.lookupValue(mlirSource);
1371 llvm::Value *origVal = llvmSource;
1372 // If a non-pointer value is expected, load the value from the source pointer.
1373 if (!isa<LLVM::LLVMPointerType>(
1374 reduction.getInitializerMoldArg().getType()) &&
1375 isa<LLVM::LLVMPointerType>(mlirSource.getType())) {
1376 origVal =
1377 builder.CreateLoad(moduleTranslation.convertType(
1378 reduction.getInitializerMoldArg().getType()),
1379 llvmSource, "omp_orig");
1380 }
1381 moduleTranslation.mapValue(reduction.getInitializerMoldArg(), origVal);
1382
1383 if (entry.getNumArguments() > 1) {
1384 llvm::Value *allocation =
1385 reductionVariableMap.lookup(loop.getReductionVars()[i]);
1386 moduleTranslation.mapValue(reduction.getInitializerAllocArg(), allocation);
1387 }
1388}
1389
1390static void
1391setInsertPointForPossiblyEmptyBlock(llvm::IRBuilderBase &builder,
1392 llvm::BasicBlock *block = nullptr) {
1393 if (block == nullptr)
1394 block = builder.GetInsertBlock();
1395
1396 if (!block->hasTerminator())
1397 builder.SetInsertPoint(block);
1398 else
1399 builder.SetInsertPoint(block->getTerminator());
1400}
1401
1402/// Inline reductions' `init` regions. This functions assumes that the
1403/// `builder`'s insertion point is where the user wants the `init` regions to be
1404/// inlined; i.e. it does not try to find a proper insertion location for the
1405/// `init` regions. It also leaves the `builder's insertions point in a state
1406/// where the user can continue the code-gen directly afterwards.
1407template <typename OP>
1408static LogicalResult
1409initReductionVars(OP op, ArrayRef<BlockArgument> reductionArgs,
1410 llvm::IRBuilderBase &builder,
1411 LLVM::ModuleTranslation &moduleTranslation,
1412 llvm::BasicBlock *latestAllocaBlock,
1414 SmallVectorImpl<llvm::Value *> &privateReductionVariables,
1415 DenseMap<Value, llvm::Value *> &reductionVariableMap,
1416 llvm::ArrayRef<bool> isByRef,
1417 SmallVectorImpl<DeferredStore> &deferredStores) {
1418 if (op.getNumReductionVars() == 0)
1419 return success();
1420
1421 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
1422 bool useDeviceSharedMem = omp::opInSharedDeviceContext(*op);
1423
1424 llvm::BasicBlock *initBlock = splitBB(builder, true, "omp.reduction.init");
1425 auto allocaIP = llvm::IRBuilderBase::InsertPoint(
1426 latestAllocaBlock, latestAllocaBlock->getTerminator()->getIterator());
1427 builder.restoreIP(allocaIP);
1428 SmallVector<llvm::Value *> byRefVars(op.getNumReductionVars());
1429
1430 for (unsigned i = 0; i < op.getNumReductionVars(); ++i) {
1431 if (isByRef[i]) {
1432 if (!reductionDecls[i].getAllocRegion().empty())
1433 continue;
1434
1435 // TODO: remove after all users of by-ref are updated to use the alloc
1436 // region: Allocate reduction variable (which is a pointer to the real
1437 // reduciton variable allocated in the inlined region)
1438 llvm::Type *varTy =
1439 moduleTranslation.convertType(reductionDecls[i].getType());
1440 if (useDeviceSharedMem)
1441 byRefVars[i] = ompBuilder->createOMPAllocShared(builder, varTy);
1442 else
1443 byRefVars[i] = builder.CreateAlloca(varTy);
1444 }
1445 }
1446
1447 setInsertPointForPossiblyEmptyBlock(builder, initBlock);
1448
1449 // store result of the alloc region to the allocated pointer to the real
1450 // reduction variable
1451 for (auto [data, addr] : deferredStores)
1452 builder.CreateStore(data, addr);
1453
1454 // Before the loop, store the initial values of reductions into reduction
1455 // variables. Although this could be done after allocas, we don't want to mess
1456 // up with the alloca insertion point.
1457 for (unsigned i = 0; i < op.getNumReductionVars(); ++i) {
1459
1460 // map block argument to initializer region
1461 mapInitializationArgs(op, moduleTranslation, builder, reductionDecls,
1462 reductionVariableMap, i);
1463
1464 // TODO In some cases (specially on the GPU), the init regions may
1465 // contains stack alloctaions. If the region is inlined in a loop, this is
1466 // problematic. Instead of just inlining the region, handle allocations by
1467 // hoisting fixed length allocations to the function entry and using
1468 // stacksave and restore for variable length ones.
1469 if (failed(inlineConvertOmpRegions(reductionDecls[i].getInitializerRegion(),
1470 "omp.reduction.neutral", builder,
1471 moduleTranslation, &phis)))
1472 return failure();
1473
1474 assert(phis.size() == 1 && "expected one value to be yielded from the "
1475 "reduction neutral element declaration region");
1476
1478
1479 if (isByRef[i]) {
1480 if (!reductionDecls[i].getAllocRegion().empty())
1481 // done in allocReductionVars
1482 continue;
1483
1484 // TODO: this path can be removed once all users of by-ref are updated to
1485 // use an alloc region
1486
1487 // Store the result of the inlined region to the allocated reduction var
1488 // ptr
1489 builder.CreateStore(phis[0], byRefVars[i]);
1490
1491 privateReductionVariables[i] = byRefVars[i];
1492 moduleTranslation.mapValue(reductionArgs[i], phis[0]);
1493 reductionVariableMap.try_emplace(op.getReductionVars()[i], phis[0]);
1494 } else {
1495 // for by-ref case the store is inside of the reduction region
1496 builder.CreateStore(phis[0], privateReductionVariables[i]);
1497 // the rest was handled in allocByValReductionVars
1498 }
1499
1500 // forget the mapping for the initializer region because we might need a
1501 // different mapping if this reduction declaration is re-used for a
1502 // different variable
1503 moduleTranslation.forgetMapping(reductionDecls[i].getInitializerRegion());
1504 }
1505
1506 return success();
1507}
1508
1509/// Collect reduction info
1510template <typename T>
1511static void collectReductionInfo(
1512 T loop, llvm::IRBuilderBase &builder,
1513 LLVM::ModuleTranslation &moduleTranslation,
1516 SmallVectorImpl<OwningAtomicReductionGen> &owningAtomicReductionGens,
1518 const ArrayRef<llvm::Value *> privateReductionVariables,
1520 ArrayRef<bool> isByRef) {
1521 unsigned numReductions = loop.getNumReductionVars();
1522
1523 for (unsigned i = 0; i < numReductions; ++i) {
1524 owningReductionGens.push_back(
1525 makeReductionGen(reductionDecls[i], builder, moduleTranslation));
1526 owningAtomicReductionGens.push_back(
1527 makeAtomicReductionGen(reductionDecls[i], builder, moduleTranslation));
1529 reductionDecls[i], builder, moduleTranslation, isByRef[i]));
1530 }
1531
1532 // Collect the reduction information.
1533 reductionInfos.reserve(numReductions);
1534 for (unsigned i = 0; i < numReductions; ++i) {
1535 llvm::OpenMPIRBuilder::ReductionGenAtomicCBTy atomicGen = nullptr;
1536 if (owningAtomicReductionGens[i])
1537 atomicGen = owningAtomicReductionGens[i];
1538 llvm::Value *variable =
1539 moduleTranslation.lookupValue(loop.getReductionVars()[i]);
1540 mlir::Type allocatedType;
1541 reductionDecls[i].getAllocRegion().walk([&](mlir::Operation *op) {
1542 if (auto alloca = mlir::dyn_cast<LLVM::AllocaOp>(op)) {
1543 allocatedType = alloca.getElemType();
1545 }
1546
1548 });
1549
1550 reductionInfos.push_back(
1551 {moduleTranslation.convertType(reductionDecls[i].getType()), variable,
1552 privateReductionVariables[i],
1553 /*EvaluationKind=*/llvm::OpenMPIRBuilder::EvalKind::Scalar,
1555 /*ReductionGenClang=*/nullptr, atomicGen,
1557 allocatedType ? moduleTranslation.convertType(allocatedType) : nullptr,
1558 reductionDecls[i].getByrefElementType()
1559 ? moduleTranslation.convertType(
1560 *reductionDecls[i].getByrefElementType())
1561 : nullptr});
1562 }
1563}
1564
1565/// handling of DeclareReductionOp's cleanup region
1566static LogicalResult
1568 llvm::ArrayRef<llvm::Value *> privateVariables,
1569 LLVM::ModuleTranslation &moduleTranslation,
1570 llvm::IRBuilderBase &builder, StringRef regionName,
1571 bool shouldLoadCleanupRegionArg = true) {
1572 for (auto [i, cleanupRegion] : llvm::enumerate(cleanupRegions)) {
1573 if (cleanupRegion->empty())
1574 continue;
1575
1576 // map the argument to the cleanup region
1577 Block &entry = cleanupRegion->front();
1578
1579 llvm::Instruction *potentialTerminator =
1580 builder.GetInsertBlock()->empty() ? nullptr
1581 : &builder.GetInsertBlock()->back();
1582 if (potentialTerminator && potentialTerminator->isTerminator())
1583 builder.SetInsertPoint(potentialTerminator);
1584 llvm::Value *privateVarValue =
1585 shouldLoadCleanupRegionArg
1586 ? builder.CreateLoad(
1587 moduleTranslation.convertType(entry.getArgument(0).getType()),
1588 privateVariables[i])
1589 : privateVariables[i];
1590
1591 moduleTranslation.mapValue(entry.getArgument(0), privateVarValue);
1592
1593 if (failed(inlineConvertOmpRegions(*cleanupRegion, regionName, builder,
1594 moduleTranslation)))
1595 return failure();
1596
1597 // clear block argument mapping in case it needs to be re-created with a
1598 // different source for another use of the same reduction decl
1599 moduleTranslation.forgetMapping(*cleanupRegion);
1600 }
1601 return success();
1602}
1603
1604// TODO: not used by ParallelOp
1605template <class OP>
1606static LogicalResult createReductionsAndCleanup(
1607 OP op, llvm::IRBuilderBase &builder,
1608 LLVM::ModuleTranslation &moduleTranslation,
1609 llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
1611 ArrayRef<llvm::Value *> privateReductionVariables, ArrayRef<bool> isByRef,
1612 bool isNowait = false, bool isTeamsReduction = false) {
1613 // Process the reductions if required.
1614 if (op.getNumReductionVars() == 0)
1615 return success();
1616
1618 SmallVector<OwningAtomicReductionGen> owningAtomicReductionGens;
1619 SmallVector<OwningDataPtrPtrReductionGen> owningReductionGenRefDataPtrGens;
1621
1622 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
1623
1624 // Create the reduction generators. We need to own them here because
1625 // ReductionInfo only accepts references to the generators.
1626 collectReductionInfo(op, builder, moduleTranslation, reductionDecls,
1627 owningReductionGens, owningAtomicReductionGens,
1628 owningReductionGenRefDataPtrGens,
1629 privateReductionVariables, reductionInfos, isByRef);
1630
1631 // The call to createReductions below expects the block to have a
1632 // terminator. Create an unreachable instruction to serve as terminator
1633 // and remove it later.
1634 llvm::UnreachableInst *tempTerminator = builder.CreateUnreachable();
1635 builder.SetInsertPoint(tempTerminator);
1636 llvm::OpenMPIRBuilder::InsertPointOrErrorTy contInsertPoint =
1637 ompBuilder->createReductions(builder.saveIP(), allocaIP, reductionInfos,
1638 isByRef, isNowait, isTeamsReduction);
1639
1640 if (failed(handleError(contInsertPoint, *op)))
1641 return failure();
1642
1643 if (!contInsertPoint->getBlock())
1644 return op->emitOpError() << "failed to convert reductions";
1645
1646 llvm::OpenMPIRBuilder::InsertPointTy afterIP = *contInsertPoint;
1647 if (!isTeamsReduction) {
1648 llvm::OpenMPIRBuilder::InsertPointOrErrorTy barrierIP =
1649 ompBuilder->createBarrier(*contInsertPoint, llvm::omp::OMPD_for);
1650
1651 if (failed(handleError(barrierIP, *op)))
1652 return failure();
1653 afterIP = *barrierIP;
1654 }
1655
1656 tempTerminator->eraseFromParent();
1657 builder.restoreIP(afterIP);
1658
1659 // after the construct, deallocate private reduction variables
1660 SmallVector<Region *> reductionRegions;
1661 llvm::transform(reductionDecls, std::back_inserter(reductionRegions),
1662 [](omp::DeclareReductionOp reductionDecl) {
1663 return &reductionDecl.getCleanupRegion();
1664 });
1665 LogicalResult result = inlineOmpRegionCleanup(
1666 reductionRegions, privateReductionVariables, moduleTranslation, builder,
1667 "omp.reduction.cleanup");
1668
1669 bool useDeviceSharedMem = omp::opInSharedDeviceContext(*op);
1670 if (useDeviceSharedMem) {
1671 for (auto [var, reductionDecl] :
1672 llvm::zip_equal(privateReductionVariables, reductionDecls))
1673 ompBuilder->createOMPFreeShared(
1674 builder, var, moduleTranslation.convertType(reductionDecl.getType()));
1675 }
1676
1677 return result;
1678}
1679
1680static ArrayRef<bool> getIsByRef(std::optional<ArrayRef<bool>> attr) {
1681 if (!attr)
1682 return {};
1683 return *attr;
1684}
1685
1686// TODO: not used by omp.parallel
1687template <typename OP>
1689 OP op, ArrayRef<BlockArgument> reductionArgs, llvm::IRBuilderBase &builder,
1690 LLVM::ModuleTranslation &moduleTranslation,
1691 llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
1693 SmallVectorImpl<llvm::Value *> &privateReductionVariables,
1694 DenseMap<Value, llvm::Value *> &reductionVariableMap,
1695 llvm::ArrayRef<bool> isByRef) {
1696 if (op.getNumReductionVars() == 0)
1697 return success();
1698
1699 SmallVector<DeferredStore> deferredStores;
1700
1701 if (failed(allocReductionVars(op, reductionArgs, builder, moduleTranslation,
1702 allocaIP, reductionDecls,
1703 privateReductionVariables, reductionVariableMap,
1704 deferredStores, isByRef)))
1705 return failure();
1706
1707 return initReductionVars(op, reductionArgs, builder, moduleTranslation,
1708 allocaIP.getBlock(), reductionDecls,
1709 privateReductionVariables, reductionVariableMap,
1710 isByRef, deferredStores);
1711}
1712
1713/// Return the llvm::Value * corresponding to the `privateVar` that
1714/// is being privatized. It isn't always as simple as looking up
1715/// moduleTranslation with privateVar. For instance, in case of
1716/// an allocatable, the descriptor for the allocatable is privatized.
1717/// This descriptor is mapped using an MapInfoOp. So, this function
1718/// will return a pointer to the llvm::Value corresponding to the
1719/// block argument for the mapped descriptor.
1720static llvm::Value *
1721findAssociatedValue(Value privateVar, llvm::IRBuilderBase &builder,
1722 LLVM::ModuleTranslation &moduleTranslation,
1723 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1724 if (mappedPrivateVars == nullptr || !mappedPrivateVars->contains(privateVar))
1725 return moduleTranslation.lookupValue(privateVar);
1726
1727 Value blockArg = (*mappedPrivateVars)[privateVar];
1728 Type privVarType = privateVar.getType();
1729 Type blockArgType = blockArg.getType();
1730 assert(isa<LLVM::LLVMPointerType>(blockArgType) &&
1731 "A block argument corresponding to a mapped var should have "
1732 "!llvm.ptr type");
1733
1734 if (privVarType == blockArgType)
1735 return moduleTranslation.lookupValue(blockArg);
1736
1737 // This typically happens when the privatized type is lowered from
1738 // boxchar<KIND> and gets lowered to !llvm.struct<(ptr, i64)>. That is the
1739 // struct/pair is passed by value. But, mapped values are passed only as
1740 // pointers, so before we privatize, we must load the pointer.
1741 if (!isa<LLVM::LLVMPointerType>(privVarType))
1742 return builder.CreateLoad(moduleTranslation.convertType(privVarType),
1743 moduleTranslation.lookupValue(blockArg));
1744
1745 return moduleTranslation.lookupValue(privateVar);
1746}
1747
1748// Privatizer region arguments may be by-value even when the available LLVM
1749// value is storage for that value, e.g. lowered Fortran boxchar descriptors in
1750// task context structs. Materialize the value expected by the region argument
1751// while preserving the existing pointer mapping for pointer arguments.
1752static llvm::Value *
1753materializeRegionArgValue(llvm::IRBuilderBase &builder,
1754 LLVM::ModuleTranslation &moduleTranslation,
1755 BlockArgument regionArg, llvm::Value *value) {
1756 if (!regionArg)
1757 return value;
1758
1759 llvm::Type *regionArgType =
1760 moduleTranslation.convertType(regionArg.getType());
1761 if (regionArgType->isPointerTy() || !value->getType()->isPointerTy())
1762 return value;
1763
1764 return builder.CreateLoad(regionArgType, value);
1765}
1766
1767/// Initialize a single (first)private variable. You probably want to use
1768/// allocateAndInitPrivateVars instead of this.
1769/// This returns the private variable which has been initialized. This
1770/// variable should be mapped before constructing the body of the Op.
1772initPrivateVar(llvm::IRBuilderBase &builder,
1773 LLVM::ModuleTranslation &moduleTranslation,
1774 omp::PrivateClauseOp &privDecl, llvm::Value *nonPrivateVar,
1775 BlockArgument &blockArg, llvm::Value *llvmPrivateVar,
1776 llvm::BasicBlock *privInitBlock,
1777 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1778 Region &initRegion = privDecl.getInitRegion();
1779 if (initRegion.empty())
1780 return llvmPrivateVar;
1781
1782 assert(nonPrivateVar);
1783 moduleTranslation.mapValue(privDecl.getInitMoldArg(), nonPrivateVar);
1784 moduleTranslation.mapValue(privDecl.getInitPrivateArg(), llvmPrivateVar);
1785
1786 // in-place convert the private initialization region
1788 if (failed(inlineConvertOmpRegions(initRegion, "omp.private.init", builder,
1789 moduleTranslation, &phis)))
1790 return llvm::createStringError(
1791 "failed to inline `init` region of `omp.private`");
1792
1793 assert(phis.size() == 1 && "expected one allocation to be yielded");
1794
1795 // clear init region block argument mapping in case it needs to be
1796 // re-created with a different source for another use of the same
1797 // reduction decl
1798 moduleTranslation.forgetMapping(initRegion);
1799
1800 // Prefer the value yielded from the init region to the allocated private
1801 // variable in case the region is operating on arguments by-value (e.g.
1802 // Fortran character boxes).
1803 return phis[0];
1804}
1805
1806/// Version of initPrivateVar which looks up the nonPrivateVar from mlirPrivVar.
1808 llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation,
1809 omp::PrivateClauseOp &privDecl, Value mlirPrivVar, BlockArgument &blockArg,
1810 llvm::Value *llvmPrivateVar, llvm::BasicBlock *privInitBlock,
1811 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1812 return initPrivateVar(
1813 builder, moduleTranslation, privDecl,
1814 findAssociatedValue(mlirPrivVar, builder, moduleTranslation,
1815 mappedPrivateVars),
1816 blockArg, llvmPrivateVar, privInitBlock, mappedPrivateVars);
1817}
1818
1819static llvm::Error
1820initPrivateVars(llvm::IRBuilderBase &builder,
1821 LLVM::ModuleTranslation &moduleTranslation,
1822 PrivateVarsInfo &privateVarsInfo,
1823 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1824 if (privateVarsInfo.blockArgs.empty())
1825 return llvm::Error::success();
1826
1827 llvm::BasicBlock *privInitBlock = splitBB(builder, true, "omp.private.init");
1828 setInsertPointForPossiblyEmptyBlock(builder, privInitBlock);
1829
1830 for (auto [idx, zip] : llvm::enumerate(llvm::zip_equal(
1831 privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
1832 privateVarsInfo.blockArgs, privateVarsInfo.llvmVars))) {
1833 auto [privDecl, mlirPrivVar, blockArg, llvmPrivateVar] = zip;
1835 builder, moduleTranslation, privDecl, mlirPrivVar, blockArg,
1836 llvmPrivateVar, privInitBlock, mappedPrivateVars);
1837
1838 if (!privVarOrErr)
1839 return privVarOrErr.takeError();
1840
1841 llvmPrivateVar = privVarOrErr.get();
1842 moduleTranslation.mapValue(blockArg, llvmPrivateVar);
1843
1845 }
1846
1847 return llvm::Error::success();
1848}
1849
1850/// Allocate and initialize delayed private variables. Returns the basic block
1851/// which comes after all of these allocations. llvm::Value * for each of these
1852/// private variables are populated in llvmPrivateVars.
1853template <typename T>
1855allocatePrivateVars(T op, llvm::IRBuilderBase &builder,
1856 LLVM::ModuleTranslation &moduleTranslation,
1857 PrivateVarsInfo &privateVarsInfo,
1858 const llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
1859 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1860 // Allocate private vars
1861 llvm::Instruction *allocaTerminator = allocaIP.getBlock()->getTerminator();
1862 splitBB(llvm::OpenMPIRBuilder::InsertPointTy(allocaIP.getBlock(),
1863 allocaTerminator->getIterator()),
1864 true, allocaTerminator->getStableDebugLoc(),
1865 "omp.region.after_alloca");
1866
1867 llvm::IRBuilderBase::InsertPointGuard guard(builder);
1868 // Update the allocaTerminator since the alloca block was split above.
1869 allocaTerminator = allocaIP.getBlock()->getTerminator();
1870 builder.SetInsertPoint(allocaTerminator);
1871 // The new terminator is an uncondition branch created by the splitBB above.
1872 assert(allocaTerminator->getNumSuccessors() == 1 &&
1873 "This is an unconditional branch created by splitBB");
1874
1875 llvm::DataLayout dataLayout = builder.GetInsertBlock()->getDataLayout();
1876 llvm::BasicBlock *afterAllocas = allocaTerminator->getSuccessor(0);
1877
1878 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
1879 bool mightUseDeviceSharedMem = omp::opInSharedDeviceContext(*op);
1880 unsigned int allocaAS =
1881 moduleTranslation.getLLVMModule()->getDataLayout().getAllocaAddrSpace();
1882 unsigned int defaultAS = moduleTranslation.getLLVMModule()
1883 ->getDataLayout()
1884 .getProgramAddressSpace();
1885
1886 for (auto [privDecl, mlirPrivVar, blockArg] :
1887 llvm::zip_equal(privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
1888 privateVarsInfo.blockArgs)) {
1889 llvm::Type *llvmAllocType =
1890 moduleTranslation.convertType(privDecl.getType());
1891 builder.SetInsertPoint(allocaIP.getBlock()->getTerminator());
1892 llvm::Value *llvmPrivateVar = nullptr;
1893 if (mightUseDeviceSharedMem && omp::allocaUsesRequireSharedMem(blockArg)) {
1894 llvmPrivateVar = ompBuilder->createOMPAllocShared(builder, llvmAllocType);
1895 } else {
1896 llvmPrivateVar = builder.CreateAlloca(
1897 llvmAllocType, /*ArraySize=*/nullptr, "omp.private.alloc");
1898 if (allocaAS != defaultAS)
1899 llvmPrivateVar = builder.CreateAddrSpaceCast(
1900 llvmPrivateVar, builder.getPtrTy(defaultAS));
1901 }
1902
1903 privateVarsInfo.llvmVars.push_back(llvmPrivateVar);
1904 }
1905
1906 return afterAllocas;
1907}
1908
1909/// This can't always be determined statically, but when we can, it is good to
1910/// avoid generating compiler-added barriers which will deadlock the program.
1912 for (mlir::Operation *parent = op->getParentOp(); parent != nullptr;
1913 parent = parent->getParentOp()) {
1914 if (mlir::isa<omp::SingleOp, omp::CriticalOp>(parent))
1915 return true;
1916
1917 // e.g.
1918 // omp.single {
1919 // omp.parallel {
1920 // op
1921 // }
1922 // }
1923 if (mlir::isa<omp::ParallelOp>(parent))
1924 return false;
1925 }
1926 return false;
1927}
1928
1929static LogicalResult copyFirstPrivateVars(
1930 mlir::Operation *op, llvm::IRBuilderBase &builder,
1931 LLVM::ModuleTranslation &moduleTranslation,
1933 ArrayRef<llvm::Value *> llvmPrivateVars,
1934 SmallVectorImpl<omp::PrivateClauseOp> &privateDecls, bool insertBarrier,
1935 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1936 // Apply copy region for firstprivate.
1937 bool needsFirstprivate =
1938 llvm::any_of(privateDecls, [](omp::PrivateClauseOp &privOp) {
1939 return privOp.getDataSharingType() ==
1940 omp::DataSharingClauseType::FirstPrivate;
1941 });
1942
1943 if (!needsFirstprivate)
1944 return success();
1945
1946 llvm::BasicBlock *copyBlock =
1947 splitBB(builder, /*CreateBranch=*/true, "omp.private.copy");
1948 setInsertPointForPossiblyEmptyBlock(builder, copyBlock);
1949
1950 for (auto [decl, moldVar, llvmVar] :
1951 llvm::zip_equal(privateDecls, moldVars, llvmPrivateVars)) {
1952 if (decl.getDataSharingType() != omp::DataSharingClauseType::FirstPrivate)
1953 continue;
1954
1955 // copyRegion implements `lhs = rhs`
1956 Region &copyRegion = decl.getCopyRegion();
1957
1958 llvm::Value *copyMoldVar = materializeRegionArgValue(
1959 builder, moduleTranslation, decl.getCopyMoldArg(), moldVar);
1960 llvm::Value *copyPrivateVar = materializeRegionArgValue(
1961 builder, moduleTranslation, decl.getCopyPrivateArg(), llvmVar);
1962
1963 moduleTranslation.mapValue(decl.getCopyMoldArg(), copyMoldVar);
1964
1965 // map copyRegion lhs arg
1966 moduleTranslation.mapValue(decl.getCopyPrivateArg(), copyPrivateVar);
1967
1968 // in-place convert copy region
1969 if (failed(inlineConvertOmpRegions(copyRegion, "omp.private.copy", builder,
1970 moduleTranslation)))
1971 return decl.emitError("failed to inline `copy` region of `omp.private`");
1972
1974
1975 // ignore unused value yielded from copy region
1976
1977 // clear copy region block argument mapping in case it needs to be
1978 // re-created with different sources for reuse of the same reduction
1979 // decl
1980 moduleTranslation.forgetMapping(copyRegion);
1981 }
1982
1983 if (insertBarrier && !opIsInSingleThread(op)) {
1984 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
1985 llvm::OpenMPIRBuilder::InsertPointOrErrorTy res =
1986 ompBuilder->createBarrier(builder.saveIP(), llvm::omp::OMPD_barrier);
1987 if (failed(handleError(res, *op)))
1988 return failure();
1989 }
1990
1991 return success();
1992}
1993
1994static LogicalResult copyFirstPrivateVars(
1995 mlir::Operation *op, llvm::IRBuilderBase &builder,
1996 LLVM::ModuleTranslation &moduleTranslation,
1997 SmallVectorImpl<mlir::Value> &mlirPrivateVars,
1998 ArrayRef<llvm::Value *> llvmPrivateVars,
1999 SmallVectorImpl<omp::PrivateClauseOp> &privateDecls, bool insertBarrier,
2000 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
2001 llvm::SmallVector<llvm::Value *> moldVars(mlirPrivateVars.size());
2002 llvm::transform(mlirPrivateVars, moldVars.begin(), [&](mlir::Value mlirVar) {
2003 // map copyRegion rhs arg
2004 llvm::Value *moldVar = findAssociatedValue(
2005 mlirVar, builder, moduleTranslation, mappedPrivateVars);
2006 assert(moldVar);
2007 return moldVar;
2008 });
2009 return copyFirstPrivateVars(op, builder, moduleTranslation, moldVars,
2010 llvmPrivateVars, privateDecls, insertBarrier,
2011 mappedPrivateVars);
2012}
2013
2014template <typename T>
2015static LogicalResult
2016cleanupPrivateVars(T op, llvm::IRBuilderBase &builder,
2017 LLVM::ModuleTranslation &moduleTranslation, Location loc,
2018 PrivateVarsInfo &privateVarsInfo) {
2019 // private variable deallocation
2020 SmallVector<Region *> privateCleanupRegions;
2021 llvm::transform(privateVarsInfo.privatizers,
2022 std::back_inserter(privateCleanupRegions),
2023 [](omp::PrivateClauseOp privatizer) {
2024 return &privatizer.getDeallocRegion();
2025 });
2026
2027 if (failed(inlineOmpRegionCleanup(privateCleanupRegions,
2028 privateVarsInfo.llvmVars, moduleTranslation,
2029 builder, "omp.private.dealloc",
2030 /*shouldLoadCleanupRegionArg=*/false)))
2031 return mlir::emitError(loc, "failed to inline `dealloc` region of an "
2032 "`omp.private` op in");
2033
2034 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
2035 bool mightUseDeviceSharedMem = omp::opInSharedDeviceContext(*op);
2036 for (auto [privDecl, llvmPrivVar, blockArg] :
2037 llvm::zip_equal(privateVarsInfo.privatizers, privateVarsInfo.llvmVars,
2038 privateVarsInfo.blockArgs)) {
2039 if (mightUseDeviceSharedMem && omp::allocaUsesRequireSharedMem(blockArg)) {
2040 ompBuilder->createOMPFreeShared(
2041 builder, llvmPrivVar,
2042 moduleTranslation.convertType(privDecl.getType()));
2043 }
2044 }
2045
2046 return success();
2047}
2048
2049/// Returns true if the construct contains omp.cancel or omp.cancellation_point
2051 // omp.cancel and omp.cancellation_point must be "closely nested" so they will
2052 // be visible and not inside of function calls. This is enforced by the
2053 // verifier.
2054 return op
2055 ->walk([](Operation *child) {
2056 if (mlir::isa<omp::CancelOp, omp::CancellationPointOp>(child))
2057 return WalkResult::interrupt();
2058 return WalkResult::advance();
2059 })
2060 .wasInterrupted();
2061}
2062
2063// Forward declarations for the task-reduction helpers defined alongside the
2064// omp.taskgroup lowering further down in this file. These are shared by the
2065// `reduction(task, ...)` modifier lowering on the parallel/worksharing
2066// constructs and by the omp.taskgroup / omp.taskloop.context task_reduction
2067// lowering. When \p isModifier is set, `__kmpc_taskred_modifier_init` is
2068// emitted (opening a task-reduction scope) instead of `__kmpc_taskred_init`,
2069// with \p isWorksharing selecting the runtime `is_ws` argument.
2070static llvm::Value *emitTaskReductionInitCall(
2072 ArrayRef<llvm::Value *> origPtrs, StringRef helperNamePrefix,
2073 llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder::InsertPointTy allocaIP,
2074 LLVM::ModuleTranslation &moduleTranslation, bool isModifier = false,
2075 bool isWorksharing = false);
2076static void
2077emitTaskReductionModifierFini(bool isWorksharing, llvm::IRBuilderBase &builder,
2078 LLVM::ModuleTranslation &moduleTranslation);
2079
2080static LogicalResult
2081convertOmpSections(Operation &opInst, llvm::IRBuilderBase &builder,
2082 LLVM::ModuleTranslation &moduleTranslation) {
2083 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2084 using StorableBodyGenCallbackTy =
2085 llvm::OpenMPIRBuilder::StorableBodyGenCallbackTy;
2086
2087 auto sectionsOp = cast<omp::SectionsOp>(opInst);
2088
2089 if (failed(checkImplementationStatus(opInst)))
2090 return failure();
2091
2092 llvm::ArrayRef<bool> isByRef = getIsByRef(sectionsOp.getReductionByref());
2093 assert(isByRef.size() == sectionsOp.getNumReductionVars());
2094
2096 collectReductionDecls(sectionsOp, reductionDecls);
2097 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
2098 findAllocInsertPoints(builder, moduleTranslation);
2099
2100 SmallVector<llvm::Value *> privateReductionVariables(
2101 sectionsOp.getNumReductionVars());
2102 DenseMap<Value, llvm::Value *> reductionVariableMap;
2103
2104 MutableArrayRef<BlockArgument> reductionArgs =
2105 cast<omp::BlockArgOpenMPOpInterface>(opInst).getReductionBlockArgs();
2106
2108 sectionsOp, reductionArgs, builder, moduleTranslation, allocaIP,
2109 reductionDecls, privateReductionVariables, reductionVariableMap,
2110 isByRef)))
2111 return failure();
2112
2113 bool isTaskReductionMod =
2114 sectionsOp.getReductionMod() == omp::ReductionModifier::task &&
2115 sectionsOp.getNumReductionVars() > 0;
2116
2118
2119 for (Operation &op : *sectionsOp.getRegion().begin()) {
2120 auto sectionOp = dyn_cast<omp::SectionOp>(op);
2121 if (!sectionOp) // omp.terminator
2122 continue;
2123
2124 Region &region = sectionOp.getRegion();
2125 auto sectionCB = [&sectionsOp, &region, &builder, &moduleTranslation](
2126 InsertPointTy allocaIP, InsertPointTy codeGenIP,
2127 ArrayRef<llvm::BasicBlock *> deallocBlocks) {
2128 builder.restoreIP(codeGenIP);
2129
2130 // map the omp.section reduction block argument to the omp.sections block
2131 // arguments
2132 // TODO: this assumes that the only block arguments are reduction
2133 // variables
2134 assert(region.getNumArguments() ==
2135 sectionsOp.getRegion().getNumArguments());
2136 for (auto [sectionsArg, sectionArg] : llvm::zip_equal(
2137 sectionsOp.getRegion().getArguments(), region.getArguments())) {
2138 llvm::Value *llvmVal = moduleTranslation.lookupValue(sectionsArg);
2139 assert(llvmVal);
2140 moduleTranslation.mapValue(sectionArg, llvmVal);
2141 }
2142
2143 return convertOmpOpRegions(region, "omp.section.region", builder,
2144 moduleTranslation)
2145 .takeError();
2146 };
2147 sectionCBs.push_back(sectionCB);
2148 }
2149
2150 // No sections within omp.sections operation - skip generation. This situation
2151 // is only possible if there is only a terminator operation inside the
2152 // sections operation
2153 if (sectionCBs.empty())
2154 return success();
2155
2156 // For `reduction(task, ...)` open a task-reduction scope for the worksharing
2157 // region. Participating explicit tasks accumulate into the per-thread private
2158 // copies, which the worksharing reduction then combines across threads. This
2159 // is emitted only after the empty-sections early return above, so it stays
2160 // balanced with the matching fini emitted after the sections region.
2161 if (isTaskReductionMod &&
2162 !emitTaskReductionInitCall(reductionDecls, privateReductionVariables,
2163 "__omp_taskred_mod_", builder, allocaIP,
2164 moduleTranslation, /*isModifier=*/true,
2165 /*isWorksharing=*/true))
2166 return sectionsOp.emitError(
2167 "failed to emit task reduction modifier initialization");
2168
2169 assert(isa<omp::SectionOp>(*sectionsOp.getRegion().op_begin()));
2170
2171 // TODO: Perform appropriate actions according to the data-sharing
2172 // attribute (shared, private, firstprivate, ...) of variables.
2173 // Currently defaults to shared.
2174 auto privCB = [&](InsertPointTy, InsertPointTy codeGenIP, llvm::Value &,
2175 llvm::Value &vPtr, llvm::Value *&replacementValue)
2176 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
2177 replacementValue = &vPtr;
2178 return codeGenIP;
2179 };
2180
2181 // TODO: Perform finalization actions for variables. This has to be
2182 // called for variables which have destructors/finalizers.
2183 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
2184
2185 allocaIP = findAllocInsertPoints(builder, moduleTranslation);
2186 bool isCancellable = constructIsCancellable(sectionsOp);
2187 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2188 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2189 moduleTranslation.getOpenMPBuilder()->createSections(
2190 ompLoc, allocaIP, sectionCBs, privCB, finiCB, isCancellable,
2191 sectionsOp.getNowait());
2192
2193 if (failed(handleError(afterIP, opInst)))
2194 return failure();
2195
2196 builder.restoreIP(*afterIP);
2197
2198 // Close the task-reduction scope before combining the worksharing copies.
2199 if (isTaskReductionMod)
2200 emitTaskReductionModifierFini(/*isWorksharing=*/true, builder,
2201 moduleTranslation);
2202
2203 // Process the reductions if required.
2205 sectionsOp, builder, moduleTranslation, allocaIP, reductionDecls,
2206 privateReductionVariables, isByRef, sectionsOp.getNowait());
2207}
2208
2209/// Converts an OpenMP scope construct into LLVM IR.
2210static LogicalResult
2211convertOmpScope(omp::ScopeOp &scopeOp, llvm::IRBuilderBase &builder,
2212 LLVM::ModuleTranslation &moduleTranslation) {
2213 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2214 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
2215
2216 if (failed(checkImplementationStatus(*scopeOp)))
2217 return failure();
2218
2219 llvm::ArrayRef<bool> isByRef = getIsByRef(scopeOp.getReductionByref());
2220 assert(isByRef.size() == scopeOp.getNumReductionVars());
2221
2222 PrivateVarsInfo privateVarsInfo(scopeOp);
2223
2225 collectReductionDecls(scopeOp, reductionDecls);
2226 InsertPointTy allocaIP = findAllocInsertPoints(builder, moduleTranslation);
2227
2228 SmallVector<llvm::Value *> privateReductionVariables(
2229 scopeOp.getNumReductionVars());
2230 DenseMap<Value, llvm::Value *> reductionVariableMap;
2231
2232 MutableArrayRef<BlockArgument> reductionArgs =
2233 cast<omp::BlockArgOpenMPOpInterface>(*scopeOp).getReductionBlockArgs();
2234
2235 // Allocate private vars before the scope body
2237 scopeOp, builder, moduleTranslation, privateVarsInfo, allocaIP);
2238 if (failed(handleError(afterAllocas, *scopeOp)))
2239 return failure();
2240
2242 scopeOp, reductionArgs, builder, moduleTranslation, allocaIP,
2243 reductionDecls, privateReductionVariables, reductionVariableMap,
2244 isByRef)))
2245 return failure();
2246
2247 auto bodyCB =
2248 [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
2249 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
2250 builder.restoreIP(codeGenIP);
2251
2252 if (handleError(
2253 initPrivateVars(builder, moduleTranslation, privateVarsInfo),
2254 *scopeOp)
2255 .failed())
2256 return llvm::make_error<PreviouslyReportedError>();
2257
2258 if (failed(copyFirstPrivateVars(
2259 scopeOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
2260 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
2261 scopeOp.getPrivateNeedsBarrier())))
2262 return llvm::make_error<PreviouslyReportedError>();
2263
2264 return convertOmpOpRegions(scopeOp.getRegion(), "omp.scope.region", builder,
2265 moduleTranslation)
2266 .takeError();
2267 };
2268
2269 auto finiCB = [&](InsertPointTy codeGenIP) -> llvm::Error {
2270 InsertPointTy oldIP = builder.saveIP();
2271 builder.restoreIP(codeGenIP);
2272 if (failed(cleanupPrivateVars(scopeOp, builder, moduleTranslation,
2273 scopeOp.getLoc(), privateVarsInfo)))
2274 return llvm::make_error<PreviouslyReportedError>();
2275 builder.restoreIP(oldIP);
2276 return llvm::Error::success();
2277 };
2278
2279 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2280 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2281 ompBuilder->createScope(ompLoc, bodyCB, finiCB, scopeOp.getNowait());
2282
2283 if (failed(handleError(afterIP, *scopeOp)))
2284 return failure();
2285
2286 builder.restoreIP(*afterIP);
2287
2288 // Process the reductions if required.
2290 scopeOp, builder, moduleTranslation, allocaIP, reductionDecls,
2291 privateReductionVariables, isByRef, scopeOp.getNowait(),
2292 /*isTeamsReduction=*/false);
2293}
2294
2295/// Converts an OpenMP single construct into LLVM IR using OpenMPIRBuilder.
2296static LogicalResult
2297convertOmpSingle(omp::SingleOp &singleOp, llvm::IRBuilderBase &builder,
2298 LLVM::ModuleTranslation &moduleTranslation) {
2299 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2300 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2301
2302 if (failed(checkImplementationStatus(*singleOp)))
2303 return failure();
2304
2305 auto bodyCB = [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
2306 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) {
2307 builder.restoreIP(codegenIP);
2308 return convertOmpOpRegions(singleOp.getRegion(), "omp.single.region",
2309 builder, moduleTranslation)
2310 .takeError();
2311 };
2312 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
2313
2314 // Handle copyprivate
2315 Operation::operand_range cpVars = singleOp.getCopyprivateVars();
2316 std::optional<ArrayAttr> cpFuncs = singleOp.getCopyprivateSyms();
2319 for (size_t i = 0, e = cpVars.size(); i < e; ++i) {
2320 llvmCPVars.push_back(moduleTranslation.lookupValue(cpVars[i]));
2322 singleOp, cast<SymbolRefAttr>((*cpFuncs)[i]));
2323 llvmCPFuncs.push_back(
2324 moduleTranslation.lookupFunction(llvmFuncOp.getName()));
2325 }
2326
2327 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2328 moduleTranslation.getOpenMPBuilder()->createSingle(
2329 ompLoc, bodyCB, finiCB, singleOp.getNowait(), llvmCPVars,
2330 llvmCPFuncs);
2331
2332 if (failed(handleError(afterIP, *singleOp)))
2333 return failure();
2334
2335 builder.restoreIP(*afterIP);
2336 return success();
2337}
2338
2339static omp::DistributeOp
2341 // Early return if we found more than one distribute op or if we can't find
2342 // any distribute op in the teams region.
2343 omp::DistributeOp distOp;
2344 WalkResult walk = teamsOp.getRegion().walk([&](omp::DistributeOp op) {
2345 if (distOp)
2346 return WalkResult::interrupt();
2347 distOp = op;
2348 return WalkResult::skip();
2349 });
2350 if (walk.wasInterrupted() || !distOp)
2351 return {};
2352
2353 auto iface =
2354 llvm::cast<mlir::omp::BlockArgOpenMPOpInterface>(teamsOp.getOperation());
2355 // Check that all uses of the reduction block arg has the same distribute op
2356 // parent.
2358 for (auto ra : iface.getReductionBlockArgs())
2359 for (auto &use : ra.getUses()) {
2360 auto *useOp = use.getOwner();
2361 // Ignore debug uses.
2362 if (mlir::isa<LLVM::DbgDeclareOp, LLVM::DbgValueOp>(useOp)) {
2363 debugUses.push_back(useOp);
2364 continue;
2365 }
2366 if (!distOp->isProperAncestor(useOp))
2367 return {};
2368 }
2369
2370 // If we are going to use distribute reduction then remove any debug uses of
2371 // the reduction parameters in teamsOp. Otherwise they will be left without
2372 // any mapped value in moduleTranslation and will eventually error out.
2373 for (auto *use : debugUses)
2374 use->erase();
2375 return distOp;
2376}
2377
2378// Convert an OpenMP Teams construct to LLVM IR using OpenMPIRBuilder
2379static LogicalResult
2380convertOmpTeams(omp::TeamsOp op, llvm::IRBuilderBase &builder,
2381 LLVM::ModuleTranslation &moduleTranslation) {
2382 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2383 if (failed(checkImplementationStatus(*op)))
2384 return failure();
2385
2386 DenseMap<Value, llvm::Value *> reductionVariableMap;
2387 unsigned numReductionVars = op.getNumReductionVars();
2389 SmallVector<llvm::Value *> privateReductionVariables(numReductionVars);
2390 llvm::ArrayRef<bool> isByRef;
2391 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
2392 findAllocInsertPoints(builder, moduleTranslation);
2393
2394 // Only do teams reduction if there is no distribute op that captures the
2395 // reduction instead.
2396 bool doTeamsReduction = !getDistributeCapturingTeamsReduction(op);
2397 if (doTeamsReduction) {
2398 isByRef = getIsByRef(op.getReductionByref());
2399
2400 assert(isByRef.size() == op.getNumReductionVars());
2401
2402 MutableArrayRef<BlockArgument> reductionArgs =
2403 llvm::cast<omp::BlockArgOpenMPOpInterface>(*op).getReductionBlockArgs();
2404
2405 collectReductionDecls(op, reductionDecls);
2406
2408 op, reductionArgs, builder, moduleTranslation, allocaIP,
2409 reductionDecls, privateReductionVariables, reductionVariableMap,
2410 isByRef)))
2411 return failure();
2412 }
2413
2414 auto bodyCB = [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
2415 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) {
2417 moduleTranslation, allocaIP, deallocBlocks);
2418 builder.restoreIP(codegenIP);
2419 return convertOmpOpRegions(op.getRegion(), "omp.teams.region", builder,
2420 moduleTranslation)
2421 .takeError();
2422 };
2423
2424 llvm::Value *numTeamsLower = nullptr;
2425 if (Value numTeamsLowerVar = op.getNumTeamsLower())
2426 numTeamsLower = moduleTranslation.lookupValue(numTeamsLowerVar);
2427
2428 llvm::Value *numTeamsUpper = nullptr;
2429 if (!op.getNumTeamsUpperVars().empty())
2430 numTeamsUpper = moduleTranslation.lookupValue(op.getNumTeams(0));
2431
2432 llvm::Value *threadLimit = nullptr;
2433 if (!op.getThreadLimitVars().empty())
2434 threadLimit = moduleTranslation.lookupValue(op.getThreadLimit(0));
2435
2436 llvm::Value *ifExpr = nullptr;
2437 if (Value ifVar = op.getIfExpr())
2438 ifExpr = moduleTranslation.lookupValue(ifVar);
2439
2440 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2441 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2442 moduleTranslation.getOpenMPBuilder()->createTeams(
2443 ompLoc, bodyCB, numTeamsLower, numTeamsUpper, threadLimit, ifExpr);
2444
2445 if (failed(handleError(afterIP, *op)))
2446 return failure();
2447
2448 builder.restoreIP(*afterIP);
2449 if (doTeamsReduction) {
2450 // Process the reductions if required.
2452 op, builder, moduleTranslation, allocaIP, reductionDecls,
2453 privateReductionVariables, isByRef,
2454 /*isNoWait*/ false, /*isTeamsReduction*/ true);
2455 }
2456 return success();
2457}
2458
2459static llvm::omp::RTLDependenceKindTy
2460convertDependKind(mlir::omp::ClauseTaskDepend kind) {
2461 switch (kind) {
2462 case mlir::omp::ClauseTaskDepend::taskdependin:
2463 return llvm::omp::RTLDependenceKindTy::DepIn;
2464 // The OpenMP runtime requires that the codegen for 'depend' clause for
2465 // 'out' dependency kind must be the same as codegen for 'depend' clause
2466 // with 'inout' dependency.
2467 case mlir::omp::ClauseTaskDepend::taskdependout:
2468 case mlir::omp::ClauseTaskDepend::taskdependinout:
2469 return llvm::omp::RTLDependenceKindTy::DepInOut;
2470 case mlir::omp::ClauseTaskDepend::taskdependmutexinoutset:
2471 return llvm::omp::RTLDependenceKindTy::DepMutexInOutSet;
2472 case mlir::omp::ClauseTaskDepend::taskdependinoutset:
2473 return llvm::omp::RTLDependenceKindTy::DepInOutSet;
2474 }
2475 llvm_unreachable("unhandled depend kind");
2476}
2477
2479 std::optional<ArrayAttr> dependKinds, OperandRange dependVars,
2480 LLVM::ModuleTranslation &moduleTranslation,
2482 if (dependVars.empty())
2483 return;
2484 for (auto dep : llvm::zip(dependVars, dependKinds->getValue())) {
2485 auto kind =
2486 cast<mlir::omp::ClauseTaskDependAttr>(std::get<1>(dep)).getValue();
2487 llvm::omp::RTLDependenceKindTy type = convertDependKind(kind);
2488 llvm::Value *depVal = moduleTranslation.lookupValue(std::get<0>(dep));
2489 llvm::OpenMPIRBuilder::DependData dd(type, depVal->getType(), depVal);
2490 dds.emplace_back(dd);
2491 }
2492}
2493
2494/// Shared implementation of a callback which adds a termiator for the new block
2495/// created for the branch taken when an openmp construct is cancelled. The
2496/// terminator is saved in \p cancelTerminators. This callback is invoked only
2497/// if there is cancellation inside of the taskgroup body.
2498/// The terminator will need to be fixed to branch to the correct block to
2499/// cleanup the construct.
2501 SmallVectorImpl<llvm::UncondBrInst *> &cancelTerminators,
2502 llvm::IRBuilderBase &llvmBuilder, llvm::OpenMPIRBuilder &ompBuilder,
2503 mlir::Operation *op, llvm::omp::Directive cancelDirective) {
2504 auto finiCB = [&](llvm::OpenMPIRBuilder::InsertPointTy ip) -> llvm::Error {
2505 llvm::IRBuilderBase::InsertPointGuard guard(llvmBuilder);
2506
2507 // ip is currently in the block branched to if cancellation occurred.
2508 // We need to create a branch to terminate that block.
2509 llvmBuilder.restoreIP(ip);
2510
2511 // We must still clean up the construct after cancelling it, so we need to
2512 // branch to the block that finalizes the taskgroup.
2513 // That block has not been created yet so use this block as a dummy for now
2514 // and fix this after creating the operation.
2515 cancelTerminators.push_back(llvmBuilder.CreateBr(ip.getBlock()));
2516 return llvm::Error::success();
2517 };
2518 // We have to add the cleanup to the OpenMPIRBuilder before the body gets
2519 // created in case the body contains omp.cancel (which will then expect to be
2520 // able to find this cleanup callback).
2521 ompBuilder.pushFinalizationCB(
2522 {finiCB, cancelDirective, constructIsCancellable(op)});
2523}
2524
2525/// If we cancelled the construct, we should branch to the finalization block of
2526/// that construct. OMPIRBuilder structures the CFG such that the cleanup block
2527/// is immediately before the continuation block. Now this finalization has
2528/// been created we can fix the branch.
2529static void
2531 llvm::OpenMPIRBuilder &ompBuilder,
2532 const llvm::OpenMPIRBuilder::InsertPointTy &afterIP) {
2533 ompBuilder.popFinalizationCB();
2534 llvm::BasicBlock *constructFini = afterIP.getBlock()->getSinglePredecessor();
2535 for (llvm::UncondBrInst *cancelBranch : cancelTerminators)
2536 cancelBranch->setSuccessor(constructFini);
2537}
2538
2539namespace {
2540/// TaskContextStructManager takes care of creating and freeing a structure
2541/// containing information needed by the task body to execute.
2542class TaskContextStructManager {
2543public:
2544 TaskContextStructManager(llvm::IRBuilderBase &builder,
2545 LLVM::ModuleTranslation &moduleTranslation,
2546 MutableArrayRef<omp::PrivateClauseOp> privateDecls)
2547 : builder{builder}, moduleTranslation{moduleTranslation},
2548 privateDecls{privateDecls} {}
2549
2550 /// Creates a heap allocated struct containing space for each private
2551 /// variable. Invariant: privateVarTypes, privateDecls, and the elements of
2552 /// the structure should all have the same order (although privateDecls which
2553 /// do not read from the mold argument are skipped).
2554 void generateTaskContextStruct();
2555
2556 /// Create GEPs to access each member of the structure representing a private
2557 /// variable, adding them to llvmPrivateVars. Null values are added where
2558 /// private decls were skipped so that the ordering continues to match the
2559 /// private decls.
2560 void createGEPsToPrivateVars();
2561
2562 /// Given the address of the structure, return a GEP for each private variable
2563 /// in the structure. Null values are added where private decls were skipped
2564 /// so that the ordering continues to match the private decls.
2565 /// Must be called after generateTaskContextStruct().
2566 SmallVector<llvm::Value *>
2567 createGEPsToPrivateVars(llvm::Value *altStructPtr) const;
2568
2569 /// De-allocate the task context structure.
2570 void freeStructPtr();
2571
2572 MutableArrayRef<llvm::Value *> getLLVMPrivateVarGEPs() {
2573 return llvmPrivateVarGEPs;
2574 }
2575
2576 llvm::Value *getStructPtr() { return structPtr; }
2577
2578private:
2579 llvm::IRBuilderBase &builder;
2580 LLVM::ModuleTranslation &moduleTranslation;
2581 MutableArrayRef<omp::PrivateClauseOp> privateDecls;
2582
2583 /// The type of each member of the structure, in order.
2584 SmallVector<llvm::Type *> privateVarTypes;
2585
2586 /// LLVM values for each private variable, or null if that private variable is
2587 /// not included in the task context structure
2588 SmallVector<llvm::Value *> llvmPrivateVarGEPs;
2589
2590 /// A pointer to the structure containing context for this task.
2591 llvm::Value *structPtr = nullptr;
2592 /// The type of the structure
2593 llvm::Type *structTy = nullptr;
2594};
2595
2596/// IteratorInfo extracts and prepares loop bounds information from an
2597/// mlir::omp::IteratorOp for lowering to LLVM IR.
2598///
2599/// It computes the per-dimension trip counts and the total linearized trip
2600/// count, casted to i64. These are used to build a canonical loop and to
2601/// reconstruct the physical induction variables inside the loop body.
2602class IteratorInfo {
2603private:
2604 llvm::SmallVector<llvm::Value *> lowerBounds;
2605 llvm::SmallVector<llvm::Value *> upperBounds;
2606 llvm::SmallVector<llvm::Value *> steps;
2607 llvm::SmallVector<llvm::Value *> trips;
2608 unsigned dims;
2609 llvm::Value *totalTrips;
2610
2611 llvm::Value *lookUpAsI64(mlir::Value val, const LLVM::ModuleTranslation &mt,
2612 llvm::IRBuilderBase &builder) {
2613 llvm::Value *v = mt.lookupValue(val);
2614 if (!v)
2615 return nullptr;
2616 if (v->getType()->isIntegerTy(64))
2617 return v;
2618 if (v->getType()->isIntegerTy())
2619 return builder.CreateSExtOrTrunc(v, builder.getInt64Ty());
2620 return nullptr;
2621 }
2622
2623public:
2624 IteratorInfo(mlir::omp::IteratorOp itersOp,
2625 mlir::LLVM::ModuleTranslation &moduleTranslation,
2626 llvm::IRBuilderBase &builder) {
2627 dims = itersOp.getLoopLowerBounds().size();
2628 lowerBounds.resize(dims);
2629 upperBounds.resize(dims);
2630 steps.resize(dims);
2631 trips.resize(dims);
2632
2633 for (unsigned d = 0; d < dims; ++d) {
2634 llvm::Value *lb = lookUpAsI64(itersOp.getLoopLowerBounds()[d],
2635 moduleTranslation, builder);
2636 llvm::Value *ub = lookUpAsI64(itersOp.getLoopUpperBounds()[d],
2637 moduleTranslation, builder);
2638 llvm::Value *st =
2639 lookUpAsI64(itersOp.getLoopSteps()[d], moduleTranslation, builder);
2640 assert(lb && ub && st &&
2641 "Expect lowerBounds, upperBounds, and steps in IteratorOp");
2642 assert((!llvm::isa<llvm::ConstantInt>(st) ||
2643 !llvm::cast<llvm::ConstantInt>(st)->isZero()) &&
2644 "Expect non-zero step in IteratorOp");
2645
2646 lowerBounds[d] = lb;
2647 upperBounds[d] = ub;
2648 steps[d] = st;
2649
2650 // trips = ((ub - lb) / step) + 1 (inclusive ub, assume positive step)
2651 llvm::Value *diff = builder.CreateSub(ub, lb);
2652 llvm::Value *div = builder.CreateSDiv(diff, st);
2653 trips[d] = builder.CreateAdd(
2654 div, llvm::ConstantInt::get(builder.getInt64Ty(), 1));
2655 }
2656
2657 totalTrips = llvm::ConstantInt::get(builder.getInt64Ty(), 1);
2658 for (unsigned d = 0; d < dims; ++d)
2659 totalTrips = builder.CreateMul(totalTrips, trips[d]);
2660 }
2661
2662 unsigned getDims() const { return dims; }
2663 llvm::ArrayRef<llvm::Value *> getLowerBounds() const { return lowerBounds; }
2664 llvm::ArrayRef<llvm::Value *> getUpperBounds() const { return upperBounds; }
2665 llvm::ArrayRef<llvm::Value *> getSteps() const { return steps; }
2666 llvm::ArrayRef<llvm::Value *> getTrips() const { return trips; }
2667 llvm::Value *getTotalTrips() const { return totalTrips; }
2668};
2669
2670} // namespace
2671
2672void TaskContextStructManager::generateTaskContextStruct() {
2673 if (privateDecls.empty())
2674 return;
2675 privateVarTypes.reserve(privateDecls.size());
2676
2677 for (omp::PrivateClauseOp &privOp : privateDecls) {
2678 // Skip private variables which can safely be allocated and initialised
2679 // inside of the task
2680 if (!privOp.readsFromMold())
2681 continue;
2682 Type mlirType = privOp.getType();
2683 privateVarTypes.push_back(moduleTranslation.convertType(mlirType));
2684 }
2685
2686 if (privateVarTypes.empty())
2687 return;
2688
2689 structTy = llvm::StructType::get(moduleTranslation.getLLVMContext(),
2690 privateVarTypes);
2691
2692 llvm::DataLayout dataLayout =
2693 builder.GetInsertBlock()->getModule()->getDataLayout();
2694 llvm::Type *intPtrTy = builder.getIntPtrTy(dataLayout);
2695 llvm::Constant *allocSize = llvm::ConstantExpr::getSizeOf(structTy);
2696
2697 // Heap allocate the structure
2698 structPtr = builder.CreateMalloc(intPtrTy, structTy, allocSize,
2699 /*ArraySize=*/nullptr, /*MallocF=*/nullptr,
2700 "omp.task.context_ptr");
2701}
2702
2703SmallVector<llvm::Value *> TaskContextStructManager::createGEPsToPrivateVars(
2704 llvm::Value *altStructPtr) const {
2705 SmallVector<llvm::Value *> ret;
2706
2707 // Create GEPs for each struct member
2708 ret.reserve(privateDecls.size());
2709 llvm::Value *zero = builder.getInt32(0);
2710 unsigned i = 0;
2711 for (auto privDecl : privateDecls) {
2712 if (!privDecl.readsFromMold()) {
2713 // Handle this inside of the task so we don't pass unnessecary vars in
2714 ret.push_back(nullptr);
2715 continue;
2716 }
2717 llvm::Value *iVal = builder.getInt32(i);
2718 llvm::Value *gep = builder.CreateGEP(structTy, altStructPtr, {zero, iVal});
2719 ret.push_back(gep);
2720 i += 1;
2721 }
2722 return ret;
2723}
2724
2725void TaskContextStructManager::createGEPsToPrivateVars() {
2726 if (!structPtr)
2727 assert(privateVarTypes.empty());
2728 // Still need to run createGEPsToPrivateVars to populate llvmPrivateVarGEPs
2729 // with null values for skipped private decls
2730
2731 llvmPrivateVarGEPs = createGEPsToPrivateVars(structPtr);
2732}
2733
2734void TaskContextStructManager::freeStructPtr() {
2735 if (!structPtr)
2736 return;
2737
2738 llvm::IRBuilderBase::InsertPointGuard guard{builder};
2739 // Ensure we don't put the call to free() after the terminator
2740 builder.SetInsertPoint(builder.GetInsertBlock()->getTerminator());
2741 builder.CreateFree(structPtr);
2742}
2743
2744static void storeAffinityEntry(llvm::IRBuilderBase &builder,
2745 llvm::OpenMPIRBuilder &ompBuilder,
2746 llvm::Value *affinityList, llvm::Value *index,
2747 llvm::Value *addr, llvm::Value *len) {
2748 llvm::StructType *kmpTaskAffinityInfoTy =
2749 ompBuilder.getKmpTaskAffinityInfoTy();
2750 llvm::Value *entry = builder.CreateInBoundsGEP(
2751 kmpTaskAffinityInfoTy, affinityList, index, "omp.affinity.entry");
2752
2753 addr = builder.CreatePtrToInt(addr, kmpTaskAffinityInfoTy->getElementType(0));
2754 len = builder.CreateIntCast(len, kmpTaskAffinityInfoTy->getElementType(1),
2755 /*isSigned=*/false);
2756 llvm::Value *flags = builder.getInt32(0);
2757
2758 builder.CreateStore(addr,
2759 builder.CreateStructGEP(kmpTaskAffinityInfoTy, entry, 0));
2760 builder.CreateStore(len,
2761 builder.CreateStructGEP(kmpTaskAffinityInfoTy, entry, 1));
2762 builder.CreateStore(flags,
2763 builder.CreateStructGEP(kmpTaskAffinityInfoTy, entry, 2));
2764}
2765
2767 llvm::IRBuilderBase &builder,
2768 LLVM::ModuleTranslation &moduleTranslation,
2769 llvm::Value *affinityList) {
2770 for (auto [i, affinityVar] : llvm::enumerate(affinityVars)) {
2771 auto entryOp = affinityVar.getDefiningOp<mlir::omp::AffinityEntryOp>();
2772 assert(entryOp && "affinity item must be omp.affinity_entry");
2773
2774 llvm::Value *addr = moduleTranslation.lookupValue(entryOp.getAddr());
2775 llvm::Value *len = moduleTranslation.lookupValue(entryOp.getLen());
2776 assert(addr && len && "expect affinity addr and len to be non-null");
2777 storeAffinityEntry(builder, *moduleTranslation.getOpenMPBuilder(),
2778 affinityList, builder.getInt64(i), addr, len);
2779 }
2780}
2781
2782static mlir::LogicalResult
2783convertIteratorRegion(llvm::Value *linearIV, IteratorInfo &iterInfo,
2784 mlir::Block &iteratorRegionBlock,
2785 llvm::IRBuilderBase &builder,
2786 LLVM::ModuleTranslation &moduleTranslation) {
2787 llvm::Value *tmp = linearIV;
2788 for (int d = (int)iterInfo.getDims() - 1; d >= 0; --d) {
2789 llvm::Value *trip = iterInfo.getTrips()[d];
2790 // idx_d = tmp % trip_d
2791 llvm::Value *idx = builder.CreateURem(tmp, trip);
2792 // tmp = tmp / trip_d
2793 tmp = builder.CreateUDiv(tmp, trip);
2794
2795 // physIV_d = lb_d + idx_d * step_d
2796 llvm::Value *physIV = builder.CreateAdd(
2797 iterInfo.getLowerBounds()[d],
2798 builder.CreateMul(idx, iterInfo.getSteps()[d]), "omp.it.phys_iv");
2799
2800 moduleTranslation.mapValue(iteratorRegionBlock.getArgument(d), physIV);
2801 }
2802
2803 // Translate the iterator region into the loop body.
2804 moduleTranslation.mapBlock(&iteratorRegionBlock, builder.GetInsertBlock());
2805 if (mlir::failed(moduleTranslation.convertBlock(iteratorRegionBlock,
2806 /*ignoreArguments=*/true,
2807 builder))) {
2808 return mlir::failure();
2809 }
2810 return mlir::success();
2811}
2812
2814 llvm::function_ref<void(llvm::Value *linearIV, mlir::omp::YieldOp yield)>;
2815
2816static mlir::LogicalResult
2817fillIteratorLoop(mlir::omp::IteratorOp itersOp, llvm::IRBuilderBase &builder,
2818 mlir::LLVM::ModuleTranslation &moduleTranslation,
2819 IteratorInfo &iterInfo, llvm::StringRef loopName,
2820 IteratorStoreEntryTy genStoreEntry) {
2821 mlir::Region &itersRegion = itersOp.getRegion();
2822 mlir::Block &iteratorRegionBlock = itersRegion.front();
2823
2824 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
2825
2826 auto bodyGen = [&](llvm::OpenMPIRBuilder::InsertPointTy bodyIP,
2827 llvm::Value *linearIV) -> llvm::Error {
2828 llvm::IRBuilderBase::InsertPointGuard guard(builder);
2829 builder.restoreIP(bodyIP);
2830
2831 if (failed(convertIteratorRegion(linearIV, iterInfo, iteratorRegionBlock,
2832 builder, moduleTranslation))) {
2833 return llvm::make_error<llvm::StringError>(
2834 "failed to convert iterator region", llvm::inconvertibleErrorCode());
2835 }
2836
2837 auto yield =
2838 mlir::dyn_cast<mlir::omp::YieldOp>(iteratorRegionBlock.getTerminator());
2839 assert(yield && yield.getResults().size() == 1 &&
2840 "expect omp.yield in iterator region to have one result");
2841
2842 genStoreEntry(linearIV, yield);
2843
2844 // Iterator-region block/value mappings are temporary for this conversion,
2845 // clear them to avoid stale entries in ModuleTranslation.
2846 moduleTranslation.forgetMapping(itersRegion);
2847
2848 return llvm::Error::success();
2849 };
2850
2851 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2852 moduleTranslation.getOpenMPBuilder()->createIteratorLoop(
2853 loc, iterInfo.getTotalTrips(), bodyGen, loopName);
2854 if (failed(handleError(afterIP, *itersOp)))
2855 return failure();
2856
2857 builder.restoreIP(*afterIP);
2858
2859 return mlir::success();
2860}
2861
2862static mlir::LogicalResult
2863buildAffinityData(mlir::omp::TaskOp &taskOp, llvm::IRBuilderBase &builder,
2864 mlir::LLVM::ModuleTranslation &moduleTranslation,
2865 llvm::OpenMPIRBuilder::AffinityData &ad) {
2866
2867 if (taskOp.getAffinityVars().empty() && taskOp.getIterated().empty()) {
2868 ad.Count = nullptr;
2869 ad.Info = nullptr;
2870 return mlir::success();
2871 }
2872
2874 llvm::StructType *kmpTaskAffinityInfoTy =
2875 moduleTranslation.getOpenMPBuilder()->getKmpTaskAffinityInfoTy();
2876
2877 auto allocateAffinityList = [&](llvm::Value *count) -> llvm::Value * {
2878 llvm::IRBuilderBase::InsertPointGuard guard(builder);
2879 if (llvm::isa<llvm::Constant>(count) || llvm::isa<llvm::Argument>(count))
2880 builder.restoreIP(findAllocInsertPoints(builder, moduleTranslation));
2881 return builder.CreateAlloca(kmpTaskAffinityInfoTy, count,
2882 "omp.affinity_list");
2883 };
2884
2885 auto createAffinity =
2886 [&](llvm::Value *count,
2887 llvm::Value *info) -> llvm::OpenMPIRBuilder::AffinityData {
2888 llvm::OpenMPIRBuilder::AffinityData ad{};
2889 ad.Count = builder.CreateTrunc(count, builder.getInt32Ty());
2890 ad.Info =
2891 builder.CreatePointerBitCastOrAddrSpaceCast(info, builder.getPtrTy(0));
2892 return ad;
2893 };
2894
2895 if (!taskOp.getAffinityVars().empty()) {
2896 llvm::Value *count = llvm::ConstantInt::get(
2897 builder.getInt64Ty(), taskOp.getAffinityVars().size());
2898 llvm::Value *list = allocateAffinityList(count);
2899 fillAffinityLocators(taskOp.getAffinityVars(), builder, moduleTranslation,
2900 list);
2901 ads.emplace_back(createAffinity(count, list));
2902 }
2903
2904 if (!taskOp.getIterated().empty()) {
2905 for (auto [i, iter] : llvm::enumerate(taskOp.getIterated())) {
2906 auto itersOp = iter.getDefiningOp<omp::IteratorOp>();
2907 assert(itersOp && "iterated value must be defined by omp.iterator");
2908 IteratorInfo iterInfo(itersOp, moduleTranslation, builder);
2909 llvm::Value *affList = allocateAffinityList(iterInfo.getTotalTrips());
2910 if (failed(fillIteratorLoop(
2911 itersOp, builder, moduleTranslation, iterInfo, "iterator",
2912 [&](llvm::Value *linearIV, mlir::omp::YieldOp yield) {
2913 auto entryOp = yield.getResults()[0]
2914 .getDefiningOp<mlir::omp::AffinityEntryOp>();
2915 assert(entryOp && "expect yield produce an affinity entry");
2916 llvm::Value *addr =
2917 moduleTranslation.lookupValue(entryOp.getAddr());
2918 llvm::Value *len =
2919 moduleTranslation.lookupValue(entryOp.getLen());
2920 storeAffinityEntry(builder,
2921 *moduleTranslation.getOpenMPBuilder(),
2922 affList, linearIV, addr, len);
2923 })))
2924 return llvm::failure();
2925 ads.emplace_back(createAffinity(iterInfo.getTotalTrips(), affList));
2926 }
2927 }
2928
2929 llvm::Value *totalAffinityCount = builder.getInt32(0);
2930 for (const auto &affinity : ads)
2931 totalAffinityCount = builder.CreateAdd(
2932 totalAffinityCount,
2933 builder.CreateIntCast(affinity.Count, builder.getInt32Ty(),
2934 /*isSigned=*/false));
2935
2936 llvm::Value *affinityInfo = ads.front().Info;
2937 if (ads.size() > 1) {
2938 llvm::StructType *kmpTaskAffinityInfoTy =
2939 moduleTranslation.getOpenMPBuilder()->getKmpTaskAffinityInfoTy();
2940 llvm::Value *affinityInfoElemSize = builder.getInt64(
2941 moduleTranslation.getLLVMModule()->getDataLayout().getTypeAllocSize(
2942 kmpTaskAffinityInfoTy));
2943
2944 llvm::Value *packedAffinityInfo = allocateAffinityList(totalAffinityCount);
2945 llvm::Value *packedAffinityInfoOffset = builder.getInt32(0);
2946 for (const auto &affinity : ads) {
2947 llvm::Value *affinityCount = builder.CreateIntCast(
2948 affinity.Count, builder.getInt32Ty(), /*isSigned=*/false);
2949 llvm::Value *affinityCountInt64 = builder.CreateIntCast(
2950 affinityCount, builder.getInt64Ty(), /*isSigned=*/false);
2951 llvm::Value *affinityInfoSize =
2952 builder.CreateMul(affinityCountInt64, affinityInfoElemSize);
2953
2954 llvm::Value *packedAffinityInfoIndex = builder.CreateIntCast(
2955 packedAffinityInfoOffset, kmpTaskAffinityInfoTy->getElementType(0),
2956 /*isSigned=*/false);
2957 packedAffinityInfoIndex = builder.CreateInBoundsGEP(
2958 kmpTaskAffinityInfoTy, packedAffinityInfo, packedAffinityInfoIndex);
2959
2960 builder.CreateMemCpy(
2961 packedAffinityInfoIndex, llvm::Align(1),
2962 builder.CreatePointerBitCastOrAddrSpaceCast(
2963 affinity.Info, builder.getPtrTy(packedAffinityInfoIndex->getType()
2964 ->getPointerAddressSpace())),
2965 llvm::Align(1), affinityInfoSize);
2966
2967 packedAffinityInfoOffset =
2968 builder.CreateAdd(packedAffinityInfoOffset, affinityCount);
2969 }
2970
2971 affinityInfo = packedAffinityInfo;
2972 }
2973
2974 ad.Count = totalAffinityCount;
2975 ad.Info = affinityInfo;
2976
2977 return mlir::success();
2978}
2979
2980// Allocates a single kmp_dep_info array sized to hold both locator
2981// (non-iterated) and iterated entries, fills the locator entries first, then
2982// runs an iterator loop for each iterator modifier object.
2983static mlir::LogicalResult
2984buildDependData(OperandRange dependVars, std::optional<ArrayAttr> dependKinds,
2985 OperandRange dependIterated,
2986 std::optional<ArrayAttr> dependIteratedKinds,
2987 llvm::IRBuilderBase &builder,
2988 mlir::LLVM::ModuleTranslation &moduleTranslation,
2989 llvm::OpenMPIRBuilder::DependenciesInfo &taskDeps) {
2990 if (dependIterated.empty()) {
2991 buildDependDataLocator(dependKinds, dependVars, moduleTranslation,
2992 taskDeps.Deps);
2993 return mlir::success();
2994 }
2995
2996 llvm::OpenMPIRBuilder &ompBuilder = *moduleTranslation.getOpenMPBuilder();
2997 llvm::Type *dependInfoTy = ompBuilder.DependInfo;
2998 unsigned numLocator = dependVars.size();
2999
3000 // Compute total count: locator deps + sum of iterator trip counts.
3001 llvm::Value *totalCount =
3002 llvm::ConstantInt::get(builder.getInt64Ty(), numLocator);
3003
3005 for (auto iter : dependIterated) {
3006 auto itersOp = iter.getDefiningOp<mlir::omp::IteratorOp>();
3007 assert(itersOp && "depend_iterated value must be defined by omp.iterator");
3008 iterInfos.emplace_back(itersOp, moduleTranslation, builder);
3009 totalCount =
3010 builder.CreateAdd(totalCount, iterInfos.back().getTotalTrips());
3011 }
3012
3013 // Heap-allocate the kmp_depend_info array so we don't risk
3014 // dynamic-sized alloca outside the entry block (e.g. inside loops).
3015 llvm::Constant *allocSize = llvm::ConstantExpr::getSizeOf(dependInfoTy);
3016 llvm::Value *depArray =
3017 builder.CreateMalloc(ompBuilder.SizeTy, dependInfoTy, allocSize,
3018 totalCount, /*MallocF=*/nullptr, ".dep.arr.addr");
3019
3020 // Fill non-iterated entries at indices [0, numLocator).
3021 if (numLocator > 0) {
3023 buildDependDataLocator(dependKinds, dependVars, moduleTranslation, dds);
3024 for (auto [i, dd] : llvm::enumerate(dds)) {
3025 llvm::Value *idx = llvm::ConstantInt::get(builder.getInt64Ty(), i);
3026 llvm::Value *entry =
3027 builder.CreateInBoundsGEP(dependInfoTy, depArray, idx);
3028 ompBuilder.emitTaskDependency(builder, entry, dd);
3029 }
3030 }
3031
3032 // Fill iterated entries starting at index numLocator.
3033 llvm::Value *offset =
3034 llvm::ConstantInt::get(builder.getInt64Ty(), numLocator);
3035 for (auto [i, iterInfo] : llvm::enumerate(iterInfos)) {
3036 auto kindAttr = cast<mlir::omp::ClauseTaskDependAttr>(
3037 dependIteratedKinds->getValue()[i]);
3038 llvm::omp::RTLDependenceKindTy rtlKind =
3039 convertDependKind(kindAttr.getValue());
3040
3041 auto itersOp = dependIterated[i].getDefiningOp<mlir::omp::IteratorOp>();
3042 if (failed(fillIteratorLoop(
3043 itersOp, builder, moduleTranslation, iterInfo, "dep_iterator",
3044 [&](llvm::Value *linearIV, mlir::omp::YieldOp yield) {
3045 llvm::Value *addr =
3046 moduleTranslation.lookupValue(yield.getResults()[0]);
3047 llvm::Value *idx = builder.CreateAdd(offset, linearIV);
3048 llvm::Value *entry =
3049 builder.CreateInBoundsGEP(dependInfoTy, depArray, idx);
3050 ompBuilder.emitTaskDependency(
3051 builder, entry,
3052 llvm::OpenMPIRBuilder::DependData{rtlKind, addr->getType(),
3053 addr});
3054 })))
3055 return mlir::failure();
3056
3057 // Advance offset by the trip count of this iterator.
3058 offset = builder.CreateAdd(offset, iterInfo.getTotalTrips());
3059 }
3060
3061 taskDeps.DepArray = depArray;
3062 taskDeps.NumDeps = builder.CreateTrunc(totalCount, builder.getInt32Ty());
3063 return mlir::success();
3064}
3065
3066/// Converts an OpenMP task construct into LLVM IR using OpenMPIRBuilder.
3067static LogicalResult
3068convertOmpTaskOp(omp::TaskOp taskOp, llvm::IRBuilderBase &builder,
3069 LLVM::ModuleTranslation &moduleTranslation) {
3070 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
3071 if (failed(checkImplementationStatus(*taskOp)))
3072 return failure();
3073
3074 PrivateVarsInfo privateVarsInfo(taskOp);
3075 TaskContextStructManager taskStructMgr{builder, moduleTranslation,
3076 privateVarsInfo.privatizers};
3077
3078 // Allocate and copy private variables before creating the task. This avoids
3079 // accessing invalid memory if (after this scope ends) the private variables
3080 // are initialized from host variables or if the variables are copied into
3081 // from host variables (firstprivate). The insertion point is just before
3082 // where the code for creating and scheduling the task will go. That puts this
3083 // code outside of the outlined task region, which is what we want because
3084 // this way the initialization and copy regions are executed immediately while
3085 // the host variable data are still live.
3087 InsertPointTy allocaIP =
3088 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
3089
3090 // Not using splitBB() because that requires the current block to have a
3091 // terminator.
3092 assert(builder.GetInsertPoint() == builder.GetInsertBlock()->end());
3093 llvm::BasicBlock *taskStartBlock = llvm::BasicBlock::Create(
3094 builder.getContext(), "omp.task.start",
3095 /*Parent=*/builder.GetInsertBlock()->getParent());
3096 llvm::Instruction *branchToTaskStartBlock = builder.CreateBr(taskStartBlock);
3097 builder.SetInsertPoint(branchToTaskStartBlock);
3098
3099 // Now do this again to make the initialization and copy blocks
3100 llvm::BasicBlock *copyBlock =
3101 splitBB(builder, /*CreateBranch=*/true, "omp.private.copy");
3102 llvm::BasicBlock *initBlock =
3103 splitBB(builder, /*CreateBranch=*/true, "omp.private.init");
3104
3105 // Now the control flow graph should look like
3106 // starter_block:
3107 // <---- where we started when convertOmpTaskOp was called
3108 // br %omp.private.init
3109 // omp.private.init:
3110 // br %omp.private.copy
3111 // omp.private.copy:
3112 // br %omp.task.start
3113 // omp.task.start:
3114 // <---- where we want the insertion point to be when we call createTask()
3115
3116 // Save the alloca insertion point on ModuleTranslation stack for use in
3117 // nested regions.
3119 moduleTranslation, allocaIP, deallocBlocks);
3120
3121 // Allocate and initialize private variables
3122 builder.SetInsertPoint(initBlock->getTerminator());
3123
3124 // Create task variable structure
3125 taskStructMgr.generateTaskContextStruct();
3126 // GEPs so that we can initialize the variables. Don't use these GEPs inside
3127 // of the body otherwise it will be the GEP not the struct which is fowarded
3128 // to the outlined function. GEPs forwarded in this way are passed in a
3129 // stack-allocated (by OpenMPIRBuilder) structure which is not safe for tasks
3130 // which may not be executed until after the current stack frame goes out of
3131 // scope.
3132 taskStructMgr.createGEPsToPrivateVars();
3133
3134 for (auto [privDecl, mlirPrivVar, blockArg, llvmPrivateVarAlloc] :
3135 llvm::zip_equal(privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
3136 privateVarsInfo.blockArgs,
3137 taskStructMgr.getLLVMPrivateVarGEPs())) {
3138 // To be handled inside the task.
3139 if (!privDecl.readsFromMold())
3140 continue;
3141 assert(llvmPrivateVarAlloc &&
3142 "reads from mold so shouldn't have been skipped");
3143
3144 llvm::Expected<llvm::Value *> privateVarOrErr =
3145 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
3146 blockArg, llvmPrivateVarAlloc, initBlock);
3147 if (!privateVarOrErr)
3148 return handleError(privateVarOrErr, *taskOp.getOperation());
3149
3151
3152 // TODO: this is a bit of a hack for Fortran character boxes.
3153 // Character boxes are passed by value into the init region and then the
3154 // initialized character box is yielded by value. Here we need to store the
3155 // yielded value into the private allocation, and load the private
3156 // allocation to match the type expected by region block arguments.
3157 [[maybe_unused]] llvm::Value *llvmPrivateVar = llvmPrivateVarAlloc;
3158 if ((privateVarOrErr.get() != llvmPrivateVarAlloc) &&
3159 !mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3160 builder.CreateStore(privateVarOrErr.get(), llvmPrivateVarAlloc);
3161 // Load it so we have the value pointed to by the GEP
3162 llvmPrivateVar = builder.CreateLoad(privateVarOrErr.get()->getType(),
3163 llvmPrivateVarAlloc);
3164 }
3165 assert(llvmPrivateVar->getType() ==
3166 moduleTranslation.convertType(blockArg.getType()));
3167
3168 // Mapping blockArg -> llvmPrivateVarAlloc is done inside the body callback
3169 // so that OpenMPIRBuilder doesn't try to pass each GEP address through a
3170 // stack allocated structure.
3171 }
3172
3173 // firstprivate copy region
3174 setInsertPointForPossiblyEmptyBlock(builder, copyBlock);
3175 if (failed(copyFirstPrivateVars(
3176 taskOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
3177 taskStructMgr.getLLVMPrivateVarGEPs(), privateVarsInfo.privatizers,
3178 taskOp.getPrivateNeedsBarrier())))
3179 return llvm::failure();
3180
3181 llvm::OpenMPIRBuilder::AffinityData ad;
3182 if (failed(buildAffinityData(taskOp, builder, moduleTranslation, ad)))
3183 return llvm::failure();
3184
3185 // Resolve and validate in_reduction declarations. Byref in_reduction has
3186 // already been rejected by checkImplementationStatus; the helper rejects the
3187 // remaining richer declare_reduction shapes (two-argument initializer,
3188 // cleanup region, missing combiner). This is pure MLIR symbol-table work and
3189 // emits no IR. The matching task_reduction descriptor is registered by an
3190 // enclosing taskgroup; here we only look the per-task storage up at runtime.
3193 taskOp.getOperation(), taskOp.getInReductionSyms(), "omp.task",
3194 "in_reduction", inRedDecls)))
3195 return failure();
3196 SmallVector<llvm::Value *> inRedOrigPtrs;
3197 inRedOrigPtrs.reserve(inRedDecls.size());
3198 for (Value v : taskOp.getInReductionVars())
3199 inRedOrigPtrs.push_back(moduleTranslation.lookupValue(v));
3200
3201 // Set up for call to createTask()
3202 builder.SetInsertPoint(taskStartBlock);
3203
3204 auto bodyCB =
3205 [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
3206 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
3207 // Save the alloca insertion point on ModuleTranslation stack for use in
3208 // nested regions.
3210 moduleTranslation, allocaIP, deallocBlocks);
3211
3212 // translate the body of the task:
3213 builder.restoreIP(codegenIP);
3214
3215 llvm::BasicBlock *privInitBlock = nullptr;
3216 privateVarsInfo.llvmVars.resize(privateVarsInfo.blockArgs.size());
3217 for (auto [i, zip] : llvm::enumerate(llvm::zip_equal(
3218 privateVarsInfo.blockArgs, privateVarsInfo.privatizers,
3219 privateVarsInfo.mlirVars))) {
3220 auto [blockArg, privDecl, mlirPrivVar] = zip;
3221 // This is handled before the task executes
3222 if (privDecl.readsFromMold())
3223 continue;
3224
3225 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3226 llvm::Type *llvmAllocType =
3227 moduleTranslation.convertType(privDecl.getType());
3228 builder.SetInsertPoint(allocaIP.getBlock()->getTerminator());
3229 llvm::Value *llvmPrivateVar = builder.CreateAlloca(
3230 llvmAllocType, /*ArraySize=*/nullptr, "omp.private.alloc");
3231
3232 llvm::Expected<llvm::Value *> privateVarOrError =
3233 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
3234 blockArg, llvmPrivateVar, privInitBlock);
3235 if (!privateVarOrError)
3236 return privateVarOrError.takeError();
3237 moduleTranslation.mapValue(blockArg, privateVarOrError.get());
3238 privateVarsInfo.llvmVars[i] = privateVarOrError.get();
3239 }
3240
3241 taskStructMgr.createGEPsToPrivateVars();
3242 for (auto [i, llvmPrivVar] :
3243 llvm::enumerate(taskStructMgr.getLLVMPrivateVarGEPs())) {
3244 if (!llvmPrivVar) {
3245 assert(privateVarsInfo.llvmVars[i] &&
3246 "This is added in the loop above");
3247 continue;
3248 }
3249 privateVarsInfo.llvmVars[i] = llvmPrivVar;
3250 }
3251
3252 // Find and map the addresses of each variable within the task context
3253 // structure
3254 for (auto [blockArg, llvmPrivateVar, privateDecl] :
3255 llvm::zip_equal(privateVarsInfo.blockArgs, privateVarsInfo.llvmVars,
3256 privateVarsInfo.privatizers)) {
3257 // This was handled above.
3258 if (!privateDecl.readsFromMold())
3259 continue;
3260 // Fix broken pass-by-value case for Fortran character boxes
3261 if (!mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3262 llvmPrivateVar = builder.CreateLoad(
3263 moduleTranslation.convertType(blockArg.getType()), llvmPrivateVar);
3264 }
3265 assert(llvmPrivateVar->getType() ==
3266 moduleTranslation.convertType(blockArg.getType()));
3267 moduleTranslation.mapValue(blockArg, llvmPrivateVar);
3268 }
3269
3270 // Map in_reduction block arguments to the per-task private storage returned
3271 // by __kmpc_task_reduction_get_th_data. This call must be emitted inside
3272 // the to-be-outlined task body so that it returns the *executing* thread's
3273 // gtid (not the encountering thread's). The descriptor is NULL: the runtime
3274 // walks up enclosing taskgroups to find the matching task_reduction
3275 // registration for `origPtr`. The original pointers are auto-captured into
3276 // the task shareds aggregate by CodeExtractor during
3277 // OpenMPIRBuilder::finalize.
3278 if (!inRedDecls.empty()) {
3279 auto iface = cast<omp::BlockArgOpenMPOpInterface>(taskOp.getOperation());
3280 llvm::OpenMPIRBuilder &ompB = *moduleTranslation.getOpenMPBuilder();
3281 llvm::Module *m = moduleTranslation.getLLVMModule();
3282 llvm::LLVMContext &llvmCtx = m->getContext();
3283 llvm::OpenMPIRBuilder::LocationDescription bodyLoc(builder);
3284 uint32_t srcLocSize;
3285 llvm::Constant *srcLocStr =
3286 ompB.getOrCreateSrcLocStr(bodyLoc, srcLocSize);
3287 llvm::Value *bodyIdent = ompB.getOrCreateIdent(srcLocStr, srcLocSize);
3288 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the gtid
3289 // call lands inside the to-be-outlined task body.
3290 ompB.updateToLocation(bodyLoc);
3291 llvm::Value *bodyGtid = ompB.getOrCreateThreadID(bodyIdent);
3292 llvm::FunctionCallee getThData = ompB.getOrCreateRuntimeFunction(
3293 *m, llvm::omp::OMPRTL___kmpc_task_reduction_get_th_data);
3294 llvm::Type *ptrTy = llvm::PointerType::getUnqual(llvmCtx);
3295 llvm::Value *nullDesc = llvm::ConstantPointerNull::get(ptrTy);
3296 ArrayRef<BlockArgument> inRedBlockArgs = iface.getInReductionBlockArgs();
3297 for (auto [blockArg, origPtr] :
3298 llvm::zip_equal(inRedBlockArgs, inRedOrigPtrs)) {
3299 // __kmpc_task_reduction_get_th_data takes and returns a generic,
3300 // default-address-space `ptr`. Normalize a non-default-address-space
3301 // original pointer to the generic address space before the call, and
3302 // cast the returned private pointer back to the block argument's
3303 // address space when it differs (mirrors the taskloop reduction
3304 // remapping in convertOmpTaskloopContextOp).
3305 llvm::Value *lookupPtr = origPtr;
3306 if (auto *origPtrTy =
3307 llvm::dyn_cast<llvm::PointerType>(lookupPtr->getType());
3308 origPtrTy && origPtrTy->getAddressSpace() != 0)
3309 lookupPtr = builder.CreateAddrSpaceCast(lookupPtr, ptrTy);
3310 llvm::Value *priv = builder.CreateCall(
3311 getThData, {bodyGtid, nullDesc, lookupPtr}, "omp.inred.priv");
3312 if (auto *argPtrTy = llvm::dyn_cast<llvm::PointerType>(
3313 moduleTranslation.convertType(blockArg.getType()));
3314 argPtrTy && argPtrTy->getAddressSpace() != 0)
3315 priv = builder.CreateAddrSpaceCast(priv, argPtrTy);
3316 moduleTranslation.mapValue(blockArg, priv);
3317 }
3318 }
3319
3320 auto continuationBlockOrError = convertOmpOpRegions(
3321 taskOp.getRegion(), "omp.task.region", builder, moduleTranslation);
3322 if (failed(handleError(continuationBlockOrError, *taskOp)))
3323 return llvm::make_error<PreviouslyReportedError>();
3324
3325 builder.SetInsertPoint(continuationBlockOrError.get()->getTerminator());
3326
3327 if (failed(cleanupPrivateVars(taskOp, builder, moduleTranslation,
3328 taskOp.getLoc(), privateVarsInfo)))
3329 return llvm::make_error<PreviouslyReportedError>();
3330
3331 // Free heap allocated task context structure at the end of the task.
3332 taskStructMgr.freeStructPtr();
3333
3334 return llvm::Error::success();
3335 };
3336
3337 llvm::OpenMPIRBuilder &ompBuilder = *moduleTranslation.getOpenMPBuilder();
3338 SmallVector<llvm::UncondBrInst *> cancelTerminators;
3339 // The directive to match here is OMPD_taskgroup because it is the taskgroup
3340 // which is canceled. This is handled here because it is the task's cleanup
3341 // block which should be branched to.
3342 pushCancelFinalizationCB(cancelTerminators, builder, ompBuilder, taskOp,
3343 llvm::omp::Directive::OMPD_taskgroup);
3344
3345 llvm::OpenMPIRBuilder::DependenciesInfo dependencies;
3346 if (failed(buildDependData(taskOp.getDependVars(), taskOp.getDependKinds(),
3347 taskOp.getDependIterated(),
3348 taskOp.getDependIteratedKinds(), builder,
3349 moduleTranslation, dependencies)))
3350 return failure();
3351
3352 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
3353 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
3354 moduleTranslation.getOpenMPBuilder()->createTask(
3355 ompLoc, allocaIP, deallocBlocks, bodyCB, !taskOp.getUntied(),
3356 moduleTranslation.lookupValue(taskOp.getFinal()),
3357 moduleTranslation.lookupValue(taskOp.getIfExpr()), dependencies, ad,
3358 taskOp.getMergeable(),
3359 moduleTranslation.lookupValue(taskOp.getEventHandle()),
3360 moduleTranslation.lookupValue(taskOp.getPriority()));
3361
3362 if (failed(handleError(afterIP, *taskOp)))
3363 return failure();
3364
3365 // Set the correct branch target for task cancellation
3366 popCancelFinalizationCB(cancelTerminators, ompBuilder, afterIP.get());
3367
3368 builder.restoreIP(*afterIP);
3369
3370 if (dependencies.DepArray)
3371 builder.CreateFree(dependencies.DepArray);
3372
3373 return success();
3374}
3375
3376/// The correct entry point is convertOmpTaskloopContextOp. This gets called
3377/// whilst lowering the body of the taskloop context (i.e. the task function).
3378static LogicalResult
3379convertOmpTaskloopWrapperOp(omp::TaskloopWrapperOp loopWrapperOp,
3380 llvm::IRBuilderBase &builder,
3381 LLVM::ModuleTranslation &moduleTranslation) {
3382 mlir::Operation &opInst = *loopWrapperOp.getOperation();
3383 if (failed(checkImplementationStatus(opInst)))
3384 return failure();
3385
3386 // Recurse into the loop body.
3387 auto continuationBlockOrError = convertOmpOpRegions(
3388 loopWrapperOp.getRegion(), "omp.taskloop.wrapper.region", builder,
3389 moduleTranslation);
3390
3391 if (failed(handleError(continuationBlockOrError, opInst)))
3392 return failure();
3393
3394 builder.SetInsertPoint(continuationBlockOrError.get());
3395 return success();
3396}
3397
3398/// Look up the given value in the mapping, and if it's not there, translate its
3399/// defining operation at the current builder insertion point. Only pure,
3400/// regionless operations are supported because the same operation will later be
3401/// translated again when the taskloop body itself is lowered.
3402static llvm::Expected<llvm::Value *>
3404 LLVM::ModuleTranslation &moduleTranslation,
3405 llvm::IRBuilderBase &builder) {
3406 if (llvm::Value *mapped = moduleTranslation.lookupValue(value))
3407 return mapped;
3408
3409 Operation *defOp = value.getDefiningOp();
3410 if (!defOp)
3411 return llvm::make_error<llvm::StringError>(
3412 "value is a block argument and is not mapped",
3413 llvm::inconvertibleErrorCode());
3414 if (defOp->getNumRegions() != 0 || !isPure(defOp))
3415 return llvm::make_error<llvm::StringError>(
3416 "unsupported op defining taskloop loop bound",
3417 llvm::inconvertibleErrorCode());
3418
3419 SmallVector<Value> mappingsToRemove;
3420 mappingsToRemove.reserve(defOp->getNumOperands() + defOp->getNumResults());
3421 for (Value operand : defOp->getOperands()) {
3422 if (moduleTranslation.lookupValue(operand))
3423 continue;
3424
3425 llvm::Expected<llvm::Value *> operandOrError =
3426 lookupOrTranslatePureValue(operand, moduleTranslation, builder);
3427 if (!operandOrError)
3428 return operandOrError.takeError();
3429 moduleTranslation.mapValue(operand, *operandOrError);
3430 mappingsToRemove.push_back(operand);
3431 }
3432
3433 if (failed(moduleTranslation.convertOperation(*defOp, builder)))
3434 return llvm::make_error<llvm::StringError>(
3435 "failed to convert op defining taskloop loop bound",
3436 llvm::inconvertibleErrorCode());
3437
3438 llvm::Value *result = moduleTranslation.lookupValue(value);
3439 assert(result && "expected conversion of loop bound op to produce a value");
3440
3441 for (Value resultValue : defOp->getResults()) {
3442 if (moduleTranslation.lookupValue(resultValue))
3443 mappingsToRemove.push_back(resultValue);
3444 }
3445 for (Value mappedValue : mappingsToRemove)
3446 moduleTranslation.forgetMapping(mappedValue);
3447
3448 return result;
3449}
3450
3451static llvm::Error
3452computeTaskloopBounds(omp::LoopNestOp loopOp, llvm::IRBuilderBase &builder,
3453 LLVM::ModuleTranslation &moduleTranslation,
3454 llvm::Value *&lbVal, llvm::Value *&ubVal,
3455 llvm::Value *&stepVal) {
3456 Operation::operand_range lowerBounds = loopOp.getLoopLowerBounds();
3457 Operation::operand_range upperBounds = loopOp.getLoopUpperBounds();
3458 Operation::operand_range steps = loopOp.getLoopSteps();
3459
3460 llvm::Expected<llvm::Value *> firstLbOrErr =
3461 lookupOrTranslatePureValue(lowerBounds[0], moduleTranslation, builder);
3462 if (!firstLbOrErr)
3463 return firstLbOrErr.takeError();
3464
3465 llvm::Type *boundType = (*firstLbOrErr)->getType();
3466 ubVal = builder.getIntN(boundType->getIntegerBitWidth(), 1);
3467 if (loopOp.getCollapseNumLoops() > 1) {
3468 // In cases where Collapse is used with Taskloop, the upper bound of the
3469 // iteration space needs to be recalculated to cater for the collapsed loop.
3470 // The Collapsed Loop UpperBound is the product of all collapsed
3471 // loop's tripcount.
3472 // The LowerBound for collapsed loops is always 1. When the loops are
3473 // collapsed, it will reset the bounds and introduce processing to ensure
3474 // the index's are presented as expected. As this happens after creating
3475 // Taskloop, these bounds need predicting. Example:
3476 // !$omp taskloop collapse(2)
3477 // do i = 1, 10
3478 // do j = 1, 5
3479 // ..
3480 // end do
3481 // end do
3482 // This loop above has a total of 50 iterations, so the lb will be 1, and
3483 // the ub will be 50. collapseLoops in OMPIRBuilder then handles ensuring
3484 // that i and j are properly presented when used in the loop.
3485 for (uint64_t i = 0; i < loopOp.getCollapseNumLoops(); i++) {
3487 i == 0 ? std::move(firstLbOrErr)
3488 : lookupOrTranslatePureValue(lowerBounds[i], moduleTranslation,
3489 builder);
3490 if (!lbOrErr)
3491 return lbOrErr.takeError();
3493 upperBounds[i], moduleTranslation, builder);
3494 if (!ubOrErr)
3495 return ubOrErr.takeError();
3497 lookupOrTranslatePureValue(steps[i], moduleTranslation, builder);
3498 if (!stepOrErr)
3499 return stepOrErr.takeError();
3500
3501 llvm::Value *loopLb = *lbOrErr;
3502 llvm::Value *loopUb = *ubOrErr;
3503 llvm::Value *loopStep = *stepOrErr;
3504 // In some cases, such as where the ub is less than the lb so the loop
3505 // steps down, the calculation for the loopTripCount is swapped. To ensure
3506 // the correct value is found, calculate both UB - LB and LB - UB then
3507 // select which value to use depending on how the loop has been
3508 // configured.
3509 llvm::Value *loopLbMinusOne = builder.CreateSub(
3510 loopLb, builder.getIntN(boundType->getIntegerBitWidth(), 1));
3511 llvm::Value *loopUbMinusOne = builder.CreateSub(
3512 loopUb, builder.getIntN(boundType->getIntegerBitWidth(), 1));
3513 llvm::Value *boundsCmp = builder.CreateICmpSLT(loopLb, loopUb);
3514 llvm::Value *ubMinusLb = builder.CreateSub(loopUb, loopLbMinusOne);
3515 llvm::Value *lbMinusUb = builder.CreateSub(loopLb, loopUbMinusOne);
3516 llvm::Value *loopTripCount =
3517 builder.CreateSelect(boundsCmp, ubMinusLb, lbMinusUb);
3518 loopTripCount = builder.CreateBinaryIntrinsic(
3519 llvm::Intrinsic::abs, loopTripCount, builder.getFalse());
3520 // For loops that have a step value not equal to 1, we need to adjust the
3521 // trip count to ensure the correct number of iterations for the loop is
3522 // captured.
3523 llvm::Value *loopTripCountDivStep =
3524 builder.CreateSDiv(loopTripCount, loopStep);
3525 loopTripCountDivStep = builder.CreateBinaryIntrinsic(
3526 llvm::Intrinsic::abs, loopTripCountDivStep, builder.getFalse());
3527 llvm::Value *loopTripCountRem =
3528 builder.CreateSRem(loopTripCount, loopStep);
3529 loopTripCountRem = builder.CreateBinaryIntrinsic(
3530 llvm::Intrinsic::abs, loopTripCountRem, builder.getFalse());
3531 llvm::Value *needsRoundUp = builder.CreateICmpNE(
3532 loopTripCountRem,
3533 builder.getIntN(loopTripCountRem->getType()->getIntegerBitWidth(),
3534 0));
3535 loopTripCount =
3536 builder.CreateAdd(loopTripCountDivStep,
3537 builder.CreateZExtOrTrunc(
3538 needsRoundUp, loopTripCountDivStep->getType()));
3539 ubVal = builder.CreateMul(ubVal, loopTripCount);
3540 }
3541 lbVal = builder.getIntN(boundType->getIntegerBitWidth(), 1);
3542 stepVal = builder.getIntN(boundType->getIntegerBitWidth(), 1);
3543 } else {
3545 lookupOrTranslatePureValue(upperBounds[0], moduleTranslation, builder);
3546 if (!ubOrErr)
3547 return ubOrErr.takeError();
3549 lookupOrTranslatePureValue(steps[0], moduleTranslation, builder);
3550 if (!stepOrErr)
3551 return stepOrErr.takeError();
3552 lbVal = *firstLbOrErr;
3553 ubVal = *ubOrErr;
3554 stepVal = *stepOrErr;
3555 }
3556
3557 assert(lbVal != nullptr && "Expected value for lbVal");
3558 assert(ubVal != nullptr && "Expected value for ubVal");
3559 assert(stepVal != nullptr && "Expected value for stepVal");
3560 return llvm::Error::success();
3561}
3562
3563// Converts an OpenMP taskloop construct into LLVM IR using OpenMPIRBuilder.
3564static LogicalResult
3565convertOmpTaskloopContextOp(omp::TaskloopContextOp contextOp,
3566 llvm::IRBuilderBase &builder,
3567 LLVM::ModuleTranslation &moduleTranslation) {
3568 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
3569 mlir::Operation &opInst = *contextOp.getOperation();
3570 omp::TaskloopWrapperOp loopWrapperOp = contextOp.getLoopOp();
3571 if (failed(checkImplementationStatus(opInst)))
3572 return failure();
3573
3574 // It stores the pointer of allocated firstprivate copies,
3575 // which can be used later for freeing the allocated space.
3576 SmallVector<llvm::Value *> llvmFirstPrivateVars;
3577 PrivateVarsInfo privateVarsInfo(contextOp);
3578 TaskContextStructManager taskStructMgr{builder, moduleTranslation,
3579 privateVarsInfo.privatizers};
3580
3582 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
3583 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
3584
3585 assert(builder.GetInsertPoint() == builder.GetInsertBlock()->end());
3586 llvm::BasicBlock *taskloopStartBlock = llvm::BasicBlock::Create(
3587 builder.getContext(), "omp.taskloop.wrapper.start",
3588 /*Parent=*/builder.GetInsertBlock()->getParent());
3589 llvm::Instruction *branchToTaskloopStartBlock =
3590 builder.CreateBr(taskloopStartBlock);
3591 builder.SetInsertPoint(branchToTaskloopStartBlock);
3592
3593 llvm::BasicBlock *copyBlock =
3594 splitBB(builder, /*CreateBranch=*/true, "omp.private.copy");
3595 llvm::BasicBlock *initBlock =
3596 splitBB(builder, /*CreateBranch=*/true, "omp.private.init");
3597
3599 moduleTranslation, allocaIP, deallocBlocks);
3600
3601 // Allocate and initialize private variables
3602 builder.SetInsertPoint(initBlock->getTerminator());
3603
3604 // TODO: don't allocate if the loop has zero iterations.
3605 taskStructMgr.generateTaskContextStruct();
3606 taskStructMgr.createGEPsToPrivateVars();
3607
3608 llvmFirstPrivateVars.resize(privateVarsInfo.blockArgs.size());
3609
3610 for (auto [i, zip] : llvm::enumerate(llvm::zip_equal(
3611 privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
3612 privateVarsInfo.blockArgs, taskStructMgr.getLLVMPrivateVarGEPs()))) {
3613 auto [privDecl, mlirPrivVar, blockArg, llvmPrivateVarAlloc] = zip;
3614 // To be handled inside the taskloop.
3615 if (!privDecl.readsFromMold())
3616 continue;
3617 assert(llvmPrivateVarAlloc &&
3618 "reads from mold so shouldn't have been skipped");
3619
3620 llvm::Expected<llvm::Value *> privateVarOrErr =
3621 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
3622 blockArg, llvmPrivateVarAlloc, initBlock);
3623 if (!privateVarOrErr)
3624 return handleError(privateVarOrErr, *contextOp.getOperation());
3625
3626 llvmFirstPrivateVars[i] = privateVarOrErr.get();
3627
3628 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3629 builder.SetInsertPoint(builder.GetInsertBlock()->getTerminator());
3630
3631 [[maybe_unused]] llvm::Value *llvmPrivateVar = llvmPrivateVarAlloc;
3632 if ((privateVarOrErr.get() != llvmPrivateVarAlloc) &&
3633 !mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3634 builder.CreateStore(privateVarOrErr.get(), llvmPrivateVarAlloc);
3635 // Load it so we have the value pointed to by the GEP
3636 llvmPrivateVar = builder.CreateLoad(privateVarOrErr.get()->getType(),
3637 llvmPrivateVarAlloc);
3638 }
3639 assert(llvmPrivateVar->getType() ==
3640 moduleTranslation.convertType(blockArg.getType()));
3641 }
3642
3643 // firstprivate copy region
3644 setInsertPointForPossiblyEmptyBlock(builder, copyBlock);
3645 if (failed(copyFirstPrivateVars(
3646 contextOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
3647 taskStructMgr.getLLVMPrivateVarGEPs(), privateVarsInfo.privatizers,
3648 contextOp.getPrivateNeedsBarrier())))
3649 return llvm::failure();
3650
3651 // Resolve and validate reduction / in_reduction declarations up front.
3652 // This is pure MLIR symbol-table work and does not emit IR, so do it
3653 // before moving the builder to the taskloop start block. Richer
3654 // declare_reduction shapes (byref) have been rejected already by
3655 // checkImplementationStatus; the rest (two-argument initializer, cleanup
3656 // region, missing combiner) are rejected by the helper.
3659 contextOp.getOperation(), contextOp.getReductionSyms(),
3660 "omp.taskloop.context", "reduction", redDecls)))
3661 return failure();
3664 contextOp.getOperation(), contextOp.getInReductionSyms(),
3665 "omp.taskloop.context", "in_reduction", inRedDecls)))
3666 return failure();
3667
3668 // The op verifier rejects nogroup + reduction, so no check is needed here.
3669
3670 SmallVector<llvm::Value *> redOrigPtrs;
3671 redOrigPtrs.reserve(redDecls.size());
3672 for (Value v : contextOp.getReductionVars())
3673 redOrigPtrs.push_back(moduleTranslation.lookupValue(v));
3674 SmallVector<llvm::Value *> inRedOrigPtrs;
3675 inRedOrigPtrs.reserve(inRedDecls.size());
3676 for (Value v : contextOp.getInReductionVars())
3677 inRedOrigPtrs.push_back(moduleTranslation.lookupValue(v));
3678
3679 // Set up insertion point for emitting the implicit-taskgroup reduction
3680 // setup (if any) and for the subsequent call to createTaskloop().
3681 builder.SetInsertPoint(taskloopStartBlock);
3682
3683 llvm::OpenMPIRBuilder &ompBuilderRef = *moduleTranslation.getOpenMPBuilder();
3684 llvm::Module *module = moduleTranslation.getLLVMModule();
3685
3686 // If we have task_reduction items, we must emit our own implicit
3687 // __kmpc_taskgroup so that the descriptor returned by __kmpc_taskred_init
3688 // is associated with that taskgroup. We then force NoGroup=true so that
3689 // OpenMPIRBuilder::createTaskloop does not emit a second taskgroup.
3690 bool implicitTaskgroup = !redDecls.empty();
3691 llvm::Value *redDesc = nullptr;
3692 if (implicitTaskgroup) {
3693 llvm::OpenMPIRBuilder::LocationDescription redLoc(builder);
3694 uint32_t srcLocSize;
3695 llvm::Constant *srcLocStr =
3696 ompBuilderRef.getOrCreateSrcLocStr(redLoc, srcLocSize);
3697 llvm::Value *ident = ompBuilderRef.getOrCreateIdent(srcLocStr, srcLocSize);
3698 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the
3699 // gtid call lands at our insertion point.
3700 ompBuilderRef.updateToLocation(redLoc);
3701 llvm::Value *outerGtid = ompBuilderRef.getOrCreateThreadID(ident);
3702 llvm::FunctionCallee taskgroupFn = ompBuilderRef.getOrCreateRuntimeFunction(
3703 *module, llvm::omp::OMPRTL___kmpc_taskgroup);
3704 builder.CreateCall(taskgroupFn, {ident, outerGtid});
3705
3706 redDesc = emitTaskReductionInitCall(redDecls, redOrigPtrs,
3707 "__omp_taskloop_taskred_", builder,
3708 allocaIP, moduleTranslation);
3709 if (!redDesc)
3710 return failure();
3711 }
3712
3713 auto loopOp = cast<omp::LoopNestOp>(loopWrapperOp.getWrappedLoop());
3714 llvm::Value *lbVal = nullptr;
3715 llvm::Value *ubVal = nullptr;
3716 llvm::Value *stepVal = nullptr;
3717 if (llvm::Error err = computeTaskloopBounds(
3718 loopOp, builder, moduleTranslation, lbVal, ubVal, stepVal))
3719 return handleError(std::move(err), opInst);
3720
3721 auto bodyCB =
3722 [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
3723 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
3724 // Save the alloca insertion point on ModuleTranslation stack for use in
3725 // nested regions.
3727 moduleTranslation, allocaIP, deallocBlocks);
3728
3729 // translate the body of the taskloop:
3730 builder.restoreIP(codegenIP);
3731
3732 llvm::BasicBlock *privInitBlock = nullptr;
3733 privateVarsInfo.llvmVars.resize(privateVarsInfo.blockArgs.size());
3734 for (auto [i, zip] : llvm::enumerate(llvm::zip_equal(
3735 privateVarsInfo.blockArgs, privateVarsInfo.privatizers,
3736 privateVarsInfo.mlirVars))) {
3737 auto [blockArg, privDecl, mlirPrivVar] = zip;
3738 // This is handled before the task executes
3739 if (privDecl.readsFromMold())
3740 continue;
3741
3742 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3743 llvm::Type *llvmAllocType =
3744 moduleTranslation.convertType(privDecl.getType());
3745 builder.SetInsertPoint(allocaIP.getBlock()->getTerminator());
3746 llvm::Value *llvmPrivateVar = builder.CreateAlloca(
3747 llvmAllocType, /*ArraySize=*/nullptr, "omp.private.alloc");
3748
3749 llvm::Expected<llvm::Value *> privateVarOrError =
3750 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
3751 blockArg, llvmPrivateVar, privInitBlock);
3752 if (!privateVarOrError)
3753 return privateVarOrError.takeError();
3754 moduleTranslation.mapValue(blockArg, privateVarOrError.get());
3755 privateVarsInfo.llvmVars[i] = privateVarOrError.get();
3756 }
3757
3758 taskStructMgr.createGEPsToPrivateVars();
3759 for (auto [i, llvmPrivVar] :
3760 llvm::enumerate(taskStructMgr.getLLVMPrivateVarGEPs())) {
3761 if (!llvmPrivVar) {
3762 assert(privateVarsInfo.llvmVars[i] &&
3763 "This is added in the loop above");
3764 continue;
3765 }
3766 privateVarsInfo.llvmVars[i] = llvmPrivVar;
3767 }
3768
3769 // Find and map the addresses of each variable within the taskloop context
3770 // structure
3771 for (auto [blockArg, llvmPrivateVar, privateDecl] :
3772 llvm::zip_equal(privateVarsInfo.blockArgs, privateVarsInfo.llvmVars,
3773 privateVarsInfo.privatizers)) {
3774 // This was handled above.
3775 if (!privateDecl.readsFromMold())
3776 continue;
3777 // Fix broken pass-by-value case for Fortran character boxes
3778 if (!mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3779 llvmPrivateVar = builder.CreateLoad(
3780 moduleTranslation.convertType(blockArg.getType()), llvmPrivateVar);
3781 }
3782 assert(llvmPrivateVar->getType() ==
3783 moduleTranslation.convertType(blockArg.getType()));
3784 moduleTranslation.mapValue(blockArg, llvmPrivateVar);
3785 }
3786
3787 // Map reduction and in_reduction block arguments to the per-task private
3788 // storage returned by __kmpc_task_reduction_get_th_data. This call must
3789 // be emitted inside the to-be-outlined task body so that it returns the
3790 // *executing* thread's gtid (not the encountering thread's). The
3791 // taskgroup descriptor `redDesc` is computed in the outer scope and is
3792 // auto-captured into the task shareds aggregate by CodeExtractor during
3793 // OpenMPIRBuilder::finalize. For in_reduction the descriptor is NULL:
3794 // the runtime walks up enclosing taskgroups to find the matching
3795 // task_reduction registration for `origPtr`.
3796 if (!redDecls.empty() || !inRedDecls.empty()) {
3797 auto iface =
3798 cast<omp::BlockArgOpenMPOpInterface>(contextOp.getOperation());
3799 llvm::OpenMPIRBuilder &ompB = *moduleTranslation.getOpenMPBuilder();
3800 llvm::Module *m = moduleTranslation.getLLVMModule();
3801 llvm::LLVMContext &llvmCtx = m->getContext();
3802 llvm::OpenMPIRBuilder::LocationDescription bodyLoc(builder);
3803 uint32_t srcLocSize;
3804 llvm::Constant *srcLocStr =
3805 ompB.getOrCreateSrcLocStr(bodyLoc, srcLocSize);
3806 llvm::Value *bodyIdent = ompB.getOrCreateIdent(srcLocStr, srcLocSize);
3807 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the
3808 // gtid call lands inside the to-be-outlined task body.
3809 ompB.updateToLocation(bodyLoc);
3810 llvm::Value *bodyGtid = ompB.getOrCreateThreadID(bodyIdent);
3811 llvm::FunctionCallee getThData = ompB.getOrCreateRuntimeFunction(
3812 *m, llvm::omp::OMPRTL___kmpc_task_reduction_get_th_data);
3813 llvm::Type *ptrTy = llvm::PointerType::getUnqual(llvmCtx);
3814
3815 // Emit one __kmpc_task_reduction_get_th_data lookup for a reduction /
3816 // in_reduction item and map its block argument to the per-task private
3817 // storage the runtime returns. The runtime entry point takes (and
3818 // returns) a generic, default-address-space `ptr`, so normalize a
3819 // non-default-address-space original pointer to the generic address
3820 // space before the call (mirroring the descriptor setup in
3821 // emitTaskReductionInitCall), and cast the returned private pointer back
3822 // to the block argument's address space when that differs.
3823 auto remapReductionArg = [&](BlockArgument blockArg, llvm::Value *desc,
3824 llvm::Value *origPtr,
3825 const llvm::Twine &name) {
3826 if (auto *origPtrTy =
3827 llvm::dyn_cast<llvm::PointerType>(origPtr->getType());
3828 origPtrTy && origPtrTy->getAddressSpace() != 0)
3829 origPtr = builder.CreateAddrSpaceCast(origPtr, ptrTy);
3830 llvm::Value *priv =
3831 builder.CreateCall(getThData, {bodyGtid, desc, origPtr}, name);
3832 if (auto *argPtrTy = llvm::dyn_cast<llvm::PointerType>(
3833 moduleTranslation.convertType(blockArg.getType()));
3834 argPtrTy && argPtrTy->getAddressSpace() != 0)
3835 priv = builder.CreateAddrSpaceCast(priv, argPtrTy);
3836 moduleTranslation.mapValue(blockArg, priv);
3837 };
3838
3839 ArrayRef<BlockArgument> redBlockArgs = iface.getReductionBlockArgs();
3840 for (auto [blockArg, origPtr] :
3841 llvm::zip_equal(redBlockArgs, redOrigPtrs))
3842 remapReductionArg(blockArg, redDesc, origPtr, "omp.taskred.priv");
3843 ArrayRef<BlockArgument> inRedBlockArgs = iface.getInReductionBlockArgs();
3844 llvm::Value *nullDesc = llvm::ConstantPointerNull::get(ptrTy);
3845 for (auto [blockArg, origPtr] :
3846 llvm::zip_equal(inRedBlockArgs, inRedOrigPtrs))
3847 remapReductionArg(blockArg, nullDesc, origPtr, "omp.inred.priv");
3848 }
3849
3850 // Lower the contents of the taskloop context region: this is the body of
3851 // the generated task, not the loop.
3852 auto continuationBlockOrError = convertOmpOpRegions(
3853 contextOp.getRegion(), "omp.taskloop.context.region", builder,
3854 moduleTranslation);
3855
3856 if (failed(handleError(continuationBlockOrError, opInst)))
3857 return llvm::make_error<PreviouslyReportedError>();
3858
3859 builder.SetInsertPoint(continuationBlockOrError.get()->getTerminator());
3860
3861 // This is freeing the private variables as mapped inside of the task: these
3862 // will be per-task private copies possibly after task duplication. This is
3863 // handled transparently by how these are passed to the structure passed
3864 // into the outlined function. When the task is duplicated, that structure
3865 // is duplicated too.
3866 if (failed(cleanupPrivateVars(contextOp, builder, moduleTranslation,
3867 contextOp.getLoc(), privateVarsInfo)))
3868 return llvm::make_error<PreviouslyReportedError>();
3869 // Similarly, the task context structure freed inside the task is the
3870 // per-task copy after task duplication.
3871 taskStructMgr.freeStructPtr();
3872
3873 return llvm::Error::success();
3874 };
3875
3876 // Taskloop divides into an appropriate number of tasks by repeatedly
3877 // duplicating the original task. Each time this is done, the task context
3878 // structure must be duplicated too.
3879 auto taskDupCB = [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
3880 llvm::Value *destPtr, llvm::Value *srcPtr)
3882 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3883 builder.restoreIP(codegenIP);
3884
3885 llvm::Type *ptrTy =
3886 builder.getPtrTy(srcPtr->getType()->getPointerAddressSpace());
3887 llvm::Value *src =
3888 builder.CreateLoad(ptrTy, srcPtr, "omp.taskloop.context.src");
3889
3890 TaskContextStructManager &srcStructMgr = taskStructMgr;
3891 TaskContextStructManager destStructMgr(builder, moduleTranslation,
3892 privateVarsInfo.privatizers);
3893 destStructMgr.generateTaskContextStruct();
3894 llvm::Value *dest = destStructMgr.getStructPtr();
3895 dest->setName("omp.taskloop.context.dest");
3896 builder.CreateStore(dest, destPtr);
3897
3899 srcStructMgr.createGEPsToPrivateVars(src);
3901 destStructMgr.createGEPsToPrivateVars(dest);
3902
3903 // Inline init regions.
3904 for (auto [privDecl, mold, blockArg, llvmPrivateVarAlloc] :
3905 llvm::zip_equal(privateVarsInfo.privatizers, srcGEPs,
3906 privateVarsInfo.blockArgs, destGEPs)) {
3907 // To be handled inside task body.
3908 if (!privDecl.readsFromMold())
3909 continue;
3910 assert(llvmPrivateVarAlloc &&
3911 "reads from mold so shouldn't have been skipped");
3912
3913 llvm::Value *moldArg = materializeRegionArgValue(
3914 builder, moduleTranslation, privDecl.getInitMoldArg(), mold);
3916 builder, moduleTranslation, privDecl, moldArg, blockArg,
3917 llvmPrivateVarAlloc, builder.GetInsertBlock());
3918 if (!privateVarOrErr)
3919 return privateVarOrErr.takeError();
3920
3922
3923 // TODO: this is a bit of a hack for Fortran character boxes.
3924 // Character boxes are passed by value into the init region and then the
3925 // initialized character box is yielded by value. Here we need to store
3926 // the yielded value into the private allocation, and load the private
3927 // allocation to match the type expected by region block arguments.
3928 [[maybe_unused]] llvm::Value *llvmPrivateVar = llvmPrivateVarAlloc;
3929 if ((privateVarOrErr.get() != llvmPrivateVarAlloc) &&
3930 !mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3931 builder.CreateStore(privateVarOrErr.get(), llvmPrivateVarAlloc);
3932 // Load it so we have the value pointed to by the GEP
3933 llvmPrivateVar = builder.CreateLoad(privateVarOrErr.get()->getType(),
3934 llvmPrivateVarAlloc);
3935 }
3936 assert(llvmPrivateVar->getType() ==
3937 moduleTranslation.convertType(blockArg.getType()));
3938
3939 // Mapping blockArg -> llvmPrivateVarAlloc is done inside the body
3940 // callback so that OpenMPIRBuilder doesn't try to pass each GEP address
3941 // through a stack allocated structure.
3942 }
3943
3944 if (failed(copyFirstPrivateVars(contextOp.getOperation(), builder,
3945 moduleTranslation, srcGEPs, destGEPs,
3946 privateVarsInfo.privatizers,
3947 contextOp.getPrivateNeedsBarrier())))
3948 return llvm::make_error<PreviouslyReportedError>();
3949
3950 return builder.saveIP();
3951 };
3952
3953 auto loopInfo = [&]() -> llvm::Expected<llvm::CanonicalLoopInfo *> {
3954 llvm::CanonicalLoopInfo *loopInfo = findCurrentLoopInfo(moduleTranslation);
3955 return loopInfo;
3956 };
3957
3958 llvm::Value *ifCond = nullptr;
3959 llvm::Value *grainsize = nullptr;
3960 int sched = 0; // default
3961 mlir::Value grainsizeVal = contextOp.getGrainsize();
3962 mlir::Value numTasksVal = contextOp.getNumTasks();
3963 if (Value ifVar = contextOp.getIfExpr())
3964 ifCond = moduleTranslation.lookupValue(ifVar);
3965 if (grainsizeVal) {
3966 grainsize = moduleTranslation.lookupValue(grainsizeVal);
3967 sched = 1; // grainsize
3968 } else if (numTasksVal) {
3969 grainsize = moduleTranslation.lookupValue(numTasksVal);
3970 sched = 2; // num_tasks
3971 }
3972
3973 llvm::OpenMPIRBuilder::TaskDupCallbackTy taskDupOrNull = nullptr;
3974 if (taskStructMgr.getStructPtr())
3975 taskDupOrNull = taskDupCB;
3976
3977 llvm::OpenMPIRBuilder &ompBuilder = *moduleTranslation.getOpenMPBuilder();
3978 SmallVector<llvm::UncondBrInst *> cancelTerminators;
3979 // The directive to match here is OMPD_taskgroup because it is the
3980 // taskgroup which is canceled. This is handled here because it is the
3981 // task's cleanup block which should be branched to. It doesn't depend upon
3982 // nogroup because even in that case the taskloop might still be inside an
3983 // explicit taskgroup.
3984 pushCancelFinalizationCB(cancelTerminators, builder, ompBuilder, contextOp,
3985 llvm::omp::Directive::OMPD_taskgroup);
3986
3987 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
3988 bool effectiveNoGroup = contextOp.getNogroup() || implicitTaskgroup;
3989 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
3990 moduleTranslation.getOpenMPBuilder()->createTaskloop(
3991 ompLoc, allocaIP, deallocBlocks, bodyCB, loopInfo, lbVal, ubVal,
3992 stepVal, contextOp.getUntied(), ifCond, grainsize, effectiveNoGroup,
3993 sched, moduleTranslation.lookupValue(contextOp.getFinal()),
3994 contextOp.getMergeable(),
3995 moduleTranslation.lookupValue(contextOp.getPriority()),
3996 loopOp.getCollapseNumLoops(), taskDupOrNull,
3997 taskStructMgr.getStructPtr());
3998
3999 if (failed(handleError(afterIP, opInst)))
4000 return failure();
4001
4002 popCancelFinalizationCB(cancelTerminators, ompBuilder, afterIP.get());
4003
4004 builder.restoreIP(*afterIP);
4005
4006 // Close the implicit taskgroup we opened for task_reduction. The end call
4007 // must execute on the encountering thread, so use the outer-scope gtid.
4008 if (implicitTaskgroup) {
4009 llvm::OpenMPIRBuilder::LocationDescription endLoc(builder);
4010 uint32_t srcLocSize;
4011 llvm::Constant *srcLocStr =
4012 ompBuilder.getOrCreateSrcLocStr(endLoc, srcLocSize);
4013 llvm::Value *ident = ompBuilder.getOrCreateIdent(srcLocStr, srcLocSize);
4014 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the
4015 // gtid call lands at our insertion point.
4016 ompBuilder.updateToLocation(endLoc);
4017 llvm::Value *outerGtid = ompBuilder.getOrCreateThreadID(ident);
4018 llvm::FunctionCallee endTgFn = ompBuilder.getOrCreateRuntimeFunction(
4019 *moduleTranslation.getLLVMModule(),
4020 llvm::omp::OMPRTL___kmpc_end_taskgroup);
4021 builder.CreateCall(endTgFn, {ident, outerGtid});
4022 }
4023 return success();
4024}
4025
4026/// Build an outlined init helper for a task_reduction declare_reduction op.
4027/// Signature: void(ptr %priv, ptr %orig). For non-byref reductions, the init
4028/// region's mold argument is mapped following the same rule as the regular
4029/// reduction path (`mapInitializationArgs`): a non-pointer mold loads the
4030/// value from %orig, while a pointer-typed mold receives %orig directly. The
4031/// yielded value is stored into %priv.
4032static llvm::Function *
4033emitTaskReductionInitFn(omp::DeclareReductionOp decl, StringRef baseName,
4034 LLVM::ModuleTranslation &moduleTranslation) {
4035 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4036 llvm::LLVMContext &ctx = llvmModule->getContext();
4037 llvm::Type *voidTy = llvm::Type::getVoidTy(ctx);
4038 llvm::Type *ptrTy = llvm::PointerType::getUnqual(ctx);
4039 llvm::FunctionType *fty =
4040 llvm::FunctionType::get(voidTy, {ptrTy, ptrTy}, false);
4041 llvm::Function *fn =
4042 llvm::Function::Create(fty, llvm::GlobalValue::InternalLinkage,
4043 baseName + ".red.init", llvmModule);
4044 fn->setDoesNotRecurse();
4045 fn->getArg(0)->setName("priv");
4046 fn->getArg(1)->setName("orig");
4047
4048 llvm::BasicBlock *entry = llvm::BasicBlock::Create(ctx, "entry", fn);
4049 llvm::IRBuilder<> b(entry);
4050
4051 // Map the initializer's mold argument the same way the regular reduction
4052 // path does in `mapInitializationArgs`: only load the original value when a
4053 // non-pointer mold is expected. For a pointer-typed mold the storage pointer
4054 // (%orig) is passed through directly, so a mold-yielding initializer lowers
4055 // to `store ptr %orig, ptr %priv` rather than emitting a spurious load.
4056 Value moldArg = decl.getInitializerMoldArg();
4057 llvm::Value *origVal = fn->getArg(1);
4058 if (!isa<LLVM::LLVMPointerType>(moldArg.getType()))
4059 origVal = b.CreateLoad(moduleTranslation.convertType(moldArg.getType()),
4060 fn->getArg(1), "omp.orig");
4061 moduleTranslation.mapValue(moldArg, origVal);
4063 if (failed(inlineConvertOmpRegions(decl.getInitializerRegion(),
4064 "omp.taskred.init", b, moduleTranslation,
4065 &phis))) {
4066 fn->eraseFromParent();
4067 return nullptr;
4068 }
4069 assert(phis.size() == 1 &&
4070 "expected one value yielded from reduction initializer");
4071 b.CreateStore(phis[0], fn->getArg(0));
4072 b.CreateRetVoid();
4073
4074 moduleTranslation.forgetMapping(decl.getInitializerRegion());
4075 return fn;
4076}
4077
4078/// Build an outlined combiner helper for a task_reduction declare_reduction op.
4079/// Signature: void(ptr %lhs, ptr %rhs). For non-byref reductions, the values
4080/// at *%lhs and *%rhs are loaded, fed into the combiner region, and the
4081/// yielded scalar is stored back into *%lhs.
4082static llvm::Function *
4083emitTaskReductionCombFn(omp::DeclareReductionOp decl, StringRef baseName,
4084 LLVM::ModuleTranslation &moduleTranslation) {
4085 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4086 llvm::LLVMContext &ctx = llvmModule->getContext();
4087 llvm::Type *voidTy = llvm::Type::getVoidTy(ctx);
4088 llvm::Type *ptrTy = llvm::PointerType::getUnqual(ctx);
4089 llvm::FunctionType *fty =
4090 llvm::FunctionType::get(voidTy, {ptrTy, ptrTy}, false);
4091 llvm::Function *fn =
4092 llvm::Function::Create(fty, llvm::GlobalValue::InternalLinkage,
4093 baseName + ".red.comb", llvmModule);
4094 fn->setDoesNotRecurse();
4095 fn->getArg(0)->setName("lhs");
4096 fn->getArg(1)->setName("rhs");
4097
4098 llvm::BasicBlock *entry = llvm::BasicBlock::Create(ctx, "entry", fn);
4099 llvm::IRBuilder<> b(entry);
4100
4101 llvm::Type *elemTy = moduleTranslation.convertType(decl.getType());
4102 Block &combBlock = decl.getReductionRegion().front();
4103 assert(combBlock.getNumArguments() == 2 &&
4104 "expected two arguments in declare_reduction combiner");
4105 llvm::Value *lhsVal = b.CreateLoad(elemTy, fn->getArg(0), "omp.lhs");
4106 llvm::Value *rhsVal = b.CreateLoad(elemTy, fn->getArg(1), "omp.rhs");
4107 moduleTranslation.mapValue(combBlock.getArgument(0), lhsVal);
4108 moduleTranslation.mapValue(combBlock.getArgument(1), rhsVal);
4109
4111 if (failed(inlineConvertOmpRegions(decl.getReductionRegion(),
4112 "omp.taskred.comb", b, moduleTranslation,
4113 &phis))) {
4114 fn->eraseFromParent();
4115 return nullptr;
4116 }
4117 assert(phis.size() == 1 &&
4118 "expected one value yielded from reduction combiner");
4119 b.CreateStore(phis[0], fn->getArg(0));
4120 b.CreateRetVoid();
4121
4122 moduleTranslation.forgetMapping(decl.getReductionRegion());
4123 return fn;
4124}
4125
4126/// Emit the per-taskgroup task_reduction descriptor array and the
4127/// `__kmpc_taskred_init` runtime call. \p origPtrs holds the LLVM values for
4128/// the original (shared) variables, one per declaration in \p redDecls.
4129/// `builder` must be set to the point at which the descriptor stores and the
4130/// init call should be emitted; the descriptor array itself is allocated at
4131/// \p allocaIP. \p helperNamePrefix is used to disambiguate the generated
4132/// init/combiner helper symbol names between taskgroup and taskloop callers.
4133///
4134/// When \p isModifier is false, emits `__kmpc_taskred_init` and returns the
4135/// `ptr` value it produces (the taskgroup reduction handle). When \p isModifier
4136/// is true, emits `__kmpc_taskred_modifier_init` instead to open a
4137/// task-reduction scope for a parallel or worksharing construct, passing
4138/// \p isWorksharing as the runtime `is_ws` argument. Returns null on failure.
4139///
4140/// Only the non-byref form is handled here. Byref reductions have already
4141/// been rejected by `checkImplementationStatus`.
4142static llvm::Value *emitTaskReductionInitCall(
4144 ArrayRef<llvm::Value *> origPtrs, StringRef helperNamePrefix,
4145 llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder::InsertPointTy allocaIP,
4146 LLVM::ModuleTranslation &moduleTranslation, bool isModifier,
4147 bool isWorksharing) {
4148 assert(redDecls.size() == origPtrs.size() &&
4149 "expected one orig pointer per reduction decl");
4150 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4151 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4152 llvm::LLVMContext &ctx = llvmModule->getContext();
4153 const llvm::DataLayout &dl = llvmModule->getDataLayout();
4154
4155 llvm::Type *ptrTy = llvm::PointerType::getUnqual(ctx);
4156 llvm::Type *i32Ty = llvm::Type::getInt32Ty(ctx);
4157 llvm::Type *sizeTy =
4158 llvm::Type::getIntNTy(ctx, dl.getPointerSizeInBits(/*AddrSpace=*/0));
4159
4160 // Identified `kmp_taskred_input_t` struct, matching the layout used by
4161 // Clang's CGOpenMPRuntime::emitTaskReductionInit.
4162 llvm::StructType *redInputTy =
4163 llvm::StructType::getTypeByName(ctx, "kmp_taskred_input_t");
4164 if (!redInputTy)
4165 redInputTy = llvm::StructType::create(
4166 ctx, {ptrTy, ptrTy, sizeTy, ptrTy, ptrTy, ptrTy, i32Ty},
4167 "kmp_taskred_input_t");
4168
4169 unsigned n = redDecls.size();
4170 llvm::ArrayType *arrTy = llvm::ArrayType::get(redInputTy, n);
4171
4172 // Allocate the descriptor array in the enclosing function's alloca block.
4173 llvm::AllocaInst *arrAlloca;
4174 {
4175 llvm::IRBuilderBase::InsertPointGuard guard(builder);
4176 builder.restoreIP(allocaIP);
4177 arrAlloca =
4178 builder.CreateAlloca(arrTy, /*ArraySize=*/nullptr, ".taskred.input");
4179 }
4180
4181 // Fill each descriptor entry at the current builder insertion point.
4182 llvm::Value *zero = builder.getInt32(0);
4183 for (unsigned i = 0; i < n; ++i) {
4184 omp::DeclareReductionOp decl = redDecls[i];
4185 llvm::Value *orig = origPtrs[i];
4186 if (auto *origPtrTy = llvm::dyn_cast<llvm::PointerType>(orig->getType());
4187 origPtrTy && origPtrTy->getAddressSpace() != 0)
4188 orig = builder.CreateAddrSpaceCast(orig, ptrTy);
4189 llvm::Type *elemTy = moduleTranslation.convertType(decl.getType());
4190 uint64_t size = dl.getTypeAllocSize(elemTy).getFixedValue();
4191
4192 std::string baseName =
4193 (llvm::Twine(helperNamePrefix) + decl.getSymName()).str();
4194 llvm::Function *initFn =
4195 emitTaskReductionInitFn(decl, baseName, moduleTranslation);
4196 llvm::Function *combFn =
4197 emitTaskReductionCombFn(decl, baseName, moduleTranslation);
4198 if (!initFn || !combFn)
4199 return nullptr;
4200 llvm::Value *elemPtr = builder.CreateInBoundsGEP(
4201 arrTy, arrAlloca, {zero, builder.getInt32(i)}, ".taskred.elem");
4202 auto storeField = [&](unsigned fieldIdx, llvm::Value *val) {
4203 llvm::Value *fieldPtr =
4204 builder.CreateStructGEP(redInputTy, elemPtr, fieldIdx);
4205 builder.CreateStore(val, fieldPtr);
4206 };
4207 storeField(0, orig); // reduce_shar
4208 storeField(1, orig); // reduce_orig
4209 storeField(2, llvm::ConstantInt::get(sizeTy, size)); // reduce_size
4210 storeField(3, initFn); // reduce_init
4211 storeField(4, llvm::ConstantPointerNull::get(ptrTy)); // reduce_fini
4212 storeField(5, combFn); // reduce_comb
4213 storeField(6, llvm::ConstantInt::get(i32Ty, 0)); // flags
4214 }
4215
4216 // Emit the runtime call that registers the task reduction data.
4217 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4218 uint32_t srcLocSize;
4219 llvm::Constant *srcLocStr =
4220 ompBuilder->getOrCreateSrcLocStr(ompLoc, srcLocSize);
4221 llvm::Value *ident = ompBuilder->getOrCreateIdent(srcLocStr, srcLocSize);
4222 ompBuilder->updateToLocation(ompLoc);
4223 llvm::Value *gtid = ompBuilder->getOrCreateThreadID(ident);
4224 if (isModifier) {
4225 // __kmpc_taskred_modifier_init(loc, gtid, is_ws, num, &arr) opens a
4226 // task-reduction scope for the enclosing parallel/worksharing region.
4227 llvm::FunctionCallee modInit = ompBuilder->getOrCreateRuntimeFunction(
4228 *llvmModule, llvm::omp::OMPRTL___kmpc_taskred_modifier_init);
4229 return builder.CreateCall(modInit,
4230 {ident, gtid,
4231 builder.getInt32(isWorksharing ? 1 : 0),
4232 builder.getInt32(n), arrAlloca},
4233 ".taskred.desc");
4234 }
4235 // __kmpc_taskred_init(gtid, num, &arr).
4236 llvm::FunctionCallee taskredInit = ompBuilder->getOrCreateRuntimeFunction(
4237 *llvmModule, llvm::omp::OMPRTL___kmpc_taskred_init);
4238 return builder.CreateCall(taskredInit, {gtid, builder.getInt32(n), arrAlloca},
4239 ".taskred.desc");
4240}
4241
4242/// Emits `__kmpc_task_reduction_modifier_fini(loc, gtid, is_ws)` at the current
4243/// builder insertion point, closing the task-reduction scope opened by the
4244/// `task` reduction modifier on a parallel or worksharing construct.
4245static void
4246emitTaskReductionModifierFini(bool isWorksharing, llvm::IRBuilderBase &builder,
4247 LLVM::ModuleTranslation &moduleTranslation) {
4248 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4249 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4250 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4251 uint32_t srcLocSize;
4252 llvm::Constant *srcLocStr =
4253 ompBuilder->getOrCreateSrcLocStr(ompLoc, srcLocSize);
4254 llvm::Value *ident = ompBuilder->getOrCreateIdent(srcLocStr, srcLocSize);
4255 ompBuilder->updateToLocation(ompLoc);
4256 llvm::Value *gtid = ompBuilder->getOrCreateThreadID(ident);
4257 llvm::FunctionCallee fini = ompBuilder->getOrCreateRuntimeFunction(
4258 *llvmModule, llvm::omp::OMPRTL___kmpc_task_reduction_modifier_fini);
4259 builder.CreateCall(fini,
4260 {ident, gtid, builder.getInt32(isWorksharing ? 1 : 0)});
4261}
4262
4263/// Converts an OpenMP taskgroup construct into LLVM IR using OpenMPIRBuilder.
4264static LogicalResult
4265convertOmpTaskgroupOp(omp::TaskgroupOp tgOp, llvm::IRBuilderBase &builder,
4266 LLVM::ModuleTranslation &moduleTranslation) {
4267 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
4268 if (failed(checkImplementationStatus(*tgOp)))
4269 return failure();
4270
4271 // Resolve and validate task_reduction declarations up front. We only handle
4272 // declare_reduction ops shaped like a non-byref scalar reduction in this
4273 // first cut; richer shapes (two-argument initializer, cleanup region,
4274 // missing combiner) require additional infrastructure.
4276 if (auto syms = tgOp.getTaskReductionSyms()) {
4277 redDecls.reserve(syms->size());
4278 for (auto sym : syms->getAsRange<SymbolRefAttr>()) {
4280 tgOp, sym);
4281 if (!decl)
4282 return tgOp.emitError()
4283 << "failed to resolve task_reduction declare_reduction symbol "
4284 << sym.getRootReference() << " in omp.taskgroup";
4285 if (decl.getInitializerRegion().front().getNumArguments() != 1)
4286 return tgOp.emitError("not yet implemented: task_reduction with "
4287 "two-argument initializer in omp.taskgroup");
4288 if (!decl.getCleanupRegion().empty())
4289 return tgOp.emitError("not yet implemented: task_reduction with "
4290 "cleanup region in omp.taskgroup");
4291 if (decl.getReductionRegion().empty())
4292 return tgOp.emitError("task_reduction declare_reduction is missing a "
4293 "combiner region");
4294 redDecls.push_back(decl);
4295 }
4296 }
4297
4298 auto bodyCB =
4299 [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
4300 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
4301 builder.restoreIP(codegenIP);
4302
4303 if (!redDecls.empty()) {
4305 origPtrs.reserve(redDecls.size());
4306 for (Value v : tgOp.getTaskReductionVars())
4307 origPtrs.push_back(moduleTranslation.lookupValue(v));
4308 if (!emitTaskReductionInitCall(redDecls, origPtrs, "__omp_taskred_",
4309 builder, allocaIP, moduleTranslation))
4310 return llvm::createStringError(
4311 llvm::inconvertibleErrorCode(),
4312 "failed to emit task_reduction initialization for omp.taskgroup");
4313 }
4314
4315 // Inside the taskgroup body, each task_reduction block argument refers to
4316 // the same shared/original storage that the runtime now knows about via
4317 // the descriptor array. Inner tasks that declare in_reduction look up
4318 // per-task private copies through the runtime; the taskgroup body itself
4319 // uses the original variable.
4320 for (auto [i, blockArg] :
4321 llvm::enumerate(tgOp.getRegion().getArguments())) {
4322 llvm::Value *orig =
4323 moduleTranslation.lookupValue(tgOp.getTaskReductionVars()[i]);
4324 moduleTranslation.mapValue(blockArg, orig);
4325 }
4326
4327 return convertOmpOpRegions(tgOp.getRegion(), "omp.taskgroup.region",
4328 builder, moduleTranslation)
4329 .takeError();
4330 };
4331
4333 InsertPointTy allocaIP =
4334 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
4335 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4336 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
4337 moduleTranslation.getOpenMPBuilder()->createTaskgroup(
4338 ompLoc, allocaIP, deallocBlocks, bodyCB);
4339
4340 if (failed(handleError(afterIP, *tgOp)))
4341 return failure();
4342
4343 builder.restoreIP(*afterIP);
4344 return success();
4345}
4346
4347static LogicalResult
4348convertOmpTaskwaitOp(omp::TaskwaitOp twOp, llvm::IRBuilderBase &builder,
4349 LLVM::ModuleTranslation &moduleTranslation) {
4350 if (failed(checkImplementationStatus(*twOp)))
4351 return failure();
4352
4353 moduleTranslation.getOpenMPBuilder()->createTaskwait(builder.saveIP());
4354 return success();
4355}
4356
4357/// Converts an OpenMP workshare loop into LLVM IR using OpenMPIRBuilder.
4358static LogicalResult
4359convertOmpWsloop(Operation &opInst, llvm::IRBuilderBase &builder,
4360 LLVM::ModuleTranslation &moduleTranslation) {
4361 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4362 auto wsloopOp = cast<omp::WsloopOp>(opInst);
4363 if (failed(checkImplementationStatus(opInst)))
4364 return failure();
4365
4366 auto loopOp = cast<omp::LoopNestOp>(wsloopOp.getWrappedLoop());
4367 llvm::ArrayRef<bool> isByRef = getIsByRef(wsloopOp.getReductionByref());
4368 assert(isByRef.size() == wsloopOp.getNumReductionVars());
4369
4370 // Static is the default.
4371 auto schedule =
4372 wsloopOp.getScheduleKind().value_or(omp::ClauseScheduleKind::Static);
4373
4374 // Find the loop configuration.
4375 llvm::Value *step = moduleTranslation.lookupValue(loopOp.getLoopSteps()[0]);
4376 llvm::Type *ivType = step->getType();
4377 llvm::Value *chunk = nullptr;
4378 if (wsloopOp.getScheduleChunk()) {
4379 llvm::Value *chunkVar =
4380 moduleTranslation.lookupValue(wsloopOp.getScheduleChunk());
4381 chunk = builder.CreateSExtOrTrunc(chunkVar, ivType);
4382 }
4383
4384 omp::DistributeOp distributeOp = nullptr;
4385 llvm::Value *distScheduleChunk = nullptr;
4386 bool hasDistSchedule = false;
4387 if (llvm::isa_and_present<omp::DistributeOp>(opInst.getParentOp())) {
4388 distributeOp = cast<omp::DistributeOp>(opInst.getParentOp());
4389 hasDistSchedule = distributeOp.getDistScheduleStatic();
4390 if (distributeOp.getDistScheduleChunkSize()) {
4391 llvm::Value *chunkVar = moduleTranslation.lookupValue(
4392 distributeOp.getDistScheduleChunkSize());
4393 distScheduleChunk = builder.CreateSExtOrTrunc(chunkVar, ivType);
4394 }
4395 }
4396
4397 PrivateVarsInfo privateVarsInfo(wsloopOp);
4398
4400 collectReductionDecls(wsloopOp, reductionDecls);
4401
4402 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
4403 findAllocInsertPoints(builder, moduleTranslation);
4404
4405 SmallVector<llvm::Value *> privateReductionVariables(
4406 wsloopOp.getNumReductionVars());
4407
4409 wsloopOp, builder, moduleTranslation, privateVarsInfo, allocaIP);
4410 if (handleError(afterAllocas, opInst).failed())
4411 return failure();
4412
4413 DenseMap<Value, llvm::Value *> reductionVariableMap;
4414
4415 MutableArrayRef<BlockArgument> reductionArgs =
4416 cast<omp::BlockArgOpenMPOpInterface>(opInst).getReductionBlockArgs();
4417
4418 SmallVector<DeferredStore> deferredStores;
4419
4420 if (failed(allocReductionVars(wsloopOp, reductionArgs, builder,
4421 moduleTranslation, allocaIP, reductionDecls,
4422 privateReductionVariables, reductionVariableMap,
4423 deferredStores, isByRef)))
4424 return failure();
4425
4426 if (handleError(initPrivateVars(builder, moduleTranslation, privateVarsInfo),
4427 opInst)
4428 .failed())
4429 return failure();
4430
4431 if (failed(copyFirstPrivateVars(
4432 wsloopOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
4433 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
4434 wsloopOp.getPrivateNeedsBarrier())))
4435 return failure();
4436
4437 assert(afterAllocas.get()->getSinglePredecessor());
4438 if (failed(initReductionVars(wsloopOp, reductionArgs, builder,
4439 moduleTranslation,
4440 afterAllocas.get()->getSinglePredecessor(),
4441 reductionDecls, privateReductionVariables,
4442 reductionVariableMap, isByRef, deferredStores)))
4443 return failure();
4444
4445 // For `reduction(task, ...)` open a task-reduction scope for the worksharing
4446 // loop. Participating explicit tasks accumulate into the per-thread private
4447 // copies, which the worksharing reduction then combines across threads.
4448 bool isTaskReductionMod =
4449 wsloopOp.getReductionMod() == omp::ReductionModifier::task &&
4450 wsloopOp.getNumReductionVars() > 0;
4451 if (isTaskReductionMod &&
4452 !emitTaskReductionInitCall(reductionDecls, privateReductionVariables,
4453 "__omp_taskred_mod_", builder, allocaIP,
4454 moduleTranslation, /*isModifier=*/true,
4455 /*isWorksharing=*/true))
4456 return wsloopOp.emitError(
4457 "failed to emit task reduction modifier initialization");
4458
4459 // TODO: Handle doacross loops when the ordered clause has a parameter.
4460 bool isOrdered = wsloopOp.getOrdered().has_value();
4461 std::optional<omp::ScheduleModifier> scheduleMod = wsloopOp.getScheduleMod();
4462 bool isSimd = wsloopOp.getScheduleSimd();
4463 bool loopNeedsBarrier = !wsloopOp.getNowait();
4464
4465 // The only legal way for the direct parent to be omp.distribute is that this
4466 // represents 'distribute parallel do'. Otherwise, this is a regular
4467 // worksharing loop.
4468 llvm::omp::WorksharingLoopType workshareLoopType =
4469 llvm::isa_and_present<omp::DistributeOp>(opInst.getParentOp())
4470 ? llvm::omp::WorksharingLoopType::DistributeForStaticLoop
4471 : llvm::omp::WorksharingLoopType::ForStaticLoop;
4472
4473 SmallVector<llvm::UncondBrInst *> cancelTerminators;
4474 pushCancelFinalizationCB(cancelTerminators, builder, *ompBuilder, wsloopOp,
4475 llvm::omp::Directive::OMPD_for);
4476
4477 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4478
4479 // Initialize linear variables and linear step
4480 LinearClauseProcessor linearClauseProcessor;
4481
4482 if (!wsloopOp.getLinearVars().empty()) {
4483 auto linearVarTypes = wsloopOp.getLinearVarTypes().value();
4484 for (mlir::Attribute linearVarType : linearVarTypes)
4485 linearClauseProcessor.registerType(moduleTranslation, linearVarType);
4486
4487 for (auto [idx, linearVar] : llvm::enumerate(wsloopOp.getLinearVars()))
4488 linearClauseProcessor.createLinearVar(
4489 builder, moduleTranslation, moduleTranslation.lookupValue(linearVar),
4490 idx);
4491 for (mlir::Value linearStep : wsloopOp.getLinearStepVars())
4492 linearClauseProcessor.initLinearStep(moduleTranslation, linearStep);
4493 }
4494
4496 wsloopOp.getRegion(), "omp.wsloop.region", builder, moduleTranslation);
4497
4498 if (failed(handleError(regionBlock, opInst)))
4499 return failure();
4500
4501 llvm::CanonicalLoopInfo *loopInfo = findCurrentLoopInfo(moduleTranslation);
4502
4503 // Emit Initialization and Update IR for linear variables
4504 if (!wsloopOp.getLinearVars().empty()) {
4505 linearClauseProcessor.initLinearVar(builder, moduleTranslation,
4506 loopInfo->getPreheader());
4507 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterBarrierIP =
4508 moduleTranslation.getOpenMPBuilder()->createBarrier(
4509 builder.saveIP(), llvm::omp::OMPD_barrier);
4510 if (failed(handleError(afterBarrierIP, *loopOp)))
4511 return failure();
4512 builder.restoreIP(*afterBarrierIP);
4513 linearClauseProcessor.updateLinearVar(builder, loopInfo->getBody(),
4514 loopInfo->getIndVar());
4515 linearClauseProcessor.splitLinearFiniBB(builder, loopInfo->getExit());
4516 }
4517
4518 builder.SetInsertPoint(*regionBlock, (*regionBlock)->begin());
4519
4520 // Check if we can generate no-loop kernel
4521 bool noLoopMode = false;
4522 omp::TargetOp targetOp = wsloopOp->getParentOfType<mlir::omp::TargetOp>();
4523 if (targetOp &&
4524 targetOp.getKernelType() == omp::TargetExecMode::spmd_no_loop) {
4525 Operation *targetCapturedOp =
4526 cast<omp::ComposableOpInterface>(*targetOp).findCapturedOp();
4527 // We need this check because, without it, noLoopMode would be set to true
4528 // for every omp.wsloop nested inside a no-loop SPMD target region, even if
4529 // that loop is not the top-level SPMD one.
4530 if (loopOp == targetCapturedOp)
4531 noLoopMode = true;
4532 }
4533
4534 for (size_t index = 0; index < wsloopOp.getLinearVars().size(); index++)
4535 linearClauseProcessor.rewriteInPlace(builder, loopInfo->getBody(),
4536 loopInfo->getLatch(), index);
4537
4538 llvm::OpenMPIRBuilder::InsertPointOrErrorTy wsloopIP =
4539 ompBuilder->applyWorkshareLoop(
4540 ompLoc.DL, loopInfo, allocaIP, loopNeedsBarrier,
4541 convertToScheduleKind(schedule), chunk, isSimd,
4542 scheduleMod == omp::ScheduleModifier::monotonic,
4543 scheduleMod == omp::ScheduleModifier::nonmonotonic, isOrdered,
4544 workshareLoopType, noLoopMode, hasDistSchedule, distScheduleChunk);
4545
4546 if (failed(handleError(wsloopIP, opInst)))
4547 return failure();
4548
4549 // Emit finalization and in-place rewrites for linear vars.
4550 if (!wsloopOp.getLinearVars().empty()) {
4551 llvm::OpenMPIRBuilder::InsertPointTy oldIP = builder.saveIP();
4552 assert(loopInfo->getLastIter() &&
4553 "`lastiter` in CanonicalLoopInfo is nullptr");
4554 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterBarrierIP =
4555 linearClauseProcessor.finalizeLinearVar(builder, moduleTranslation,
4556 loopInfo->getLastIter());
4557 if (failed(handleError(afterBarrierIP, *loopOp)))
4558 return failure();
4559
4560 builder.restoreIP(oldIP);
4561 }
4562
4563 // Set the correct branch target for task cancellation
4564 popCancelFinalizationCB(cancelTerminators, *ompBuilder, wsloopIP.get());
4565
4566 // Close the task-reduction scope before the worksharing reduction combine.
4567 if (isTaskReductionMod)
4568 emitTaskReductionModifierFini(/*isWorksharing=*/true, builder,
4569 moduleTranslation);
4570
4571 // Process the reductions if required.
4572 if (failed(createReductionsAndCleanup(
4573 wsloopOp, builder, moduleTranslation, allocaIP, reductionDecls,
4574 privateReductionVariables, isByRef, wsloopOp.getNowait(),
4575 /*isTeamsReduction=*/false)))
4576 return failure();
4577
4578 return cleanupPrivateVars(wsloopOp, builder, moduleTranslation,
4579 wsloopOp.getLoc(), privateVarsInfo);
4580}
4581
4582/// Converts the OpenMP parallel operation to LLVM IR.
4583static LogicalResult
4584convertOmpParallel(omp::ParallelOp opInst, llvm::IRBuilderBase &builder,
4585 LLVM::ModuleTranslation &moduleTranslation) {
4586 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
4587 ArrayRef<bool> isByRef = getIsByRef(opInst.getReductionByref());
4588 assert(isByRef.size() == opInst.getNumReductionVars());
4589 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4590 bool isCancellable = constructIsCancellable(opInst);
4591
4592 if (failed(checkImplementationStatus(*opInst)))
4593 return failure();
4594
4595 PrivateVarsInfo privateVarsInfo(opInst);
4596
4597 // Collect reduction declarations
4599 collectReductionDecls(opInst, reductionDecls);
4600 SmallVector<llvm::Value *> privateReductionVariables(
4601 opInst.getNumReductionVars());
4602 SmallVector<DeferredStore> deferredStores;
4603 // Only open a task-reduction scope when the `task` modifier is present and
4604 // there are reduction variables to combine; otherwise the matching fini in
4605 // the reduction-combine path (guarded by getNumReductionVars() > 0) would be
4606 // skipped, leaving the modifier init unbalanced.
4607 bool isTaskReductionMod =
4608 opInst.getReductionMod() == omp::ReductionModifier::task &&
4609 opInst.getNumReductionVars() > 0;
4610
4611 auto bodyGenCB =
4612 [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
4613 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
4615 opInst, builder, moduleTranslation, privateVarsInfo, allocaIP);
4616 if (handleError(afterAllocas, *opInst).failed())
4617 return llvm::make_error<PreviouslyReportedError>();
4618
4619 // Allocate reduction vars
4620 DenseMap<Value, llvm::Value *> reductionVariableMap;
4621
4622 MutableArrayRef<BlockArgument> reductionArgs =
4623 cast<omp::BlockArgOpenMPOpInterface>(*opInst).getReductionBlockArgs();
4624
4625 allocaIP =
4626 InsertPointTy(allocaIP.getBlock(),
4627 allocaIP.getBlock()->getTerminator()->getIterator());
4628
4629 if (failed(allocReductionVars(
4630 opInst, reductionArgs, builder, moduleTranslation, allocaIP,
4631 reductionDecls, privateReductionVariables, reductionVariableMap,
4632 deferredStores, isByRef)))
4633 return llvm::make_error<PreviouslyReportedError>();
4634
4635 assert(afterAllocas.get()->getSinglePredecessor());
4636 builder.restoreIP(codeGenIP);
4637
4638 if (handleError(
4639 initPrivateVars(builder, moduleTranslation, privateVarsInfo),
4640 *opInst)
4641 .failed())
4642 return llvm::make_error<PreviouslyReportedError>();
4643
4644 if (failed(copyFirstPrivateVars(
4645 opInst, builder, moduleTranslation, privateVarsInfo.mlirVars,
4646 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
4647 opInst.getPrivateNeedsBarrier())))
4648 return llvm::make_error<PreviouslyReportedError>();
4649
4650 if (failed(
4651 initReductionVars(opInst, reductionArgs, builder, moduleTranslation,
4652 afterAllocas.get()->getSinglePredecessor(),
4653 reductionDecls, privateReductionVariables,
4654 reductionVariableMap, isByRef, deferredStores)))
4655 return llvm::make_error<PreviouslyReportedError>();
4656
4657 // For `reduction(task, ...)` open a task-reduction scope so participating
4658 // explicit tasks accumulate into the per-thread private copies; the
4659 // parallel reduction then combines those copies across the team.
4660 if (isTaskReductionMod &&
4661 !emitTaskReductionInitCall(reductionDecls, privateReductionVariables,
4662 "__omp_taskred_mod_", builder, allocaIP,
4663 moduleTranslation, /*isModifier=*/true,
4664 /*isWorksharing=*/false))
4665 return llvm::createStringError(
4666 "failed to emit task reduction modifier initialization");
4667
4668 // Save the alloca insertion point on ModuleTranslation stack for use in
4669 // nested regions.
4671 moduleTranslation, allocaIP, deallocBlocks);
4672
4673 // ParallelOp has only one region associated with it.
4675 opInst.getRegion(), "omp.par.region", builder, moduleTranslation);
4676 if (!regionBlock)
4677 return regionBlock.takeError();
4678
4679 // Process the reductions if required.
4680 if (opInst.getNumReductionVars() > 0) {
4681 // Collect reduction info
4683 SmallVector<OwningAtomicReductionGen> owningAtomicReductionGens;
4685 owningReductionGenRefDataPtrGens;
4687 collectReductionInfo(opInst, builder, moduleTranslation, reductionDecls,
4688 owningReductionGens, owningAtomicReductionGens,
4689 owningReductionGenRefDataPtrGens,
4690 privateReductionVariables, reductionInfos, isByRef);
4691
4692 // Move to region cont block
4693 builder.SetInsertPoint((*regionBlock)->getTerminator());
4694
4695 // Close the task-reduction scope before the per-thread reduction
4696 // contributions are combined across the team.
4697 if (isTaskReductionMod)
4698 emitTaskReductionModifierFini(/*isWorksharing=*/false, builder,
4699 moduleTranslation);
4700
4701 // Generate reductions from info
4702 llvm::UnreachableInst *tempTerminator = builder.CreateUnreachable();
4703 builder.SetInsertPoint(tempTerminator);
4704
4705 llvm::OpenMPIRBuilder::InsertPointOrErrorTy contInsertPoint =
4706 ompBuilder->createReductions(
4707 builder.saveIP(), allocaIP, reductionInfos, isByRef,
4708 /*IsNoWait=*/false, /*IsTeamsReduction=*/false);
4709 if (!contInsertPoint)
4710 return contInsertPoint.takeError();
4711
4712 if (!contInsertPoint->getBlock())
4713 return llvm::make_error<PreviouslyReportedError>();
4714
4715 tempTerminator->eraseFromParent();
4716 builder.restoreIP(*contInsertPoint);
4717 }
4718
4719 return llvm::Error::success();
4720 };
4721
4722 auto privCB = [](InsertPointTy allocaIP, InsertPointTy codeGenIP,
4723 llvm::Value &, llvm::Value &val, llvm::Value *&replVal) {
4724 // tell OpenMPIRBuilder not to do anything. We handled Privatisation in
4725 // bodyGenCB.
4726 replVal = &val;
4727 return codeGenIP;
4728 };
4729
4730 // TODO: Perform finalization actions for variables. This has to be
4731 // called for variables which have destructors/finalizers.
4732 auto finiCB = [&](InsertPointTy codeGenIP) -> llvm::Error {
4733 InsertPointTy oldIP = builder.saveIP();
4734 builder.restoreIP(codeGenIP);
4735
4736 // if the reduction has a cleanup region, inline it here to finalize the
4737 // reduction variables
4738 SmallVector<Region *> reductionCleanupRegions;
4739 llvm::transform(reductionDecls, std::back_inserter(reductionCleanupRegions),
4740 [](omp::DeclareReductionOp reductionDecl) {
4741 return &reductionDecl.getCleanupRegion();
4742 });
4743 if (failed(inlineOmpRegionCleanup(
4744 reductionCleanupRegions, privateReductionVariables,
4745 moduleTranslation, builder, "omp.reduction.cleanup")))
4746 return llvm::createStringError(
4747 "failed to inline `cleanup` region of `omp.declare_reduction`");
4748
4749 if (failed(cleanupPrivateVars(opInst, builder, moduleTranslation,
4750 opInst.getLoc(), privateVarsInfo)))
4751 return llvm::make_error<PreviouslyReportedError>();
4752
4753 // If we could be performing cancellation, add the cancellation barrier on
4754 // the way out of the outlined region.
4755 if (isCancellable) {
4756 auto IPOrErr = ompBuilder->createBarrier(
4757 llvm::OpenMPIRBuilder::LocationDescription(builder),
4758 llvm::omp::Directive::OMPD_unknown,
4759 /* ForceSimpleCall */ false,
4760 /* CheckCancelFlag */ false);
4761 if (!IPOrErr)
4762 return IPOrErr.takeError();
4763 }
4764
4765 builder.restoreIP(oldIP);
4766 return llvm::Error::success();
4767 };
4768
4769 llvm::Value *ifCond = nullptr;
4770 if (auto ifVar = opInst.getIfExpr())
4771 ifCond = moduleTranslation.lookupValue(ifVar);
4772 llvm::Value *numThreads = nullptr;
4773 if (!opInst.getNumThreadsVars().empty())
4774 numThreads = moduleTranslation.lookupValue(opInst.getNumThreads(0));
4775 auto pbKind = llvm::omp::OMP_PROC_BIND_default;
4776 if (auto bind = opInst.getProcBindKind())
4777 pbKind = getProcBindKind(*bind);
4778
4780 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
4781 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
4782 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4783
4784 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
4785 ompBuilder->createParallel(ompLoc, allocaIP, deallocBlocks, bodyGenCB,
4786 privCB, finiCB, ifCond, numThreads, pbKind,
4787 isCancellable);
4788
4789 if (failed(handleError(afterIP, *opInst)))
4790 return failure();
4791
4792 builder.restoreIP(*afterIP);
4793 return success();
4794}
4795
4796/// Convert Order attribute to llvm::omp::OrderKind.
4797static llvm::omp::OrderKind
4798convertOrderKind(std::optional<omp::ClauseOrderKind> o) {
4799 if (!o)
4800 return llvm::omp::OrderKind::OMP_ORDER_unknown;
4801 switch (*o) {
4802 case omp::ClauseOrderKind::Concurrent:
4803 return llvm::omp::OrderKind::OMP_ORDER_concurrent;
4804 }
4805 llvm_unreachable("Unknown ClauseOrderKind kind");
4806}
4807
4808/// Converts an OpenMP simd loop into LLVM IR using OpenMPIRBuilder.
4809static LogicalResult
4810convertOmpSimd(Operation &opInst, llvm::IRBuilderBase &builder,
4811 LLVM::ModuleTranslation &moduleTranslation) {
4812 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
4813 auto simdOp = cast<omp::SimdOp>(opInst);
4814
4815 if (failed(checkImplementationStatus(opInst)))
4816 return failure();
4817
4818 PrivateVarsInfo privateVarsInfo(simdOp);
4819
4820 MutableArrayRef<BlockArgument> reductionArgs =
4821 cast<omp::BlockArgOpenMPOpInterface>(opInst).getReductionBlockArgs();
4822 DenseMap<Value, llvm::Value *> reductionVariableMap;
4823 SmallVector<llvm::Value *> privateReductionVariables(
4824 simdOp.getNumReductionVars());
4825 SmallVector<DeferredStore> deferredStores;
4827 collectReductionDecls(simdOp, reductionDecls);
4828 llvm::ArrayRef<bool> isByRef = getIsByRef(simdOp.getReductionByref());
4829 assert(isByRef.size() == simdOp.getNumReductionVars());
4830
4831 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
4832 findAllocInsertPoints(builder, moduleTranslation);
4833
4835 simdOp, builder, moduleTranslation, privateVarsInfo, allocaIP);
4836 if (handleError(afterAllocas, opInst).failed())
4837 return failure();
4838
4839 // Initialize linear variables and linear step
4840 LinearClauseProcessor linearClauseProcessor;
4841
4842 if (!simdOp.getLinearVars().empty()) {
4843 auto linearVarTypes = simdOp.getLinearVarTypes().value();
4844 for (mlir::Attribute linearVarType : linearVarTypes)
4845 linearClauseProcessor.registerType(moduleTranslation, linearVarType);
4846 for (auto [idx, linearVar] : llvm::enumerate(simdOp.getLinearVars())) {
4847 bool isImplicit = false;
4848 for (auto [mlirPrivVar, llvmPrivateVar] : llvm::zip_equal(
4849 privateVarsInfo.mlirVars, privateVarsInfo.llvmVars)) {
4850 // If the linear variable is implicit, reuse the already
4851 // existing llvm::Value
4852 if (linearVar == mlirPrivVar) {
4853 isImplicit = true;
4854 linearClauseProcessor.createLinearVar(builder, moduleTranslation,
4855 llvmPrivateVar, idx);
4856 break;
4857 }
4858 }
4859
4860 if (!isImplicit)
4861 linearClauseProcessor.createLinearVar(
4862 builder, moduleTranslation,
4863 moduleTranslation.lookupValue(linearVar), idx);
4864 }
4865 for (mlir::Value linearStep : simdOp.getLinearStepVars())
4866 linearClauseProcessor.initLinearStep(moduleTranslation, linearStep);
4867 }
4868
4869 if (failed(allocReductionVars(simdOp, reductionArgs, builder,
4870 moduleTranslation, allocaIP, reductionDecls,
4871 privateReductionVariables, reductionVariableMap,
4872 deferredStores, isByRef)))
4873 return failure();
4874
4875 if (handleError(initPrivateVars(builder, moduleTranslation, privateVarsInfo),
4876 opInst)
4877 .failed())
4878 return failure();
4879
4880 // No call to copyFirstPrivateVars because FIRSTPRIVATE is not allowed for
4881 // SIMD.
4882
4883 assert(afterAllocas.get()->getSinglePredecessor());
4884 if (failed(initReductionVars(simdOp, reductionArgs, builder,
4885 moduleTranslation,
4886 afterAllocas.get()->getSinglePredecessor(),
4887 reductionDecls, privateReductionVariables,
4888 reductionVariableMap, isByRef, deferredStores)))
4889 return failure();
4890
4891 llvm::ConstantInt *simdlen = nullptr;
4892 if (std::optional<uint64_t> simdlenVar = simdOp.getSimdlen())
4893 simdlen = builder.getInt64(simdlenVar.value());
4894
4895 llvm::ConstantInt *safelen = nullptr;
4896 if (std::optional<uint64_t> safelenVar = simdOp.getSafelen())
4897 safelen = builder.getInt64(safelenVar.value());
4898
4899 llvm::MapVector<llvm::Value *, llvm::Value *> alignedVars;
4900 llvm::omp::OrderKind order = convertOrderKind(simdOp.getOrder());
4901
4902 llvm::BasicBlock *sourceBlock = builder.GetInsertBlock();
4903 std::optional<ArrayAttr> alignmentValues = simdOp.getAlignments();
4904 mlir::OperandRange operands = simdOp.getAlignedVars();
4905 for (size_t i = 0; i < operands.size(); ++i) {
4906 llvm::Value *alignment = nullptr;
4907 llvm::Value *llvmVal = moduleTranslation.lookupValue(operands[i]);
4908 llvm::Type *ty = llvmVal->getType();
4909
4910 auto intAttr = cast<IntegerAttr>((*alignmentValues)[i]);
4911 alignment = builder.getInt64(intAttr.getInt());
4912 assert(ty->isPointerTy() && "Invalid type for aligned variable");
4913 assert(alignment && "Invalid alignment value");
4914
4915 // Check if the alignment value is not a power of 2. If so, skip emitting
4916 // alignment.
4917 if (!intAttr.getValue().isPowerOf2())
4918 continue;
4919
4920 auto curInsert = builder.saveIP();
4921 builder.SetInsertPoint(sourceBlock);
4922 llvmVal = builder.CreateLoad(ty, llvmVal);
4923 builder.restoreIP(curInsert);
4924 alignedVars[llvmVal] = alignment;
4925 }
4926
4928 simdOp.getRegion(), "omp.simd.region", builder, moduleTranslation);
4929
4930 if (failed(handleError(regionBlock, opInst)))
4931 return failure();
4932
4933 llvm::CanonicalLoopInfo *loopInfo = findCurrentLoopInfo(moduleTranslation);
4934 // Emit Initialization for linear variables
4935 if (simdOp.getLinearVars().size()) {
4936 linearClauseProcessor.initLinearVar(builder, moduleTranslation,
4937 loopInfo->getPreheader());
4938
4939 linearClauseProcessor.updateLinearVar(builder, loopInfo->getBody(),
4940 loopInfo->getIndVar());
4941 }
4942 builder.SetInsertPoint(*regionBlock, (*regionBlock)->begin());
4943
4944 for (size_t index = 0; index < simdOp.getLinearVars().size(); index++)
4945 linearClauseProcessor.rewriteInPlace(builder, loopInfo->getBody(),
4946 loopInfo->getLatch(), index);
4947
4948 ompBuilder->applySimd(loopInfo, alignedVars,
4949 simdOp.getIfExpr()
4950 ? moduleTranslation.lookupValue(simdOp.getIfExpr())
4951 : nullptr,
4952 order, simdlen, safelen);
4953
4954 linearClauseProcessor.emitStoresForLinearVar(builder);
4955
4956 // We now need to reduce the per-simd-lane reduction variable into the
4957 // original variable. This works a bit differently to other reductions (e.g.
4958 // wsloop) because we don't need to call into the OpenMP runtime to handle
4959 // threads: everything happened in this one thread.
4960 for (auto [i, tuple] : llvm::enumerate(
4961 llvm::zip(reductionDecls, isByRef, simdOp.getReductionVars(),
4962 privateReductionVariables))) {
4963 auto [decl, byRef, reductionVar, privateReductionVar] = tuple;
4964
4965 OwningReductionGen gen = makeReductionGen(decl, builder, moduleTranslation);
4966 llvm::Value *originalVariable = moduleTranslation.lookupValue(reductionVar);
4967 llvm::Type *reductionType = moduleTranslation.convertType(decl.getType());
4968
4969 // We have one less load for by-ref case because that load is now inside of
4970 // the reduction region.
4971 llvm::Value *redValue = originalVariable;
4972 if (!byRef)
4973 redValue =
4974 builder.CreateLoad(reductionType, redValue, "red.value." + Twine(i));
4975 llvm::Value *privateRedValue = builder.CreateLoad(
4976 reductionType, privateReductionVar, "red.private.value." + Twine(i));
4977 llvm::Value *reduced;
4978
4979 auto res = gen(builder.saveIP(), redValue, privateRedValue, reduced);
4980 if (failed(handleError(res, opInst)))
4981 return failure();
4982 builder.restoreIP(res.get());
4983
4984 // For by-ref case, the store is inside of the reduction region.
4985 if (!byRef)
4986 builder.CreateStore(reduced, originalVariable);
4987 }
4988
4989 // After the construct, deallocate private reduction variables.
4990 SmallVector<Region *> reductionRegions;
4991 llvm::transform(reductionDecls, std::back_inserter(reductionRegions),
4992 [](omp::DeclareReductionOp reductionDecl) {
4993 return &reductionDecl.getCleanupRegion();
4994 });
4995 if (failed(inlineOmpRegionCleanup(reductionRegions, privateReductionVariables,
4996 moduleTranslation, builder,
4997 "omp.reduction.cleanup")))
4998 return failure();
4999
5000 return cleanupPrivateVars(simdOp, builder, moduleTranslation, simdOp.getLoc(),
5001 privateVarsInfo);
5002}
5003
5004/// Converts an OpenMP loop nest into LLVM IR using OpenMPIRBuilder.
5005static LogicalResult
5006convertOmpLoopNest(Operation &opInst, llvm::IRBuilderBase &builder,
5007 LLVM::ModuleTranslation &moduleTranslation) {
5008 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5009 auto loopOp = cast<omp::LoopNestOp>(opInst);
5010
5011 if (failed(checkImplementationStatus(opInst)))
5012 return failure();
5013
5014 // Set up the source location value for OpenMP runtime.
5015 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5016
5017 // Generator of the canonical loop body.
5020 auto bodyGen = [&](llvm::OpenMPIRBuilder::InsertPointTy ip,
5021 llvm::Value *iv) -> llvm::Error {
5022 // Make sure further conversions know about the induction variable.
5023 moduleTranslation.mapValue(
5024 loopOp.getRegion().front().getArgument(loopInfos.size()), iv);
5025
5026 // Capture the body insertion point for use in nested loops. BodyIP of the
5027 // CanonicalLoopInfo always points to the beginning of the entry block of
5028 // the body.
5029 bodyInsertPoints.push_back(ip);
5030
5031 if (loopInfos.size() != loopOp.getNumLoops() - 1)
5032 return llvm::Error::success();
5033
5034 // Convert the body of the loop.
5035 builder.restoreIP(ip);
5037 loopOp.getRegion(), "omp.loop_nest.region", builder, moduleTranslation);
5038 if (!regionBlock)
5039 return regionBlock.takeError();
5040
5041 builder.SetInsertPoint(*regionBlock, (*regionBlock)->begin());
5042 return llvm::Error::success();
5043 };
5044
5045 // Delegate actual loop construction to the OpenMP IRBuilder.
5046 // TODO: this currently assumes omp.loop_nest is semantically similar to SCF
5047 // loop, i.e. it has a positive step, uses signed integer semantics.
5048 // Reconsider this code when the nested loop operation clearly supports more
5049 // cases.
5050 for (unsigned i = 0, e = loopOp.getNumLoops(); i < e; ++i) {
5051 llvm::Value *lowerBound =
5052 moduleTranslation.lookupValue(loopOp.getLoopLowerBounds()[i]);
5053 llvm::Value *upperBound =
5054 moduleTranslation.lookupValue(loopOp.getLoopUpperBounds()[i]);
5055 llvm::Value *step = moduleTranslation.lookupValue(loopOp.getLoopSteps()[i]);
5056
5057 // Make sure loop trip count are emitted in the preheader of the outermost
5058 // loop at the latest so that they are all available for the new collapsed
5059 // loop will be created below.
5060 llvm::OpenMPIRBuilder::LocationDescription loc = ompLoc;
5061 llvm::OpenMPIRBuilder::InsertPointTy computeIP = ompLoc.IP;
5062 if (i != 0) {
5063 loc = llvm::OpenMPIRBuilder::LocationDescription(bodyInsertPoints.back(),
5064 ompLoc.DL);
5065 computeIP = loopInfos.front()->getPreheaderIP();
5066 }
5067
5069 ompBuilder->createCanonicalLoop(
5070 loc, bodyGen, lowerBound, upperBound, step,
5071 /*IsSigned=*/true, loopOp.getLoopInclusive(), computeIP);
5072
5073 if (failed(handleError(loopResult, *loopOp)))
5074 return failure();
5075
5076 loopInfos.push_back(*loopResult);
5077 }
5078
5079 llvm::OpenMPIRBuilder::InsertPointTy afterIP =
5080 loopInfos.front()->getAfterIP();
5081
5082 // Do tiling.
5083 if (const auto &tiles = loopOp.getTileSizes()) {
5084 llvm::Type *ivType = loopInfos.front()->getIndVarType();
5086
5087 for (auto tile : tiles.value()) {
5088 llvm::Value *tileVal = llvm::ConstantInt::get(ivType, tile);
5089 tileSizes.push_back(tileVal);
5090 }
5091
5092 std::vector<llvm::CanonicalLoopInfo *> newLoops =
5093 ompBuilder->tileLoops(ompLoc.DL, loopInfos, tileSizes);
5094
5095 // Update afterIP to get the correct insertion point after
5096 // tiling.
5097 llvm::BasicBlock *afterBB = newLoops.front()->getAfter();
5098 llvm::BasicBlock *afterAfterBB = afterBB->getSingleSuccessor();
5099 afterIP = {afterAfterBB, afterAfterBB->begin()};
5100
5101 // Update the loop infos.
5102 loopInfos.clear();
5103 for (const auto &newLoop : newLoops)
5104 loopInfos.push_back(newLoop);
5105 } // Tiling done.
5106
5107 // Do collapse.
5108 const auto &numCollapse = loopOp.getCollapseNumLoops();
5110 loopInfos.begin(), loopInfos.begin() + (numCollapse));
5111
5112 auto newTopLoopInfo =
5113 ompBuilder->collapseLoops(ompLoc.DL, collapseLoopInfos, {});
5114
5115 assert(newTopLoopInfo && "New top loop information is missing");
5116 moduleTranslation.stackWalk<OpenMPLoopInfoStackFrame>(
5117 [&](OpenMPLoopInfoStackFrame &frame) {
5118 frame.loopInfo = newTopLoopInfo;
5119 return WalkResult::interrupt();
5120 });
5121
5122 // Continue building IR after the loop. Note that the LoopInfo returned by
5123 // `collapseLoops` points inside the outermost loop and is intended for
5124 // potential further loop transformations. Use the insertion point stored
5125 // before collapsing loops instead.
5126 builder.restoreIP(afterIP);
5127 return success();
5128}
5129
5130/// Convert an omp.canonical_loop to LLVM-IR
5131static LogicalResult
5132convertOmpCanonicalLoopOp(omp::CanonicalLoopOp op, llvm::IRBuilderBase &builder,
5133 LLVM::ModuleTranslation &moduleTranslation) {
5134 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5135
5136 llvm::OpenMPIRBuilder::LocationDescription loopLoc(builder);
5137 Value loopIV = op.getInductionVar();
5138 Value loopTC = op.getTripCount();
5139
5140 llvm::Value *llvmTC = moduleTranslation.lookupValue(loopTC);
5141
5143 ompBuilder->createCanonicalLoop(
5144 loopLoc,
5145 [&](llvm::OpenMPIRBuilder::InsertPointTy ip, llvm::Value *llvmIV) {
5146 // Register the mapping of MLIR induction variable to LLVM-IR
5147 // induction variable
5148 moduleTranslation.mapValue(loopIV, llvmIV);
5149
5150 builder.restoreIP(ip);
5152 convertOmpOpRegions(op.getRegion(), "omp.loop.region", builder,
5153 moduleTranslation);
5154
5155 return bodyGenStatus.takeError();
5156 },
5157 llvmTC, "omp.loop");
5158 if (!llvmOrError)
5159 return op.emitError(llvm::toString(llvmOrError.takeError()));
5160
5161 llvm::CanonicalLoopInfo *llvmCLI = *llvmOrError;
5162 llvm::IRBuilderBase::InsertPoint afterIP = llvmCLI->getAfterIP();
5163 builder.restoreIP(afterIP);
5164
5165 // Register the mapping of MLIR loop to LLVM-IR OpenMPIRBuilder loop
5166 if (Value cli = op.getCli())
5167 moduleTranslation.mapOmpLoop(cli, llvmCLI);
5168
5169 return success();
5170}
5171
5172/// Apply a `#pragma omp unroll` / "!$omp unroll" transformation using the
5173/// OpenMPIRBuilder.
5174static LogicalResult
5175applyUnrollHeuristic(omp::UnrollHeuristicOp op, llvm::IRBuilderBase &builder,
5176 LLVM::ModuleTranslation &moduleTranslation) {
5177 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5178
5179 Value applyee = op.getApplyee();
5180 assert(applyee && "Loop to apply unrolling on required");
5181
5182 llvm::CanonicalLoopInfo *consBuilderCLI =
5183 moduleTranslation.lookupOMPLoop(applyee);
5184 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5185 ompBuilder->unrollLoopHeuristic(loc.DL, consBuilderCLI);
5186
5187 moduleTranslation.invalidateOmpLoop(applyee);
5188 return success();
5189}
5190
5191/// Apply a `#pragma omp tile` / `!$omp tile` transformation using the
5192/// OpenMPIRBuilder.
5193static LogicalResult applyTile(omp::TileOp op, llvm::IRBuilderBase &builder,
5194 LLVM::ModuleTranslation &moduleTranslation) {
5195 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5196 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5197
5199 SmallVector<llvm::Value *> translatedSizes;
5200
5201 for (Value size : op.getSizes()) {
5202 llvm::Value *translatedSize = moduleTranslation.lookupValue(size);
5203 assert(translatedSize &&
5204 "sizes clause arguments must already be translated");
5205 translatedSizes.push_back(translatedSize);
5206 }
5207
5208 for (Value applyee : op.getApplyees()) {
5209 llvm::CanonicalLoopInfo *consBuilderCLI =
5210 moduleTranslation.lookupOMPLoop(applyee);
5211 assert(applyee && "Canonical loop must already been translated");
5212 translatedLoops.push_back(consBuilderCLI);
5213 }
5214
5215 auto generatedLoops =
5216 ompBuilder->tileLoops(loc.DL, translatedLoops, translatedSizes);
5217 if (!op.getGeneratees().empty()) {
5218 for (auto [mlirLoop, genLoop] :
5219 zip_equal(op.getGeneratees(), generatedLoops))
5220 moduleTranslation.mapOmpLoop(mlirLoop, genLoop);
5221 }
5222
5223 // CLIs can only be consumed once
5224 for (Value applyee : op.getApplyees())
5225 moduleTranslation.invalidateOmpLoop(applyee);
5226
5227 return success();
5228}
5229
5230/// Apply a `#pragma omp fuse` / `!$omp fuse` transformation using the
5231/// OpenMPIRBuilder.
5232static LogicalResult applyFuse(omp::FuseOp op, llvm::IRBuilderBase &builder,
5233 LLVM::ModuleTranslation &moduleTranslation) {
5234 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5235 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5236
5237 // Select what CLIs are going to be fused
5238 SmallVector<llvm::CanonicalLoopInfo *> beforeFuse, toFuse, afterFuse;
5239 for (size_t i = 0; i < op.getApplyees().size(); i++) {
5240 Value applyee = op.getApplyees()[i];
5241 llvm::CanonicalLoopInfo *consBuilderCLI =
5242 moduleTranslation.lookupOMPLoop(applyee);
5243 assert(applyee && "Canonical loop must already been translated");
5244 if (op.getFirst().has_value() && i < op.getFirst().value() - 1)
5245 beforeFuse.push_back(consBuilderCLI);
5246 else if (op.getCount().has_value() &&
5247 i >= op.getFirst().value() + op.getCount().value() - 1)
5248 afterFuse.push_back(consBuilderCLI);
5249 else
5250 toFuse.push_back(consBuilderCLI);
5251 }
5252 assert(
5253 (op.getGeneratees().empty() ||
5254 beforeFuse.size() + afterFuse.size() + 1 == op.getGeneratees().size()) &&
5255 "Wrong number of generatees");
5256
5257 // do the fuse
5258 auto generatedLoop = ompBuilder->fuseLoops(loc.DL, toFuse);
5259 if (!op.getGeneratees().empty()) {
5260 size_t i = 0;
5261 for (; i < beforeFuse.size(); i++)
5262 moduleTranslation.mapOmpLoop(op.getGeneratees()[i], beforeFuse[i]);
5263 moduleTranslation.mapOmpLoop(op.getGeneratees()[i++], generatedLoop);
5264 for (; i < afterFuse.size(); i++)
5265 moduleTranslation.mapOmpLoop(op.getGeneratees()[i], afterFuse[i]);
5266 }
5267
5268 // CLIs can only be consumed once
5269 for (Value applyee : op.getApplyees())
5270 moduleTranslation.invalidateOmpLoop(applyee);
5271
5272 return success();
5273}
5274
5275/// Convert an Atomic Ordering attribute to llvm::AtomicOrdering.
5276static llvm::AtomicOrdering
5277convertAtomicOrdering(std::optional<omp::ClauseMemoryOrderKind> ao) {
5278 if (!ao)
5279 return llvm::AtomicOrdering::Monotonic; // Default Memory Ordering
5280
5281 switch (*ao) {
5282 case omp::ClauseMemoryOrderKind::Seq_cst:
5283 return llvm::AtomicOrdering::SequentiallyConsistent;
5284 case omp::ClauseMemoryOrderKind::Acq_rel:
5285 return llvm::AtomicOrdering::AcquireRelease;
5286 case omp::ClauseMemoryOrderKind::Acquire:
5287 return llvm::AtomicOrdering::Acquire;
5288 case omp::ClauseMemoryOrderKind::Release:
5289 return llvm::AtomicOrdering::Release;
5290 case omp::ClauseMemoryOrderKind::Relaxed:
5291 return llvm::AtomicOrdering::Monotonic;
5292 }
5293 llvm_unreachable("Unknown ClauseMemoryOrderKind kind");
5294}
5295
5296/// Convert omp.atomic.read operation to LLVM IR.
5297static LogicalResult
5298convertOmpAtomicRead(Operation &opInst, llvm::IRBuilderBase &builder,
5299 LLVM::ModuleTranslation &moduleTranslation) {
5300 auto readOp = cast<omp::AtomicReadOp>(opInst);
5301 if (failed(checkImplementationStatus(opInst)))
5302 return failure();
5303
5304 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5305 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
5306 findAllocInsertPoints(builder, moduleTranslation);
5307
5308 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5309
5310 llvm::AtomicOrdering AO = convertAtomicOrdering(readOp.getMemoryOrder());
5311 llvm::Value *x = moduleTranslation.lookupValue(readOp.getX());
5312 llvm::Value *v = moduleTranslation.lookupValue(readOp.getV());
5313
5314 llvm::Type *elementType =
5315 moduleTranslation.convertType(readOp.getElementType());
5316
5317 llvm::OpenMPIRBuilder::AtomicOpValue V = {v, elementType, false, false};
5318 llvm::OpenMPIRBuilder::AtomicOpValue X = {x, elementType, false, false};
5319 builder.restoreIP(ompBuilder->createAtomicRead(ompLoc, X, V, AO, allocaIP));
5320 return success();
5321}
5322
5323/// Converts an omp.atomic.write operation to LLVM IR.
5324static LogicalResult
5325convertOmpAtomicWrite(Operation &opInst, llvm::IRBuilderBase &builder,
5326 LLVM::ModuleTranslation &moduleTranslation) {
5327 auto writeOp = cast<omp::AtomicWriteOp>(opInst);
5328 if (failed(checkImplementationStatus(opInst)))
5329 return failure();
5330
5331 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5332 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
5333 findAllocInsertPoints(builder, moduleTranslation);
5334
5335 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5336 llvm::AtomicOrdering ao = convertAtomicOrdering(writeOp.getMemoryOrder());
5337 llvm::Value *expr = moduleTranslation.lookupValue(writeOp.getExpr());
5338 llvm::Value *dest = moduleTranslation.lookupValue(writeOp.getX());
5339 llvm::Type *ty = moduleTranslation.convertType(writeOp.getExpr().getType());
5340 llvm::OpenMPIRBuilder::AtomicOpValue x = {dest, ty, /*isSigned=*/false,
5341 /*isVolatile=*/false};
5342 builder.restoreIP(
5343 ompBuilder->createAtomicWrite(ompLoc, x, expr, ao, allocaIP));
5344 return success();
5345}
5346
5347/// Converts an LLVM dialect binary operation to the corresponding enum value
5348/// for `atomicrmw` supported binary operation.
5349static llvm::AtomicRMWInst::BinOp convertBinOpToAtomic(Operation &op) {
5351 .Case([&](LLVM::AddOp) { return llvm::AtomicRMWInst::BinOp::Add; })
5352 .Case([&](LLVM::SubOp) { return llvm::AtomicRMWInst::BinOp::Sub; })
5353 .Case([&](LLVM::AndOp) { return llvm::AtomicRMWInst::BinOp::And; })
5354 .Case([&](LLVM::OrOp) { return llvm::AtomicRMWInst::BinOp::Or; })
5355 .Case([&](LLVM::XOrOp) { return llvm::AtomicRMWInst::BinOp::Xor; })
5356 .Case([&](LLVM::UMaxOp) { return llvm::AtomicRMWInst::BinOp::UMax; })
5357 .Case([&](LLVM::UMinOp) { return llvm::AtomicRMWInst::BinOp::UMin; })
5358 .Case([&](LLVM::FAddOp) { return llvm::AtomicRMWInst::BinOp::FAdd; })
5359 .Case([&](LLVM::FSubOp) { return llvm::AtomicRMWInst::BinOp::FSub; })
5360 .Default(llvm::AtomicRMWInst::BinOp::BAD_BINOP);
5361}
5362
5363static void extractAtomicControlFlags(omp::AtomicUpdateOp atomicUpdateOp,
5364 bool &isIgnoreDenormalMode,
5365 bool &isFineGrainedMemory,
5366 bool &isRemoteMemory) {
5367 isIgnoreDenormalMode = false;
5368 isFineGrainedMemory = false;
5369 isRemoteMemory = false;
5370 if (atomicUpdateOp &&
5371 atomicUpdateOp->hasAttr(atomicUpdateOp.getAtomicControlAttrName())) {
5372 mlir::omp::AtomicControlAttr atomicControlAttr =
5373 atomicUpdateOp.getAtomicControlAttr();
5374 isIgnoreDenormalMode = atomicControlAttr.getIgnoreDenormalMode();
5375 isFineGrainedMemory = atomicControlAttr.getFineGrainedMemory();
5376 isRemoteMemory = atomicControlAttr.getRemoteMemory();
5377 }
5378}
5379
5380/// Converts an OpenMP atomic update operation using OpenMPIRBuilder.
5381static LogicalResult
5382convertOmpAtomicUpdate(omp::AtomicUpdateOp &opInst,
5383 llvm::IRBuilderBase &builder,
5384 LLVM::ModuleTranslation &moduleTranslation) {
5385 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5386 if (failed(checkImplementationStatus(*opInst)))
5387 return failure();
5388
5389 // Convert values and types.
5390 auto &innerOpList = opInst.getRegion().front().getOperations();
5391 bool isXBinopExpr{false};
5392 llvm::AtomicRMWInst::BinOp binop;
5393 mlir::Value mlirExpr;
5394 llvm::Value *llvmExpr = nullptr;
5395 llvm::Value *llvmX = nullptr;
5396 llvm::Type *llvmXElementType = nullptr;
5397 if (innerOpList.size() == 2) {
5398 // The two operations here are the update and the terminator.
5399 // Since we can identify the update operation, there is a possibility
5400 // that we can generate the atomicrmw instruction.
5401 mlir::Operation &innerOp = *opInst.getRegion().front().begin();
5402 if (!llvm::is_contained(innerOp.getOperands(),
5403 opInst.getRegion().getArgument(0))) {
5404 return opInst.emitError("no atomic update operation with region argument"
5405 " as operand found inside atomic.update region");
5406 }
5407 binop = convertBinOpToAtomic(innerOp);
5408 isXBinopExpr = innerOp.getOperand(0) == opInst.getRegion().getArgument(0);
5409 mlirExpr = (isXBinopExpr ? innerOp.getOperand(1) : innerOp.getOperand(0));
5410 llvmExpr = moduleTranslation.lookupValue(mlirExpr);
5411 } else {
5412 // Since the update region includes more than one operation
5413 // we will resort to generating a cmpxchg loop.
5414 binop = llvm::AtomicRMWInst::BinOp::BAD_BINOP;
5415 }
5416 llvmX = moduleTranslation.lookupValue(opInst.getX());
5417 llvmXElementType = moduleTranslation.convertType(
5418 opInst.getRegion().getArgument(0).getType());
5419 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {llvmX, llvmXElementType,
5420 /*isSigned=*/false,
5421 /*isVolatile=*/false};
5422
5423 llvm::AtomicOrdering atomicOrdering =
5424 convertAtomicOrdering(opInst.getMemoryOrder());
5425
5426 // Generate update code.
5427 auto updateFn =
5428 [&opInst, &moduleTranslation](
5429 llvm::Value *atomicx,
5430 llvm::IRBuilder<> &builder) -> llvm::Expected<llvm::Value *> {
5431 Block &bb = *opInst.getRegion().begin();
5432 moduleTranslation.mapValue(*opInst.getRegion().args_begin(), atomicx);
5433 moduleTranslation.mapBlock(&bb, builder.GetInsertBlock());
5434 if (failed(moduleTranslation.convertBlock(bb, true, builder)))
5435 return llvm::make_error<PreviouslyReportedError>();
5436
5437 omp::YieldOp yieldop = dyn_cast<omp::YieldOp>(bb.getTerminator());
5438 assert(yieldop && yieldop.getResults().size() == 1 &&
5439 "terminator must be omp.yield op and it must have exactly one "
5440 "argument");
5441 return moduleTranslation.lookupValue(yieldop.getResults()[0]);
5442 };
5443
5444 bool isIgnoreDenormalMode;
5445 bool isFineGrainedMemory;
5446 bool isRemoteMemory;
5447 extractAtomicControlFlags(opInst, isIgnoreDenormalMode, isFineGrainedMemory,
5448 isRemoteMemory);
5449 // Handle ambiguous alloca, if any.
5450 auto allocaIP = findAllocInsertPoints(builder, moduleTranslation);
5451 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5452 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
5453 ompBuilder->createAtomicUpdate(ompLoc, allocaIP, llvmAtomicX, llvmExpr,
5454 atomicOrdering, binop, updateFn,
5455 isXBinopExpr, isIgnoreDenormalMode,
5456 isFineGrainedMemory, isRemoteMemory);
5457
5458 if (failed(handleError(afterIP, *opInst)))
5459 return failure();
5460
5461 builder.restoreIP(*afterIP);
5462 return success();
5463}
5464
5465static LogicalResult
5466convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
5467 llvm::IRBuilderBase &builder,
5468 LLVM::ModuleTranslation &moduleTranslation) {
5469 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5470 if (failed(checkImplementationStatus(*atomicCaptureOp)))
5471 return failure();
5472
5473 mlir::Value mlirExpr;
5474 bool isXBinopExpr = false, isPostfixUpdate = false;
5475 llvm::AtomicRMWInst::BinOp binop = llvm::AtomicRMWInst::BinOp::BAD_BINOP;
5476
5477 omp::AtomicUpdateOp atomicUpdateOp = atomicCaptureOp.getAtomicUpdateOp();
5478 omp::AtomicWriteOp atomicWriteOp = atomicCaptureOp.getAtomicWriteOp();
5479
5480 assert((atomicUpdateOp || atomicWriteOp) &&
5481 "internal op must be an atomic.update or atomic.write op");
5482
5483 if (atomicWriteOp) {
5484 isPostfixUpdate = true;
5485 mlirExpr = atomicWriteOp.getExpr();
5486 } else {
5487 isPostfixUpdate = atomicCaptureOp.getSecondOp() ==
5488 atomicCaptureOp.getAtomicUpdateOp().getOperation();
5489 auto &innerOpList = atomicUpdateOp.getRegion().front().getOperations();
5490 // Find the binary update operation that uses the region argument
5491 // and get the expression to update
5492 if (innerOpList.size() == 2) {
5493 mlir::Operation &innerOp = *atomicUpdateOp.getRegion().front().begin();
5494 if (!llvm::is_contained(innerOp.getOperands(),
5495 atomicUpdateOp.getRegion().getArgument(0))) {
5496 return atomicUpdateOp.emitError(
5497 "no atomic update operation with region argument"
5498 " as operand found inside atomic.update region");
5499 }
5500 binop = convertBinOpToAtomic(innerOp);
5501 isXBinopExpr =
5502 innerOp.getOperand(0) == atomicUpdateOp.getRegion().getArgument(0);
5503 mlirExpr = (isXBinopExpr ? innerOp.getOperand(1) : innerOp.getOperand(0));
5504 } else {
5505 binop = llvm::AtomicRMWInst::BinOp::BAD_BINOP;
5506 }
5507 }
5508
5509 llvm::Value *llvmExpr = moduleTranslation.lookupValue(mlirExpr);
5510 llvm::Value *llvmX =
5511 moduleTranslation.lookupValue(atomicCaptureOp.getAtomicReadOp().getX());
5512 llvm::Value *llvmV =
5513 moduleTranslation.lookupValue(atomicCaptureOp.getAtomicReadOp().getV());
5514 llvm::Type *llvmXElementType = moduleTranslation.convertType(
5515 atomicCaptureOp.getAtomicReadOp().getElementType());
5516 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {llvmX, llvmXElementType,
5517 /*isSigned=*/false,
5518 /*isVolatile=*/false};
5519 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicV = {llvmV, llvmXElementType,
5520 /*isSigned=*/false,
5521 /*isVolatile=*/false};
5522
5523 llvm::AtomicOrdering atomicOrdering =
5524 convertAtomicOrdering(atomicCaptureOp.getMemoryOrder());
5525
5526 auto updateFn =
5527 [&](llvm::Value *atomicx,
5528 llvm::IRBuilder<> &builder) -> llvm::Expected<llvm::Value *> {
5529 if (atomicWriteOp)
5530 return moduleTranslation.lookupValue(atomicWriteOp.getExpr());
5531 Block &bb = *atomicUpdateOp.getRegion().begin();
5532 moduleTranslation.mapValue(*atomicUpdateOp.getRegion().args_begin(),
5533 atomicx);
5534 moduleTranslation.mapBlock(&bb, builder.GetInsertBlock());
5535 if (failed(moduleTranslation.convertBlock(bb, true, builder)))
5536 return llvm::make_error<PreviouslyReportedError>();
5537
5538 omp::YieldOp yieldop = dyn_cast<omp::YieldOp>(bb.getTerminator());
5539 assert(yieldop && yieldop.getResults().size() == 1 &&
5540 "terminator must be omp.yield op and it must have exactly one "
5541 "argument");
5542 return moduleTranslation.lookupValue(yieldop.getResults()[0]);
5543 };
5544
5545 bool isIgnoreDenormalMode;
5546 bool isFineGrainedMemory;
5547 bool isRemoteMemory;
5548 extractAtomicControlFlags(atomicUpdateOp, isIgnoreDenormalMode,
5549 isFineGrainedMemory, isRemoteMemory);
5550 // Handle ambiguous alloca, if any.
5551 auto allocaIP = findAllocInsertPoints(builder, moduleTranslation);
5552 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5553 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
5554 ompBuilder->createAtomicCapture(
5555 ompLoc, allocaIP, llvmAtomicX, llvmAtomicV, llvmExpr, atomicOrdering,
5556 binop, updateFn, atomicUpdateOp, isPostfixUpdate, isXBinopExpr,
5557 isIgnoreDenormalMode, isFineGrainedMemory, isRemoteMemory);
5558
5559 if (failed(handleError(afterIP, *atomicCaptureOp)))
5560 return failure();
5561
5562 builder.restoreIP(*afterIP);
5563 return success();
5564}
5565
5566/// Helper to extract the OMPAtomicCompareOp from an integer comparison
5567/// predicate. Returns std::nullopt for unsupported predicates.
5568static std::optional<llvm::omp::OMPAtomicCompareOp>
5569convertICmpPredicateToAtomicCompareOp(LLVM::ICmpPredicate predicate) {
5570 switch (predicate) {
5571 case LLVM::ICmpPredicate::eq:
5572 return llvm::omp::OMPAtomicCompareOp::EQ;
5573 case LLVM::ICmpPredicate::slt:
5574 case LLVM::ICmpPredicate::ult:
5575 return llvm::omp::OMPAtomicCompareOp::MIN;
5576 case LLVM::ICmpPredicate::sgt:
5577 case LLVM::ICmpPredicate::ugt:
5578 return llvm::omp::OMPAtomicCompareOp::MAX;
5579 default:
5580 return std::nullopt;
5581 }
5582}
5583
5584/// Helper to extract the OMPAtomicCompareOp from a floating-point comparison
5585/// predicate. Returns std::nullopt for unsupported predicates.
5586static std::optional<llvm::omp::OMPAtomicCompareOp>
5587convertFCmpPredicateToAtomicCompareOp(LLVM::FCmpPredicate predicate) {
5588 switch (predicate) {
5589 case LLVM::FCmpPredicate::oeq:
5590 case LLVM::FCmpPredicate::ueq:
5591 return llvm::omp::OMPAtomicCompareOp::EQ;
5592 case LLVM::FCmpPredicate::olt:
5593 case LLVM::FCmpPredicate::ult:
5594 return llvm::omp::OMPAtomicCompareOp::MIN;
5595 case LLVM::FCmpPredicate::ogt:
5596 case LLVM::FCmpPredicate::ugt:
5597 return llvm::omp::OMPAtomicCompareOp::MAX;
5598 default:
5599 return std::nullopt;
5600 }
5601}
5602
5603/// Converts an omp.atomic.compare operation to LLVM IR.
5604///
5605/// if (x == e) x = d
5606/// The region contains a comparison + select pattern:
5607/// ^bb0(%xval: T):
5608/// %cmp = llvm.icmp/fcmp <pred> %xval, %e : T
5609/// %sel = llvm.select %cmp, %d, %xval : i1, T
5610/// omp.yield(%sel : T)
5611///
5612/// From MLIR extract:
5613/// 1) comparison operator
5614/// 2) expected value (e)
5615/// 3) desired value (d)
5616/// These are passed to OpenMPIRBuilder::createAtomicCompare which generates
5617/// the actual cmpxchg / atomicrmw instruction.
5618///
5619static LogicalResult
5620convertOmpAtomicCompare(omp::AtomicCompareOp atomicCompareOp,
5621 llvm::IRBuilderBase &builder,
5622 LLVM::ModuleTranslation &moduleTranslation) {
5623 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5624 if (failed(checkImplementationStatus(*atomicCompareOp)))
5625 return failure();
5626
5627 Region &region = atomicCompareOp.getRegion();
5628 Block &block = region.front();
5629
5630 // Determine element type from the region block argument
5631 llvm::Type *llvmXElementType =
5632 moduleTranslation.convertType(block.getArgument(0).getType());
5633 if (!llvmXElementType)
5634 return atomicCompareOp.emitError(
5635 "unable to determine element type for atomic compare");
5636
5637 llvm::Value *llvmX = moduleTranslation.lookupValue(atomicCompareOp.getX());
5638
5639 // IsSigned is determined from the comparison predicate in the region.
5640 // Signed ICmp predicates (slt/sgt) set this to true; unsigned (ult/ugt)
5641 // leave it false. For EQ and float comparisons, signedness is irrelevant.
5642 bool isSigned = false;
5643 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {llvmX, llvmXElementType,
5644 isSigned,
5645 /*IsVolatile=*/false};
5646
5647 llvm::AtomicOrdering atomicOrdering =
5648 convertAtomicOrdering(atomicCompareOp.getMemoryOrder());
5649
5650 auto isAtomicComparePatternOp = [](Operation &op) {
5651 return llvm::isa<LLVM::ICmpOp, LLVM::FCmpOp, LLVM::SelectOp, LLVM::AndOp,
5652 LLVM::OrOp>(op);
5653 };
5654
5655 // Pre-translate operations inside the region that compute e and d (e.g.,
5656 // GEP, loads for dereferencing Fortran pointers) but are not part of the
5657 // atomic compare-and-swap pattern (icmp/fcmp, select, and/or).
5658 //
5659 // 1) Validity: The OpenMP spec requires e and d to be evaluated before the
5660 // atomic operation, so emitting their computation here is correct.
5661 // 2) Memory effects: These ops only depend on values defined outside the
5662 // region. They cannot observe the block argument (%xval), which is the
5663 // value loaded atomically by cmpxchg and does not exist yet.
5664 // 3) Invariant enforcement: The `allOperandsMapped` check below skips any
5665 // op whose operands include the unmapped block argument, guaranteeing
5666 // only region-external-dependent ops are pre-translated.
5667 for (Operation &op : block.without_terminator()) {
5668 // Skip operations that form the atomic compare pattern — these are
5669 // not emitted as individual instructions but are analyzed below to
5670 // extract the comparison predicate, expected value (e), and desired
5671 // value (d) for generating a single cmpxchg/atomicrmw.
5672 if (isAtomicComparePatternOp(op))
5673 continue;
5674
5675 // Avoid translating ops that depend on the unmapped block argument.
5676 bool allOperandsMapped = llvm::all_of(op.getOperands(), [&](mlir::Value v) {
5677 return moduleTranslation.lookupValue(v) != nullptr;
5678 });
5679 if (!allOperandsMapped)
5680 continue;
5681
5682 if (failed(moduleTranslation.convertOperation(op, builder)))
5683 return atomicCompareOp.emitError(
5684 "failed to translate operation inside atomic compare region");
5685 }
5686
5687 // Look up a value that may have been pre-translated or defined outside the
5688 // region.
5689 auto materializeValue = [&](mlir::Value val) -> llvm::Value * {
5690 // Check if the value is already mapped (pre-translated or defined outside).
5691 if (llvm::Value *existing = moduleTranslation.lookupValue(val))
5692 return existing;
5693 // Fallback for a single LoadOp whose address is mapped but whose result
5694 // was not pre-translated.
5695 if (auto loadOp = val.getDefiningOp<LLVM::LoadOp>()) {
5696 if (loadOp->getParentRegion() == &region) {
5697 llvm::Value *loadAddr = moduleTranslation.lookupValue(loadOp.getAddr());
5698 if (!loadAddr)
5699 return nullptr;
5700 llvm::Type *loadType =
5701 moduleTranslation.convertType(loadOp.getResult().getType());
5702 return builder.CreateLoad(loadType, loadAddr);
5703 }
5704 }
5705 return nullptr;
5706 };
5707
5708 // Walk the region to extract comparison predicate, eVal, and dVal.
5709 // if (x == eVal) x = dVal
5710 llvm::omp::OMPAtomicCompareOp compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
5711 llvm::Value *eVal = nullptr;
5712 llvm::Value *dVal = nullptr;
5713 bool isXBinopExpr = false;
5714
5715 auto traceToAggregate = [](mlir::Value v) -> mlir::Value {
5716 if (auto extractOp = v.getDefiningOp<LLVM::ExtractValueOp>())
5717 return extractOp.getContainer();
5718 return nullptr;
5719 };
5720
5721 // Check for a decomposed complex comparison pattern:
5722 // %re_x = llvm.extractvalue %xval[0]
5723 // %re_e = llvm.extractvalue %eStruct[0]
5724 // %cmp_re = llvm.fcmp "oeq" %re_x, %re_e
5725 // %im_x = llvm.extractvalue %xval[1]
5726 // %im_e = llvm.extractvalue %eStruct[1]
5727 // %cmp_im = llvm.fcmp "oeq" %im_x, %im_e
5728 // %cmp = llvm.and %cmp_re, %cmp_im (for EQ)
5729 // Detect this by looking for AndOp/OrOp whose operands are both FCmpOps
5730 // operating on ExtractValueOps from the block argument.
5731 bool isComplexPattern = false;
5732 for (Operation &op : block.getOperations()) {
5733 if (!isa<LLVM::AndOp, LLVM::OrOp>(op))
5734 continue;
5735
5736 // Using : %cmp = llvm.and %cmp_re, %cmp_im
5737 auto lhsFcmp = op.getOperand(0).getDefiningOp<LLVM::FCmpOp>();
5738 auto rhsFcmp = op.getOperand(1).getDefiningOp<LLVM::FCmpOp>();
5739 if (!lhsFcmp || !rhsFcmp)
5740 continue;
5741
5742 // Using : %cmp_re = llvm.fcmp "oeq" %re_x, %re_e
5743 // Check presence of x (block argument) and get e.
5744 mlir::Value lhsAgg0 = traceToAggregate(lhsFcmp.getOperand(0));
5745 mlir::Value lhsAgg1 = traceToAggregate(lhsFcmp.getOperand(1));
5746 bool lhsXIsOp0 = (lhsAgg0 == block.getArgument(0));
5747 bool lhsXIsOp1 = (lhsAgg1 == block.getArgument(0));
5748 if (!lhsXIsOp0 && !lhsXIsOp1)
5749 continue;
5750 mlir::Value eAggregate = lhsXIsOp0 ? lhsAgg1 : lhsAgg0;
5751 if (!eAggregate)
5752 continue;
5753
5754 if (isa<LLVM::AndOp>(op))
5755 compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
5756 else
5757 // OrOp corresponds to NE, which is not a valid atomic compare op.
5758 return atomicCompareOp.emitError(
5759 "unsupported comparison predicate (NE) for complex atomic compare");
5760
5761 isXBinopExpr = lhsXIsOp0;
5762 eVal = materializeValue(eAggregate);
5763 isComplexPattern = true;
5764 break;
5765 }
5766
5767 if (isComplexPattern) {
5768 // dVal from SelectOp or YieldOp.
5769 for (Operation &op : block.getOperations()) {
5770 if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
5771 dVal = materializeValue(selectOp.getTrueValue());
5772 break;
5773 }
5774 }
5775 if (!dVal) {
5776 auto yieldOp = cast<omp::YieldOp>(block.getTerminator());
5777 if (yieldOp.getResults().empty())
5778 return atomicCompareOp.emitError(
5779 "failed to extract desired value (d) from atomic compare region");
5780 dVal = materializeValue(yieldOp.getResults()[0]);
5781 }
5782
5783 const llvm::DataLayout &DL =
5784 builder.GetInsertBlock()->getModule()->getDataLayout();
5785 unsigned totalBits =
5786 DL.getTypeStoreSizeInBits(llvmXElementType).getFixedValue();
5787
5788 llvm::IntegerType *intTy =
5789 llvm::IntegerType::get(builder.getContext(), totalBits);
5790
5791 llvm::Align complexAlign = DL.getABITypeAlign(llvmXElementType);
5792 llvm::Align intAlign = DL.getABITypeAlign(intTy);
5793 llvm::Align maxAlign = std::max(complexAlign, intAlign);
5794
5795 llvm::AllocaInst *eAlloca =
5796 builder.CreateAlloca(llvmXElementType, nullptr, "cmplx.e");
5797 eAlloca->setAlignment(maxAlign);
5798 llvm::AllocaInst *dAlloca =
5799 builder.CreateAlloca(llvmXElementType, nullptr, "cmplx.d");
5800 dAlloca->setAlignment(maxAlign);
5801
5802 builder.CreateAlignedStore(eVal, eAlloca, maxAlign);
5803 llvm::Value *eInt =
5804 builder.CreateAlignedLoad(intTy, eAlloca, maxAlign, "cmplx.e.int");
5805 builder.CreateAlignedStore(dVal, dAlloca, maxAlign);
5806 llvm::Value *dInt =
5807 builder.CreateAlignedLoad(intTy, dAlloca, maxAlign, "cmplx.d.int");
5808
5809 llvm::AtomicOrdering failOrdering =
5810 llvm::AtomicCmpXchgInst::getStrongestFailureOrdering(atomicOrdering);
5811 auto *cmpXchg = builder.CreateAtomicCmpXchg(llvmX, eInt, dInt, maxAlign,
5812 atomicOrdering, failOrdering);
5813 cmpXchg->setWeak(atomicCompareOp.getWeak());
5814
5815 // Emit flush after atomic compare if needed (for release, acq_rel,
5816 // seq_cst orderings).
5817 if (atomicOrdering == llvm::AtomicOrdering::Release ||
5818 atomicOrdering == llvm::AtomicOrdering::AcquireRelease ||
5819 atomicOrdering == llvm::AtomicOrdering::SequentiallyConsistent) {
5820 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5821 ompBuilder->createFlush(ompLoc);
5822 }
5823 return success();
5824 } else {
5825
5826 for (Operation &op : block.getOperations()) {
5827 if (auto icmpOp = dyn_cast<LLVM::ICmpOp>(op)) {
5828 auto maybeOp =
5829 convertICmpPredicateToAtomicCompareOp(icmpOp.getPredicate());
5830 if (!maybeOp)
5831 return atomicCompareOp.emitError(
5832 "unsupported comparison predicate in atomic compare");
5833 compareOp = *maybeOp;
5834
5835 LLVM::ICmpPredicate pred = icmpOp.getPredicate();
5836 isSigned = (pred == LLVM::ICmpPredicate::slt ||
5837 pred == LLVM::ICmpPredicate::sgt ||
5838 pred == LLVM::ICmpPredicate::sle ||
5839 pred == LLVM::ICmpPredicate::sge);
5840
5841 // Identify which operand is the block argument (x) and which is e.
5842 isXBinopExpr = (icmpOp.getOperand(0) == block.getArgument(0));
5843 mlir::Value eOperand =
5844 isXBinopExpr ? icmpOp.getOperand(1) : icmpOp.getOperand(0);
5845 eVal = materializeValue(eOperand);
5846 } else if (auto fcmpOp = dyn_cast<LLVM::FCmpOp>(op)) {
5847 auto maybeOp =
5848 convertFCmpPredicateToAtomicCompareOp(fcmpOp.getPredicate());
5849 if (!maybeOp)
5850 return atomicCompareOp.emitError(
5851 "unsupported comparison predicate in atomic compare");
5852 compareOp = *maybeOp;
5853
5854 isXBinopExpr = (fcmpOp.getOperand(0) == block.getArgument(0));
5855 mlir::Value eOperand =
5856 isXBinopExpr ? fcmpOp.getOperand(1) : fcmpOp.getOperand(0);
5857 eVal = materializeValue(eOperand);
5858 } else if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
5859 if (!dVal)
5860 dVal = materializeValue(selectOp.getTrueValue());
5861 }
5862 }
5863 }
5864
5865 // For non-complex patterns, also extract dVal from SelectOp.
5866 if (!dVal) {
5867 for (Operation &op : block.getOperations()) {
5868 if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
5869 dVal = materializeValue(selectOp.getTrueValue());
5870 break;
5871 }
5872 }
5873 }
5874
5875 if (!eVal)
5876 return atomicCompareOp.emitError(
5877 "failed to extract expected value (e) from atomic compare region");
5878 if (!dVal) {
5879 // Fall back to the yield operand.
5880 auto yieldOp = cast<omp::YieldOp>(block.getTerminator());
5881 if (yieldOp.getResults().empty())
5882 return atomicCompareOp.emitError(
5883 "failed to extract desired value (d) from atomic compare region");
5884 dVal = materializeValue(yieldOp.getResults()[0]);
5885 }
5886
5887 llvmAtomicX.IsSigned = isSigned;
5888
5889 llvm::OpenMPIRBuilder::AtomicOpValue vOpVal = {nullptr, nullptr, false,
5890 false};
5891 llvm::OpenMPIRBuilder::AtomicOpValue rOpVal = {nullptr, nullptr, false,
5892 false};
5893 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5894
5895 bool isWeak = atomicCompareOp.getWeak();
5896
5897 bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
5898 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
5899 ompBuilder->createAtomicCompare(ompLoc, llvmAtomicX, vOpVal, rOpVal, eVal,
5900 dVal, atomicOrdering, compareOp,
5901 isXBinopExpr, false, false, isWeak);
5902 ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
5903
5904 if (failed(handleError(afterIP, *atomicCompareOp)))
5905 return failure();
5906
5907 builder.restoreIP(*afterIP);
5908 return success();
5909}
5910
5911static llvm::omp::Directive convertCancellationConstructType(
5912 omp::ClauseCancellationConstructType directive) {
5913 switch (directive) {
5914 case omp::ClauseCancellationConstructType::Loop:
5915 return llvm::omp::Directive::OMPD_for;
5916 case omp::ClauseCancellationConstructType::Parallel:
5917 return llvm::omp::Directive::OMPD_parallel;
5918 case omp::ClauseCancellationConstructType::Sections:
5919 return llvm::omp::Directive::OMPD_sections;
5920 case omp::ClauseCancellationConstructType::Taskgroup:
5921 return llvm::omp::Directive::OMPD_taskgroup;
5922 }
5923 llvm_unreachable("Unhandled cancellation construct type");
5924}
5925
5926static LogicalResult
5927convertOmpCancel(omp::CancelOp op, llvm::IRBuilderBase &builder,
5928 LLVM::ModuleTranslation &moduleTranslation) {
5929 if (failed(checkImplementationStatus(*op.getOperation())))
5930 return failure();
5931
5932 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5933 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5934
5935 llvm::Value *ifCond = nullptr;
5936 if (Value ifVar = op.getIfExpr())
5937 ifCond = moduleTranslation.lookupValue(ifVar);
5938
5939 llvm::omp::Directive cancelledDirective =
5940 convertCancellationConstructType(op.getCancelDirective());
5941
5942 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
5943 ompBuilder->createCancel(ompLoc, ifCond, cancelledDirective);
5944
5945 if (failed(handleError(afterIP, *op.getOperation())))
5946 return failure();
5947
5948 builder.restoreIP(afterIP.get());
5949
5950 return success();
5951}
5952
5953static LogicalResult
5954convertOmpCancellationPoint(omp::CancellationPointOp op,
5955 llvm::IRBuilderBase &builder,
5956 LLVM::ModuleTranslation &moduleTranslation) {
5957 if (failed(checkImplementationStatus(*op.getOperation())))
5958 return failure();
5959
5960 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5961 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5962
5963 llvm::omp::Directive cancelledDirective =
5964 convertCancellationConstructType(op.getCancelDirective());
5965
5966 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
5967 ompBuilder->createCancellationPoint(ompLoc, cancelledDirective);
5968
5969 if (failed(handleError(afterIP, *op.getOperation())))
5970 return failure();
5971
5972 builder.restoreIP(afterIP.get());
5973
5974 return success();
5975}
5976
5977/// Converts an OpenMP Threadprivate operation into LLVM IR using
5978/// OpenMPIRBuilder.
5979static LogicalResult
5980convertOmpThreadprivate(Operation &opInst, llvm::IRBuilderBase &builder,
5981 LLVM::ModuleTranslation &moduleTranslation) {
5982 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5983 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
5984 auto threadprivateOp = cast<omp::ThreadprivateOp>(opInst);
5985
5986 if (failed(checkImplementationStatus(opInst)))
5987 return failure();
5988
5989 Value symAddr = threadprivateOp.getSymAddr();
5990 auto *symOp = symAddr.getDefiningOp();
5991
5992 if (auto asCast = dyn_cast<LLVM::AddrSpaceCastOp>(symOp))
5993 symOp = asCast.getOperand().getDefiningOp();
5994
5995 if (!isa<LLVM::AddressOfOp>(symOp))
5996 return opInst.emitError("Addressing symbol not found");
5997 LLVM::AddressOfOp addressOfOp = dyn_cast<LLVM::AddressOfOp>(symOp);
5998
5999 LLVM::GlobalOp global =
6000 addressOfOp.getGlobal(moduleTranslation.symbolTable());
6001 llvm::GlobalValue *globalValue = moduleTranslation.lookupGlobal(global);
6002 llvm::Type *type = globalValue->getValueType();
6003 llvm::TypeSize typeSize =
6004 builder.GetInsertBlock()->getModule()->getDataLayout().getTypeStoreSize(
6005 type);
6006 llvm::ConstantInt *size = builder.getInt64(typeSize.getFixedValue());
6007 llvm::Value *callInst = ompBuilder->createCachedThreadPrivate(
6008 ompLoc, globalValue, size, global.getSymName() + ".cache");
6009 moduleTranslation.mapValue(opInst.getResult(0), callInst);
6010
6011 return success();
6012}
6013
6014static llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseKind
6015convertToDeviceClauseKind(mlir::omp::DeclareTargetDeviceType deviceClause) {
6016 switch (deviceClause) {
6017 case mlir::omp::DeclareTargetDeviceType::host:
6018 return llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseHost;
6019 break;
6020 case mlir::omp::DeclareTargetDeviceType::nohost:
6021 return llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseNoHost;
6022 break;
6023 case mlir::omp::DeclareTargetDeviceType::any:
6024 return llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseAny;
6025 break;
6026 }
6027 llvm_unreachable("unhandled device clause");
6028}
6029
6030static llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryKind
6032 mlir::omp::DeclareTargetCaptureClause captureClause) {
6033 switch (captureClause) {
6034 case mlir::omp::DeclareTargetCaptureClause::to:
6035 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryTo;
6036 case mlir::omp::DeclareTargetCaptureClause::link:
6037 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryLink;
6038 case mlir::omp::DeclareTargetCaptureClause::enter:
6039 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryEnter;
6040 case mlir::omp::DeclareTargetCaptureClause::none:
6041 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryNone;
6042 }
6043 llvm_unreachable("unhandled capture clause");
6044}
6045
6047 Operation *op = value.getDefiningOp();
6048 if (auto addrCast = dyn_cast_if_present<LLVM::AddrSpaceCastOp>(op))
6049 op = addrCast->getOperand(0).getDefiningOp();
6050 if (auto addressOfOp = dyn_cast_if_present<LLVM::AddressOfOp>(op)) {
6051 auto modOp = addressOfOp->getParentOfType<mlir::ModuleOp>();
6052 return modOp.lookupSymbol(addressOfOp.getGlobalName());
6053 }
6054 return nullptr;
6055}
6056
6058 while (Operation *op = value.getDefiningOp()) {
6059 if (auto addrCast = dyn_cast_if_present<LLVM::AddrSpaceCastOp>(op))
6060 value = addrCast.getOperand();
6061 // Traces through hlfir.declare, fir.declare to reach the base address and
6062 // use for type lookup.
6063 else if (op->getName().getIdentifier() &&
6064 (op->getName().getIdentifier().str() == "hlfir.declare" ||
6065 op->getName().getIdentifier().str() == "fir.declare")) {
6066 if (op->getNumOperands() > 0)
6067 value = op->getOperand(0);
6068 else
6069 break;
6070 } else {
6071 break;
6072 }
6073 }
6074 return value;
6075}
6076
6077static llvm::SmallString<64>
6078getDeclareTargetRefPtrSuffix(LLVM::GlobalOp globalOp,
6079 llvm::OpenMPIRBuilder &ompBuilder,
6080 llvm::vfs::FileSystem &vfs) {
6081 llvm::SmallString<64> suffix;
6082 llvm::raw_svector_ostream os(suffix);
6083 if (globalOp.getVisibility() == mlir::SymbolTable::Visibility::Private) {
6084 auto loc = globalOp->getLoc()->findInstanceOf<FileLineColLoc>();
6085 auto fileInfoCallBack = [&loc]() {
6086 return std::pair<std::string, uint64_t>(
6087 llvm::StringRef(loc.getFilename()), loc.getLine());
6088 };
6089
6090 os << llvm::format(
6091 "_%x",
6092 ompBuilder.getTargetEntryUniqueInfo(fileInfoCallBack, vfs).FileID);
6093 }
6094 os << "_decl_tgt_ref_ptr";
6095
6096 return suffix;
6097}
6098
6099static bool isDeclareTargetLink(Value value) {
6100 if (auto declareTargetGlobal =
6101 dyn_cast_if_present<omp::DeclareTargetInterface>(
6102 getGlobalOpFromValue(value)))
6103 if (declareTargetGlobal.getDeclareTargetCaptureClause() ==
6104 omp::DeclareTargetCaptureClause::link)
6105 return true;
6106 return false;
6107}
6108
6109static bool isDeclareTargetTo(Value value) {
6110 if (auto declareTargetGlobal =
6111 dyn_cast_if_present<omp::DeclareTargetInterface>(
6112 getGlobalOpFromValue(value)))
6113 if (declareTargetGlobal.getDeclareTargetCaptureClause() ==
6114 omp::DeclareTargetCaptureClause::to ||
6115 declareTargetGlobal.getDeclareTargetCaptureClause() ==
6116 omp::DeclareTargetCaptureClause::enter)
6117 return true;
6118 return false;
6119}
6120
6121// Returns the reference pointer generated by the lowering of the declare
6122// target operation in cases where the link clause is used or the to clause is
6123// used in USM mode.
6124static llvm::Value *
6126 LLVM::ModuleTranslation &moduleTranslation) {
6127 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
6128 if (auto gOp =
6129 dyn_cast_or_null<LLVM::GlobalOp>(getGlobalOpFromValue(value))) {
6130 // In this case, we must utilise the reference pointer generated by
6131 // the declare target operation, similar to Clang
6132 if (isDeclareTargetLink(value) ||
6133 (isDeclareTargetTo(value) &&
6134 ompBuilder->Config.hasRequiresUnifiedSharedMemory())) {
6136 gOp, *ompBuilder, moduleTranslation.getFileSystem());
6137
6138 if (gOp.getSymName().contains(suffix))
6139 return moduleTranslation.getLLVMModule()->getNamedValue(
6140 gOp.getSymName());
6141
6142 return moduleTranslation.getLLVMModule()->getNamedValue(
6143 (gOp.getSymName().str() + suffix.str()).str());
6144 }
6145 }
6146 return nullptr;
6147}
6148
6149namespace {
6150// Append customMappers information to existing MapInfosTy
6151struct MapInfosTy : llvm::OpenMPIRBuilder::MapInfosTy {
6152 SmallVector<Operation *, 4> Mappers;
6153
6154 /// Append arrays in \a CurInfo.
6155 void append(MapInfosTy &curInfo) {
6156 Mappers.append(curInfo.Mappers.begin(), curInfo.Mappers.end());
6157 llvm::OpenMPIRBuilder::MapInfosTy::append(curInfo);
6158 }
6159};
6160// A small helper structure to contain data gathered
6161// for map lowering and coalese it into one area and
6162// avoiding extra computations such as searches in the
6163// llvm module for lowered mapped variables or checking
6164// if something is declare target (and retrieving the
6165// value) more than neccessary.
6166struct MapInfoData : MapInfosTy {
6167 llvm::SmallVector<bool, 4> IsDeclareTarget;
6168 llvm::SmallVector<bool, 4> IsAMember;
6169 // Identify if mapping was added by mapClause or use_device clauses.
6170 llvm::SmallVector<bool, 4> IsAMapping;
6171 llvm::SmallVector<mlir::Operation *, 4> MapClause;
6172 llvm::SmallVector<llvm::Value *, 4> OriginalValue;
6173 // Stripped off array/pointer to get the underlying
6174 // element type
6175 llvm::SmallVector<llvm::Type *, 4> BaseType;
6176
6177 /// Append arrays in \a CurInfo.
6178 void append(MapInfoData &CurInfo) {
6179 IsDeclareTarget.append(CurInfo.IsDeclareTarget.begin(),
6180 CurInfo.IsDeclareTarget.end());
6181 MapClause.append(CurInfo.MapClause.begin(), CurInfo.MapClause.end());
6182 OriginalValue.append(CurInfo.OriginalValue.begin(),
6183 CurInfo.OriginalValue.end());
6184 BaseType.append(CurInfo.BaseType.begin(), CurInfo.BaseType.end());
6185 MapInfosTy::append(CurInfo);
6186 }
6187};
6188
6189enum class TargetDirectiveEnumTy : uint32_t {
6190 None = 0,
6191 Target = 1,
6192 TargetData = 2,
6193 TargetEnterData = 3,
6194 TargetExitData = 4,
6195 TargetUpdate = 5
6196};
6197
6198static TargetDirectiveEnumTy getTargetDirectiveEnumTyFromOp(Operation *op) {
6199 return llvm::TypeSwitch<Operation *, TargetDirectiveEnumTy>(op)
6200 .Case([](omp::TargetDataOp) { return TargetDirectiveEnumTy::TargetData; })
6201 .Case([](omp::TargetEnterDataOp) {
6202 return TargetDirectiveEnumTy::TargetEnterData;
6203 })
6204 .Case([&](omp::TargetExitDataOp) {
6205 return TargetDirectiveEnumTy::TargetExitData;
6206 })
6207 .Case([&](omp::TargetUpdateOp) {
6208 return TargetDirectiveEnumTy::TargetUpdate;
6209 })
6210 .Case([&](omp::TargetOp) { return TargetDirectiveEnumTy::Target; })
6211 .Default([&](Operation *op) { return TargetDirectiveEnumTy::None; });
6212}
6213
6214} // namespace
6215
6216static uint64_t getArrayElementSizeInBits(LLVM::LLVMArrayType arrTy,
6217 DataLayout &dl) {
6218 if (auto nestedArrTy = llvm::dyn_cast_if_present<LLVM::LLVMArrayType>(
6219 arrTy.getElementType()))
6220 return getArrayElementSizeInBits(nestedArrTy, dl);
6221 return dl.getTypeSizeInBits(arrTy.getElementType());
6222}
6223
6224// The intent is to verify if the mapped data being passed is a
6225// pointer -> pointee that requires special handling in certain cases,
6226// e.g. applying the OMP_MAP_PTR_AND_OBJ map type.
6227//
6228// There may be a better way to verify this, but unfortunately with
6229// opaque pointers we lose the ability to easily check if something is
6230// a pointer whilst maintaining access to the underlying type.
6231static bool checkIfPointerMap(omp::MapInfoOp mapOp) {
6232 // If we have a varPtrPtr field assigned then the underlying type is a pointer
6233 if (mapOp.getVarPtrPtr())
6234 return true;
6235
6236 // If the map data is declare target with a link clause, then it's represented
6237 // as a pointer when we lower it to LLVM-IR even if at the MLIR level it has
6238 // no relation to pointers.
6239 if (isDeclareTargetLink(mapOp.getVarPtr()))
6240 return true;
6241
6242 return false;
6243}
6244
6245// This function calculates the size to be offloaded for a specified type, given
6246// its associated map clause (which can contain bounds information which affects
6247// the total size), this size is calculated based on the underlying element type
6248// e.g. given a 1-D array of ints, we will calculate the size from the integer
6249// type * number of elements in the array. This size can be used in other
6250// calculations but is ultimately used as an argument to the OpenMP runtimes
6251// kernel argument structure which is generated through the combinedInfo data
6252// structures.
6253// This function is somewhat equivalent to Clang's getExprTypeSize inside of
6254// CGOpenMPRuntime.cpp.
6255static llvm::Value *getSizeInBytes(DataLayout &dl, const mlir::Type &type,
6256 Operation *clauseOp,
6257 llvm::Value *basePointer,
6258 llvm::Type *baseType,
6259 llvm::IRBuilderBase &builder,
6260 LLVM::ModuleTranslation &moduleTranslation) {
6261 if (auto memberClause =
6262 mlir::dyn_cast_if_present<mlir::omp::MapInfoOp>(clauseOp)) {
6263 // This calculates the size to transfer based on bounds and the underlying
6264 // element type, provided bounds have been specified (Fortran
6265 // pointers/allocatables/target and arrays that have sections specified fall
6266 // into this as well)
6267 if (!memberClause.getBounds().empty()) {
6268 llvm::Value *elementCount = builder.getInt64(1);
6269 for (auto bounds : memberClause.getBounds()) {
6270 if (auto boundOp = mlir::dyn_cast_if_present<mlir::omp::MapBoundsOp>(
6271 bounds.getDefiningOp())) {
6272 // The below calculation for the size to be mapped calculated from the
6273 // map.info's bounds is: (elemCount * [UB - LB] + 1), later we
6274 // multiply by the underlying element types byte size to get the full
6275 // size to be offloaded based on the bounds
6276 elementCount = builder.CreateMul(
6277 elementCount,
6278 builder.CreateAdd(
6279 builder.CreateSub(
6280 moduleTranslation.lookupValue(boundOp.getUpperBound()),
6281 moduleTranslation.lookupValue(boundOp.getLowerBound())),
6282 builder.getInt64(1)));
6283 }
6284 }
6285
6286 // utilising getTypeSizeInBits instead of getTypeSize as getTypeSize gives
6287 // the size in inconsistent byte or bit format.
6288 uint64_t underlyingTypeSzInBits = dl.getTypeSizeInBits(type);
6289 if (auto arrTy = llvm::dyn_cast_if_present<LLVM::LLVMArrayType>(type))
6290 underlyingTypeSzInBits = getArrayElementSizeInBits(arrTy, dl);
6291
6292 // The size in bytes x number of elements, the sizeInBytes stored is
6293 // the underyling types size, e.g. if ptr<i32>, it'll be the i32's
6294 // size, so we do some on the fly runtime math to get the size in
6295 // bytes from the extent (ub - lb) * sizeInBytes. NOTE: This may need
6296 // some adjustment for members with more complex types.
6297 llvm::Value *sizeCalc = builder.CreateMul(
6298 elementCount, builder.getInt64(underlyingTypeSzInBits / 8),
6299 "element_count");
6300
6301 // This is a part of a "complicated" bit of size calculation logic that is
6302 // in place to handle a couple of scenarios, one specific to Fortran and
6303 // the other a more general OpenMP issue. The other piece of the
6304 // calculation can be found as the final size calculation within the
6305 // processIndividualMap function. Ideally we would move it here, but due
6306 // to the complexity of calculating the final base address of some
6307 // constructs (required for a nullary check), it's left as the final step.
6308 // So, in the below 2 cases, the nullary check is in processIndividualMap
6309 // and the size equality check is here. The cases this modifications help
6310 // cover are:
6311 //
6312 // 1) If an argument has a null base pointer, then the size must be set to
6313 // 0 to avoid the runtime exploding/complaining about an illegal
6314 // pointer map. The size returning non-zero is feasible in certain
6315 // cases if for example someone has specified there own bounds/range.
6316 // 2) We wish to support a very specific OpenMP Fortran edge-case where a
6317 // size zero array can be legally presence checked and found to be on
6318 // device when it has been mapped. In these rare occasions the
6319 // allocatable/pointer will have a size of 1 allocated for the
6320 // underlying data, but this wall not be represented within the size of
6321 // the descriptor, so we get a non-nullary pointer and a size of 0,
6322 // allowing us to specify a size of 1 in these cases registering it on
6323 // the device mapping table as present.
6324 //
6325 // The default fall through case is just returning the size calculation
6326 // above, if we are not nullary and the size we calculate is non-zero,
6327 // which is basically any pointer type that is allocated in someway
6328 // (providing you are not running on a rare system that allows malloc's of
6329 // size 0 with whatever caveats that may come with).
6330 //
6331 // Later in the nullary check in processIndividualMap it just devolves to
6332 // selecting a size of 0 if we are nullary, if we are not, we will return
6333 // either 1 or the calculated size, depending on the outcome of this
6334 // select.
6335 if (checkIfPointerMap(memberClause)) {
6336 return builder.CreateSelect(
6337 builder.CreateICmpEQ(sizeCalc, builder.getInt64(0)),
6338 builder.getInt64(1), sizeCalc);
6339 }
6340
6341 return sizeCalc;
6342 }
6343 }
6344
6345 return builder.getInt64(dl.getTypeSizeInBits(type) / 8);
6346}
6347
6348// Convert the MLIR map flag set to the runtime map flag set for embedding
6349// in LLVM-IR. This is important as the two bit-flag lists do not correspond
6350// 1-to-1 as there's flags the runtime doesn't care about and vice versa.
6351// Certain flags are discarded here such as RefPtee and co.
6352static llvm::omp::OpenMPOffloadMappingFlags
6353convertClauseMapFlags(omp::ClauseMapFlags mlirFlags) {
6354 const bool hasExplicitMap =
6355 (mlirFlags & ~omp::ClauseMapFlags::is_device_ptr) !=
6356 omp::ClauseMapFlags::none;
6357
6358 llvm::omp::OpenMPOffloadMappingFlags mapType =
6359 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE;
6360
6361 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::to))
6362 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TO;
6363
6364 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::from))
6365 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_FROM;
6366
6367 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::always))
6368 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ALWAYS;
6369
6370 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::del))
6371 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_DELETE;
6372
6373 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::return_param))
6374 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
6375
6376 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::priv))
6377 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_PRIVATE;
6378
6379 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::literal))
6380 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL;
6381
6382 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::implicit))
6383 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_IMPLICIT;
6384
6385 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::close))
6386 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_CLOSE;
6387
6388 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::present))
6389 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_PRESENT;
6390
6391 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::ompx_hold))
6392 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_OMPX_HOLD;
6393
6394 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::attach))
6395 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH;
6396
6397 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::is_device_ptr)) {
6398 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TARGET_PARAM;
6399 if (!hasExplicitMap)
6400 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL;
6401 }
6402
6403 return mapType;
6404}
6405
6407 MapInfoData &mapData, SmallVectorImpl<Value> &mapVars,
6408 LLVM::ModuleTranslation &moduleTranslation, DataLayout &dl,
6409 llvm::IRBuilderBase &builder, ArrayRef<Value> useDevPtrOperands = {},
6410 ArrayRef<Value> useDevAddrOperands = {},
6411 ArrayRef<Value> hasDevAddrOperands = {}) {
6412
6413 auto checkRefPtrOrPteeMapWithAttach = [](omp::ClauseMapFlags mapType) {
6414 bool hasRefType =
6415 bitEnumContainsAll(mapType, omp::ClauseMapFlags::ref_ptr) ||
6416 bitEnumContainsAll(mapType, omp::ClauseMapFlags::ref_ptee);
6417 return hasRefType &&
6418 bitEnumContainsAll(mapType, omp::ClauseMapFlags::attach);
6419 };
6420
6421 auto checkIsAMember = [](const auto &mapVars, auto mapOp) {
6422 // Check if this is a member mapping and correctly assign that it is, if
6423 // it is a member of a larger object.
6424 // TODO: Need better handling of members, and distinguishing of members
6425 // that are implicitly allocated on device vs explicitly passed in as
6426 // arguments.
6427 // TODO: May require some further additions to support nested record
6428 // types, i.e. member maps that can have member maps.
6429 for (Value mapValue : mapVars) {
6430 auto map = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
6431 for (auto member : map.getMembers())
6432 if (member == mapOp)
6433 return true;
6434 }
6435 return false;
6436 };
6437
6438 // Process MapOperands
6439 for (Value mapValue : mapVars) {
6440 auto mapOp = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
6441 bool isRefPtrOrPteeMapWithAttach =
6442 checkRefPtrOrPteeMapWithAttach(mapOp.getMapType());
6443 Value offloadPtr = (mapOp.getVarPtrPtr() && !isRefPtrOrPteeMapWithAttach)
6444 ? mapOp.getVarPtrPtr()
6445 : mapOp.getVarPtr();
6446 mapData.OriginalValue.push_back(moduleTranslation.lookupValue(offloadPtr));
6447 mapData.Pointers.push_back(
6448 isRefPtrOrPteeMapWithAttach
6449 ? moduleTranslation.lookupValue(mapOp.getVarPtrPtr())
6450 : mapData.OriginalValue.back());
6451
6452 if (llvm::Value *refPtr =
6453 getRefPtrIfDeclareTarget(offloadPtr, moduleTranslation)) {
6454 mapData.IsDeclareTarget.push_back(true);
6455 mapData.BasePointers.push_back(refPtr);
6456 } else if (isDeclareTargetTo(offloadPtr)) {
6457 mapData.IsDeclareTarget.push_back(true);
6458 mapData.BasePointers.push_back(mapData.OriginalValue.back());
6459 } else { // regular mapped variable
6460 mapData.IsDeclareTarget.push_back(false);
6461 mapData.BasePointers.push_back(mapData.OriginalValue.back());
6462 }
6463
6464 // In every situation we currently have if we have a varPtrPtr present
6465 // we wish to utilise it's type for the base type, main cases are
6466 // currently Fortran descriptor base address maps and attach maps.
6467 mapData.BaseType.push_back(moduleTranslation.convertType(
6468 mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtrType().value()
6469 : mapOp.getVarPtrType()));
6470
6471 // For the attach map cases, it's a little odd, as we effectively have to
6472 // utilise the base address (including all bounds offsets) for the pointer
6473 // field, the pointer address for the base address field, and the pointer
6474 // not the data (base addresses) size. So we end up with a mix of base
6475 // types and sizes we wish to insert here.
6476 mlir::Type sizeType = (isRefPtrOrPteeMapWithAttach || !mapOp.getVarPtrPtr())
6477 ? mapOp.getVarPtrType()
6478 : mapOp.getVarPtrPtrType().value();
6479 mapData.Sizes.push_back(getSizeInBytes(
6480 dl, sizeType, isRefPtrOrPteeMapWithAttach ? nullptr : mapOp,
6481 mapData.Pointers.back(), moduleTranslation.convertType(sizeType),
6482 builder, moduleTranslation));
6483 mapData.MapClause.push_back(mapOp.getOperation());
6484 mapData.Types.push_back(convertClauseMapFlags(mapOp.getMapType()));
6485 mapData.Names.push_back(LLVM::createMappingInformation(
6486 mapOp.getLoc(), *moduleTranslation.getOpenMPBuilder()));
6487 mapData.DevicePointers.push_back(llvm::OpenMPIRBuilder::DeviceInfoTy::None);
6488 if (mapOp.getMapperId())
6489 mapData.Mappers.push_back(
6491 mapOp, mapOp.getMapperIdAttr()));
6492 else
6493 mapData.Mappers.push_back(nullptr);
6494 mapData.IsAMapping.push_back(true);
6495 mapData.IsAMember.push_back(checkIsAMember(mapVars, mapOp));
6496 }
6497
6498 auto findMapInfo = [&mapData](llvm::Value *val,
6499 llvm::OpenMPIRBuilder::DeviceInfoTy devInfoTy,
6500 size_t memberCount) {
6501 unsigned index = 0;
6502 bool found = false;
6503 for (llvm::Value *basePtr : mapData.OriginalValue) {
6504 auto mapOp = cast<omp::MapInfoOp>(mapData.MapClause[index]);
6505 // TODO: Currently we define an equivalent mapping as
6506 // the same base pointer and an equivalent member count, but
6507 // that is a loose definition. We may have to extend to check
6508 // for other fields (varPtrPtr/individual members being mapped).
6509 // Note: Attach maps are not the same as a normal data transfer
6510 // they specify to the runtime to perform an attach map and they
6511 // (at least at the moment) are never something we would aim to
6512 // return in a use_dev_* clause, so they are skipped in terms of
6513 // duplicate maps.
6514 bool isAttachMap =
6515 (mapData.Types[index] &
6516 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) ==
6517 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH;
6518 if (!isAttachMap && basePtr == val && mapData.IsAMapping[index] &&
6519 memberCount == mapOp.getMembers().size()) {
6520 found = true;
6521 mapData.Types[index] |=
6522 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
6523 mapData.DevicePointers[index] = devInfoTy;
6524 }
6525 index++;
6526 }
6527 return found;
6528 };
6529
6530 // Process useDevPtr(Addr)Operands
6531 auto addDevInfos = [&](const llvm::ArrayRef<Value> &useDevOperands,
6532 llvm::OpenMPIRBuilder::DeviceInfoTy devInfoTy) {
6533 for (Value mapValue : useDevOperands) {
6534 auto mapOp = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
6535 Value offloadPtr =
6536 mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtr() : mapOp.getVarPtr();
6537 llvm::Value *origValue = moduleTranslation.lookupValue(offloadPtr);
6538
6539 // Check if map info is already present for this entry.
6540 if (!findMapInfo(origValue, devInfoTy, mapOp.getMembers().size())) {
6541 mapData.OriginalValue.push_back(origValue);
6542 mapData.Pointers.push_back(mapData.OriginalValue.back());
6543 mapData.IsDeclareTarget.push_back(false);
6544 mapData.BasePointers.push_back(mapData.OriginalValue.back());
6545 mlir::Type baseTy = mapOp.getVarPtrPtr()
6546 ? mapOp.getVarPtrPtrType().value()
6547 : mapOp.getVarPtrType();
6548 mapData.BaseType.push_back(moduleTranslation.convertType(baseTy));
6549 mapData.Sizes.push_back(builder.getInt64(0));
6550 mapData.MapClause.push_back(mapOp.getOperation());
6551 mapData.Types.push_back(
6552 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM);
6553 mapData.Names.push_back(LLVM::createMappingInformation(
6554 mapOp.getLoc(), *moduleTranslation.getOpenMPBuilder()));
6555 mapData.DevicePointers.push_back(devInfoTy);
6556 mapData.Mappers.push_back(nullptr);
6557 mapData.IsAMapping.push_back(false);
6558 mapData.IsAMember.push_back(checkIsAMember(useDevOperands, mapOp));
6559 }
6560 }
6561 };
6562
6563 addDevInfos(useDevAddrOperands, llvm::OpenMPIRBuilder::DeviceInfoTy::Address);
6564 addDevInfos(useDevPtrOperands, llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer);
6565
6566 for (Value mapValue : hasDevAddrOperands) {
6567 auto mapOp = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
6568 Value offloadPtr =
6569 mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtr() : mapOp.getVarPtr();
6570 llvm::Value *origValue = moduleTranslation.lookupValue(offloadPtr);
6571 auto mapType = convertClauseMapFlags(mapOp.getMapType());
6572 auto mapTypeAlways = llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ALWAYS;
6573 bool isDevicePtr =
6574 (mapOp.getMapType() & omp::ClauseMapFlags::is_device_ptr) !=
6575 omp::ClauseMapFlags::none;
6576
6577 mapData.OriginalValue.push_back(origValue);
6578 mapData.BasePointers.push_back(origValue);
6579 mapData.Pointers.push_back(origValue);
6580 mapData.IsDeclareTarget.push_back(false);
6581
6582 mlir::Type baseTy = mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtrType().value()
6583 : mapOp.getVarPtrType();
6584 mapData.BaseType.push_back(moduleTranslation.convertType(baseTy));
6585 mapData.Sizes.push_back(builder.getInt64(dl.getTypeSize(baseTy)));
6586
6587 mapData.MapClause.push_back(mapOp.getOperation());
6588 if (llvm::to_underlying(mapType & mapTypeAlways)) {
6589 // Descriptors are mapped with the ALWAYS flag, since they can get
6590 // rematerialized, so the address of the decriptor for a given object
6591 // may change from one place to another.
6592 mapData.Types.push_back(mapType);
6593 // Technically it's possible for a non-descriptor mapping to have
6594 // both has-device-addr and ALWAYS, so lookup the mapper in case it
6595 // exists.
6596 if (mapOp.getMapperId()) {
6597 mapData.Mappers.push_back(
6599 mapOp, mapOp.getMapperIdAttr()));
6600 } else {
6601 mapData.Mappers.push_back(nullptr);
6602 }
6603 } else {
6604 // For is_device_ptr we need the map type to propagate so the runtime
6605 // can materialize the device-side copy of the pointer container.
6606 mapData.Types.push_back(
6607 isDevicePtr ? mapType
6608 : llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL);
6609 mapData.Mappers.push_back(nullptr);
6610 }
6611 mapData.Names.push_back(LLVM::createMappingInformation(
6612 mapOp.getLoc(), *moduleTranslation.getOpenMPBuilder()));
6613 mapData.DevicePointers.push_back(
6614 isDevicePtr ? llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer
6615 : llvm::OpenMPIRBuilder::DeviceInfoTy::Address);
6616 mapData.IsAMapping.push_back(false);
6617 mapData.IsAMember.push_back(checkIsAMember(hasDevAddrOperands, mapOp));
6618 }
6619}
6620
6621static int getMapDataMemberIdx(MapInfoData &mapData, omp::MapInfoOp memberOp) {
6622 auto *res = llvm::find(mapData.MapClause, memberOp);
6623 assert(res != mapData.MapClause.end() &&
6624 "MapInfoOp for member not found in MapData, cannot return index");
6625 return std::distance(mapData.MapClause.begin(), res);
6626}
6627
6629 omp::MapInfoOp mapInfo, bool first = true) {
6630 ArrayAttr indexAttr = mapInfo.getMembersIndexAttr();
6631 llvm::SmallVector<size_t> occludedChildren;
6632 llvm::sort(
6633 indices.begin(), indices.end(), [&](const size_t a, const size_t b) {
6634 // Bail early if we are asked to look at the same index. If we do not
6635 // bail early, we can end up mistakenly adding indices to
6636 // occludedChildren. This can occur with some types of libc++ hardening.
6637 if (a == b)
6638 return false;
6639
6640 auto memberIndicesA = cast<ArrayAttr>(indexAttr[a]);
6641 auto memberIndicesB = cast<ArrayAttr>(indexAttr[b]);
6642
6643 for (auto it : llvm::zip(memberIndicesA, memberIndicesB)) {
6644 int64_t aIndex = mlir::cast<IntegerAttr>(std::get<0>(it)).getInt();
6645 int64_t bIndex = mlir::cast<IntegerAttr>(std::get<1>(it)).getInt();
6646
6647 if (aIndex == bIndex)
6648 continue;
6649
6650 if (aIndex < bIndex)
6651 return first;
6652
6653 if (aIndex > bIndex)
6654 return !first;
6655 }
6656
6657 // Iterated up until the end of the smallest member and
6658 // they were found to be equal up to that point, so select
6659 // the member with the lowest index count, so the "parent"
6660 bool memberAParent = memberIndicesA.size() < memberIndicesB.size();
6661 if (memberAParent)
6662 occludedChildren.push_back(b);
6663 else
6664 occludedChildren.push_back(a);
6665 return memberAParent;
6666 });
6667
6668 for (auto v : occludedChildren)
6669 indices.erase(std::remove(indices.begin(), indices.end(), v),
6670 indices.end());
6671}
6672
6673static omp::MapInfoOp getFirstOrLastMappedMemberPtr(omp::MapInfoOp mapInfo,
6674 bool first) {
6675 ArrayAttr indexAttr = mapInfo.getMembersIndexAttr();
6676 // Only 1 member has been mapped, we can return it.
6677 if (indexAttr.size() == 1)
6678 return cast<omp::MapInfoOp>(mapInfo.getMembers()[0].getDefiningOp());
6679 llvm::SmallVector<size_t> indices(indexAttr.size());
6680 std::iota(indices.begin(), indices.end(), 0);
6681 sortMapIndices(indices, mapInfo, first);
6682 return llvm::cast<omp::MapInfoOp>(
6683 mapInfo.getMembers()[indices.front()].getDefiningOp());
6684}
6685
6686/// This function calculates the array/pointer offset for map data provided
6687/// with bounds operations, e.g. when provided something like the following:
6688///
6689/// Fortran
6690/// map(tofrom: array(2:5, 3:2))
6691///
6692/// We must calculate the initial pointer offset to pass across, this function
6693/// performs this using bounds.
6694///
6695/// TODO/WARNING: This only supports Fortran's column major indexing currently
6696/// as is noted in the note below and comments in the function, we must extend
6697/// this function when we add a C++ frontend.
6698/// NOTE: which while specified in row-major order it currently needs to be
6699/// flipped for Fortran's column order array allocation and access (as
6700/// opposed to C++'s row-major, hence the backwards processing where order is
6701/// important). This is likely important to keep in mind for the future when
6702/// we incorporate a C++ frontend, both frontends will need to agree on the
6703/// ordering of generated bounds operations (one may have to flip them) to
6704/// make the below lowering frontend agnostic. The offload size
6705/// calcualtion may also have to be adjusted for C++.
6706static std::vector<llvm::Value *>
6708 llvm::IRBuilderBase &builder, bool isArrayTy,
6709 OperandRange bounds) {
6710 std::vector<llvm::Value *> idx;
6711 // There's no bounds to calculate an offset from, we can safely
6712 // ignore and return no indices.
6713 if (bounds.empty())
6714 return idx;
6715
6716 // If we have an array type, then we have its type so can treat it as a
6717 // normal GEP instruction where the bounds operations are simply indexes
6718 // into the array. We currently do reverse order of the bounds, which
6719 // I believe leans more towards Fortran's column-major in memory.
6720 if (isArrayTy) {
6721 idx.push_back(builder.getInt64(0));
6722 for (int i = bounds.size() - 1; i >= 0; --i) {
6723 if (auto boundOp = dyn_cast_if_present<omp::MapBoundsOp>(
6724 bounds[i].getDefiningOp())) {
6725 idx.push_back(moduleTranslation.lookupValue(boundOp.getLowerBound()));
6726 }
6727 }
6728 } else {
6729 // If we do not have an array type, but we have bounds, then we're dealing
6730 // with a pointer that's being treated like an array and we have the
6731 // underlying type e.g. an i32, or f64 etc, e.g. a fortran descriptor base
6732 // address (pointer pointing to the actual data) so we must caclulate the
6733 // offset using a single index which the following loop attempts to
6734 // compute using the standard column-major algorithm e.g for a 3D array:
6735 //
6736 // ((((c_idx * b_len) + b_idx) * a_len) + a_idx)
6737 //
6738 // It is of note that it's doing column-major rather than row-major at the
6739 // moment, but having a way for the frontend to indicate which major format
6740 // to use or standardizing/canonicalizing the order of the bounds to compute
6741 // the offset may be useful in the future when there's other frontends with
6742 // different formats.
6743 for (int i = bounds.size() - 1; i >= 0; --i) {
6744 if (auto boundOp = dyn_cast_if_present<omp::MapBoundsOp>(
6745 bounds[i].getDefiningOp())) {
6746 if (i == ((int)bounds.size() - 1))
6747 idx.emplace_back(
6748 moduleTranslation.lookupValue(boundOp.getLowerBound()));
6749 else
6750 idx.back() = builder.CreateAdd(
6751 builder.CreateMul(idx.back(), moduleTranslation.lookupValue(
6752 boundOp.getExtent())),
6753 moduleTranslation.lookupValue(boundOp.getLowerBound()));
6754 }
6755 }
6756 }
6757
6758 return idx;
6759}
6760
6762 llvm::transform(values, std::back_inserter(ints), [](Attribute value) {
6763 return cast<IntegerAttr>(value).getInt();
6764 });
6765}
6766
6767// Gathers members that are overlapping in the parent, excluding members that
6768// themselves overlap, keeping the top-most (closest to parents level) map.
6769static void
6771 omp::MapInfoOp parentOp) {
6772 // No members mapped, no overlaps.
6773 if (parentOp.getMembers().empty())
6774 return;
6775
6776 // Single member, we can insert and return early.
6777 if (parentOp.getMembers().size() == 1) {
6778 overlapMapDataIdxs.push_back(0);
6779 return;
6780 }
6781
6782 ArrayAttr indexAttr = parentOp.getMembersIndexAttr();
6783 size_t numMembers = indexAttr.size();
6784
6785 // Pre-convert all member indices to integer arrays for efficient comparison.
6786 llvm::SmallVector<llvm::SmallVector<int64_t>> memberIndices(numMembers);
6787 for (auto [i, indicesAttr] : llvm::enumerate(indexAttr))
6788 getAsIntegers(cast<ArrayAttr>(indicesAttr), memberIndices[i]);
6789
6790 // For each member, check if it's superseded by another (shorter prefix)
6791 // member. If member j's indices are a prefix of member i's indices, then
6792 // i is a child of j and should be skipped. e.g. if member [0] is mapped,
6793 // we skip members [0,1], [0,2], etc.
6794 llvm::SmallDenseSet<size_t> skipIndices;
6795 for (size_t i = 0; i < numMembers; ++i) {
6796 const auto &iIndices = memberIndices[i];
6797 for (size_t j = 0; j < numMembers; ++j) {
6798 if (i == j)
6799 continue;
6800 const auto &jIndices = memberIndices[j];
6801 // If j's indices are a strict prefix of i's indices, skip i
6802 if (jIndices.size() < iIndices.size() &&
6803 std::equal(jIndices.begin(), jIndices.end(), iIndices.begin())) {
6804 skipIndices.insert(i);
6805 break; // No need to check other potential parents
6806 }
6807 }
6808 }
6809
6810 // Collect indices of members that are not superseded by a parent.
6811 for (size_t i = 0; i < numMembers; ++i)
6812 if (!skipIndices.contains(i))
6813 overlapMapDataIdxs.push_back(i);
6814}
6815
6816/// This function handles the insertion of a single item of map data from
6817/// MapInfoData into the OMPIRBuilder's MapInfo list. Utilising this function
6818/// means the map being inserted can be treated as a non-parent map entity,
6819/// if the memberOfFlag is set then the map being inserted is treated as
6820/// a member map of a larger entity. The insertion into the MapInfo list of
6821/// the OMPIRBuilder can vary based on a number of factors, such as if it's
6822/// a ref_ptr or ref_ptee map, if it's a member of a record, what construct
6823/// the map belongs to and the various map type bit flags that are set for
6824/// the map.
6825static void
6826processIndividualMap(llvm::IRBuilderBase &builder,
6827 llvm::OpenMPIRBuilder &ompBuilder, MapInfoData &mapData,
6828 size_t mapDataIdx, MapInfosTy &combinedInfo,
6829 TargetDirectiveEnumTy targetDirective,
6830 llvm::omp::OpenMPOffloadMappingFlags memberOfFlag =
6831 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE,
6832 bool isTargetParam = true, int mapDataParentIdx = -1) {
6833 auto mapFlag = mapData.Types[mapDataIdx];
6834 auto mapInfoOp = llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIdx]);
6835
6836 bool isPtrTy = checkIfPointerMap(mapInfoOp);
6837 bool isAttachMap = ((convertClauseMapFlags(mapInfoOp.getMapType()) &
6838 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) ==
6839 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH);
6840
6841 // Declare target variables are not passed to the kernel, and for the moment
6842 // attach maps are not passed to the kernel. However, it is possible to create
6843 // attach maps that transfer data and thus can be kernel arguments, but our
6844 // existing frontend does not do this.
6845 if (isTargetParam &&
6846 (targetDirective == TargetDirectiveEnumTy::Target &&
6847 !mapData.IsDeclareTarget[mapDataIdx]) &&
6848 !isAttachMap)
6849 mapFlag |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TARGET_PARAM;
6850
6851 if (mapInfoOp.getMapCaptureType() == omp::VariableCaptureKind::ByCopy &&
6852 !isPtrTy)
6853 mapFlag |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL;
6854
6855 // If we have a pointer and it's part of a MEMBER_OF mapping we do not apply
6856 // MEMBER_OF, as the runtime currently has a work-around that utilises
6857 // MEMBER_OF to prevent reference updating in certain scenarios instead of
6858 // target_param. However, this causes a noticeable issue in cases where we
6859 // map some data (Fortran descriptor primarily at the moment), alter it on
6860 // the host, and then expect it to not be updated in a subsequent implicit map
6861 // (such as an implicit map on a target).
6862 if (memberOfFlag != llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE) {
6863 if (!isPtrTy && !isAttachMap)
6864 ompBuilder.setCorrectMemberOfFlag(mapFlag, memberOfFlag);
6865
6866 // The return parameter should be the over-riding parent in cases where we
6867 // have a return parameter that is echoed to all members, the main case of
6868 // this currently is with fortran descriptors. It may need more finessing
6869 // for C/C++ in the future or descriptors that are members of derived
6870 // types.
6871 mapFlag &= ~llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
6872 }
6873
6874 // We apply MAP_PTR_AND_OBJ when within a declare mapper object as it enforces
6875 // MEMBER_OF mappings on maps that are passed the initial nesting depth, which
6876 // includes pointed to data and attach members, both of which are technically
6877 // not part of the main object. This has the side effect of causing early
6878 // map-backs in certain cases where an implicit declare mapper has been
6879 // emitted for a target region. Applying MAP_PTR_AND_OBJ in these situations
6880 // circumvents this.
6881 if (isPtrTy && !isAttachMap && mapData.IsDeclareTarget[mapDataIdx])
6882 mapFlag |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_PTR_AND_OBJ;
6883
6884 // if we're provided a mapDataParentIdx, then the data being mapped is
6885 // part of a larger object (in a parent <-> member mapping) and in this
6886 // case our BasePointer should be the parent. Except in the edge case
6887 // where we are mapping pointee data, where we try staying close to
6888 // what Clang currently does and utilise the regular base pointer of the
6889 // data.
6890 bool isRefPtee =
6891 !bitEnumContainsAll(mapInfoOp.getMapType(),
6892 omp::ClauseMapFlags::ref_ptr) &&
6893 bitEnumContainsAll(mapInfoOp.getMapType(), omp::ClauseMapFlags::ref_ptee);
6894 bool isRefPtrPtee = bitEnumContainsAll(mapInfoOp.getMapType(),
6895 omp::ClauseMapFlags::ref_ptr |
6896 omp::ClauseMapFlags::ref_ptee);
6897
6898 if (!mapInfoOp->getParentOfType<omp::DeclareMapperOp>() &&
6899 mapDataParentIdx >= 0 && !(isRefPtee || (isRefPtrPtee && isPtrTy))) {
6900 combinedInfo.BasePointers.emplace_back(
6901 mapData.BasePointers[mapDataParentIdx]);
6902 } else {
6903 combinedInfo.BasePointers.emplace_back(mapData.BasePointers[mapDataIdx]);
6904 }
6905
6906 combinedInfo.Pointers.emplace_back(mapData.Pointers[mapDataIdx]);
6907 combinedInfo.DevicePointers.emplace_back(
6908 memberOfFlag != llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE
6909 ? llvm::OpenMPIRBuilder::DeviceInfoTy::None
6910 : mapData.DevicePointers[mapDataIdx]);
6911 combinedInfo.Mappers.emplace_back(mapData.Mappers[mapDataIdx]);
6912 combinedInfo.Names.emplace_back(mapData.Names[mapDataIdx]);
6913 combinedInfo.Types.emplace_back(mapFlag);
6914 combinedInfo.Sizes.emplace_back(
6915 isPtrTy ? builder.CreateSelect(
6916 builder.CreateIsNull(mapData.Pointers[mapDataIdx]),
6917 builder.getInt64(0), mapData.Sizes[mapDataIdx])
6918 : mapData.Sizes[mapDataIdx]);
6919}
6920
6921// This creates two insertions into the MapInfosTy data structure for the
6922// "parent" of a set of members, (usually a container e.g.
6923// class/structure/derived type) when subsequent members have also been
6924// explicitly mapped on the same map clause. Certain types, such as Fortran
6925// descriptors are mapped like this as well, however, the members are
6926// implicit as far as a user is concerned, but we must explicitly map them
6927// internally.
6928//
6929// This function also returns the memberOfFlag for this particular parent,
6930// which is utilised in subsequent member mappings (by modifying there map type
6931// with it) to indicate that a member is part of this parent and should be
6932// treated by the runtime as such. Important to achieve the correct mapping.
6933//
6934// This function borrows a lot from Clang's emitCombinedEntry function
6935// inside of CGOpenMPRuntime.cpp
6937 LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder,
6938 llvm::OpenMPIRBuilder &ompBuilder, DataLayout &dl, MapInfosTy &combinedInfo,
6939 MapInfoData &mapData, uint64_t mapDataIndex,
6940 llvm::omp::OpenMPOffloadMappingFlags memberOfFlag,
6941 TargetDirectiveEnumTy targetDirective) {
6942 using MapFlags = llvm::omp::OpenMPOffloadMappingFlags;
6943 assert(!ompBuilder.Config.isTargetDevice() &&
6944 "function only supported for host device codegen");
6945 auto parentClause =
6946 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
6947 auto *parentMapper = mapData.Mappers[mapDataIndex];
6948
6949 // Map the first segment of the parent. If a user-defined mapper is attached,
6950 // include the parent's to/from-style bits (and common modifiers) in this
6951 // base entry so the mapper receives correct copy semantics via its 'type'
6952 // parameter. Also keep TARGET_PARAM when required for kernel arguments.
6953 MapFlags baseFlag = (targetDirective == TargetDirectiveEnumTy::Target &&
6954 !mapData.IsDeclareTarget[mapDataIndex])
6955 ? MapFlags::OMP_MAP_TARGET_PARAM
6956 : MapFlags::OMP_MAP_NONE;
6957
6958 if (parentMapper) {
6959 // Preserve relevant map-type bits from the parent clause. These include
6960 // the copy direction (TO/FROM), as well as commonly used modifiers that
6961 // should be visible to the mapper for correct behaviour.
6962 MapFlags parentFlags = mapData.Types[mapDataIndex];
6963 MapFlags preserve = MapFlags::OMP_MAP_TO | MapFlags::OMP_MAP_FROM |
6964 MapFlags::OMP_MAP_ALWAYS | MapFlags::OMP_MAP_CLOSE |
6965 MapFlags::OMP_MAP_PRESENT |
6966 MapFlags::OMP_MAP_OMPX_HOLD |
6967 MapFlags::OMP_MAP_IMPLICIT;
6968 baseFlag |= (parentFlags & preserve);
6969 } else {
6970 MapFlags parentFlags = mapData.Types[mapDataIndex];
6971 MapFlags preserve =
6972 MapFlags::OMP_MAP_PRESENT | MapFlags::OMP_MAP_RETURN_PARAM;
6973 baseFlag |= (parentFlags & preserve);
6974 }
6975
6976 combinedInfo.Types.emplace_back(baseFlag);
6977 combinedInfo.DevicePointers.emplace_back(
6978 mapData.DevicePointers[mapDataIndex]);
6979 // Only attach the mapper to the base entry when we are mapping the whole
6980 // parent. Combined/segment entries must not carry a mapper; otherwise the
6981 // mapper can be invoked with a partial size, which is undefined behaviour.
6982 combinedInfo.Mappers.emplace_back(
6983 parentMapper && !parentClause.getPartialMap() ? parentMapper : nullptr);
6984 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
6985 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder));
6986 combinedInfo.BasePointers.emplace_back(mapData.BasePointers[mapDataIndex]);
6987
6988 // Calculate size of the parent object being mapped based on the
6989 // addresses at runtime, highAddr - lowAddr = size. This of course
6990 // doesn't factor in allocated data like pointers, hence the further
6991 // processing of members specified by users, or in the case of
6992 // Fortran pointers and allocatables, the mapping of the pointed to
6993 // data by the descriptor (which itself, is a structure containing
6994 // runtime information on the dynamically allocated data).
6995 llvm::Value *lowAddr, *highAddr;
6996 if (!parentClause.getPartialMap()) {
6997 lowAddr = builder.CreatePointerCast(mapData.Pointers[mapDataIndex],
6998 builder.getPtrTy());
6999 highAddr = builder.CreatePointerCast(
7000 builder.CreateConstGEP1_32(mapData.BaseType[mapDataIndex],
7001 mapData.Pointers[mapDataIndex], 1),
7002 builder.getPtrTy());
7003 combinedInfo.Pointers.emplace_back(mapData.Pointers[mapDataIndex]);
7004 } else {
7005 auto mapOp = dyn_cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
7006 int firstMemberIdx = getMapDataMemberIdx(
7007 mapData, getFirstOrLastMappedMemberPtr(mapOp, true));
7008 lowAddr = builder.CreatePointerCast(mapData.BasePointers[firstMemberIdx],
7009 builder.getPtrTy());
7010
7011 int lastMemberIdx = getMapDataMemberIdx(
7012 mapData, getFirstOrLastMappedMemberPtr(mapOp, false));
7013 auto lastMemberMapInfo =
7014 cast<omp::MapInfoOp>(mapData.MapClause[lastMemberIdx]);
7015
7016 // NOTE: Currently, for RefPtee the BaseType is set to the varPtrPtr field,
7017 // which is the pointer datas type and not the member within the structure
7018 // that it's part of, so we have to make sure we use the member type in this
7019 // case when calculating the parents size offsets.
7020 // TODO: May be good to extend MapInfoData to support tracking of both
7021 // VarPtr/VarPtrPtr BaseType's to better distinguish what's being used more
7022 // consistently.
7023 bool isRefPteeMap = bitEnumContainsAll(lastMemberMapInfo.getMapType(),
7024 omp::ClauseMapFlags::ref_ptee) &&
7025 !bitEnumContainsAll(lastMemberMapInfo.getMapType(),
7026 omp::ClauseMapFlags::ref_ptr);
7027 llvm::Type *castType = mapData.BaseType[lastMemberIdx];
7028 if (isRefPteeMap)
7029 castType =
7030 moduleTranslation.convertType(lastMemberMapInfo.getVarPtrType());
7031 highAddr = builder.CreatePointerCast(
7032 builder.CreateGEP(castType, mapData.BasePointers[lastMemberIdx],
7033 builder.getInt64(1)),
7034 builder.getPtrTy());
7035 combinedInfo.Pointers.emplace_back(mapData.BasePointers[firstMemberIdx]);
7036 }
7037
7038 llvm::Value *size = builder.CreateIntCast(
7039 builder.CreatePtrDiff(builder.getInt8Ty(), highAddr, lowAddr),
7040 builder.getInt64Ty(),
7041 /*isSigned=*/false);
7042 combinedInfo.Sizes.push_back(size);
7043
7044 // This creates the initial MEMBER_OF mapping that consists of
7045 // the parent/top level container (same as above effectively, except
7046 // with a fixed initial compile time size and separate maptype which
7047 // indicates the true mape type (tofrom etc.). This parent mapping is
7048 // only relevant if the structure in its totality is being mapped,
7049 // otherwise the above suffices.
7050 if (!parentClause.getPartialMap()) {
7051 // TODO: This will need to be expanded to include the whole host of logic
7052 // for the map flags that Clang currently supports (e.g. it should do some
7053 // further case specific flag modifications). For the moment, it handles
7054 // what we support as expected.
7055 MapFlags mapFlag = mapData.Types[mapDataIndex];
7056 bool hasMapClose = (MapFlags(mapFlag) & MapFlags::OMP_MAP_CLOSE) ==
7057 MapFlags::OMP_MAP_CLOSE;
7058 ompBuilder.setCorrectMemberOfFlag(mapFlag, memberOfFlag);
7059
7060 llvm::SmallVector<size_t> overlapIdxs;
7061 // Find all of the members that "overlap", i.e. occlude other members that
7062 // were mapped alongside the parent, e.g. member [0], occludes [0,1] and
7063 // [0,2], but not [1,0].
7064 getOverlappedMembers(overlapIdxs, parentClause);
7065
7066 // When we only have one overlap we skip the case that tries to segment the
7067 // mapping as best it can without creating holes, as the calculation is more
7068 // likely to have more overhead than anything we gain from mapping a smaller
7069 // chunk of data. This can be seen in cases where we are mapping Fortran
7070 // descriptors which are a special case of record type mapping.
7071 //
7072 // The cases for close and update are unique edge cases where the segmenting
7073 // does not play well with the runtime currently.
7074 if (targetDirective == TargetDirectiveEnumTy::TargetUpdate || hasMapClose ||
7075 overlapIdxs.size() == 1) {
7076 combinedInfo.Types.emplace_back(mapFlag);
7077 combinedInfo.DevicePointers.emplace_back(
7078 mapData.DevicePointers[mapDataIndex]);
7079 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
7080 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder));
7081 combinedInfo.BasePointers.emplace_back(
7082 mapData.BasePointers[mapDataIndex]);
7083 combinedInfo.Pointers.emplace_back(mapData.Pointers[mapDataIndex]);
7084 combinedInfo.Sizes.emplace_back(mapData.Sizes[mapDataIndex]);
7085 combinedInfo.Mappers.emplace_back(nullptr);
7086 } else {
7087 // We need to make sure the overlapped members are sorted in order of
7088 // lowest address to highest address.
7089 sortMapIndices(overlapIdxs, parentClause);
7090
7091 lowAddr = builder.CreatePointerCast(mapData.Pointers[mapDataIndex],
7092 builder.getPtrTy());
7093 highAddr = builder.CreatePointerCast(
7094 builder.CreateConstGEP1_32(mapData.BaseType[mapDataIndex],
7095 mapData.Pointers[mapDataIndex], 1),
7096 builder.getPtrTy());
7097
7098 // Currently, the return parameter should be the over-riding parent in
7099 // cases where we have a return parameter that is echoed to all members,
7100 // the main case of this currently is with fortran descriptors. It may
7101 // need more finessing for C/C++ in the future or descriptors that are
7102 // members of derived types.
7103 mapFlag &= ~llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
7104
7105 // TODO: We may want to skip arrays/array sections in this as Clang does.
7106 // It appears to be an optimisation rather than a necessity though,
7107 // but this requires further investigation. However, we would have to make
7108 // sure to not exclude maps with bounds that ARE pointers, as these are
7109 // processed as separate components, i.e. pointer + data.
7110 for (auto v : overlapIdxs) {
7111 auto mapDataOverlapIdx = getMapDataMemberIdx(
7112 mapData,
7113 cast<omp::MapInfoOp>(parentClause.getMembers()[v].getDefiningOp()));
7114 auto isPtrMap = checkIfPointerMap(
7115 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataOverlapIdx]));
7116 combinedInfo.Types.emplace_back(mapFlag);
7117 combinedInfo.DevicePointers.emplace_back(
7118 llvm::OpenMPIRBuilder::DeviceInfoTy::None);
7119 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
7120 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder));
7121 combinedInfo.BasePointers.emplace_back(
7122 mapData.BasePointers[mapDataIndex]);
7123 combinedInfo.Mappers.emplace_back(nullptr);
7124 combinedInfo.Pointers.emplace_back(lowAddr);
7125 auto sizeCalc = builder.CreateIntCast(
7126 builder.CreatePtrDiff(builder.getInt8Ty(),
7127 mapData.OriginalValue[mapDataOverlapIdx],
7128 lowAddr),
7129 builder.getInt64Ty(), /*isSigned=*/true);
7130 // In certain cases, we'll generate a size of 0 if we're not careful
7131 // (e.g. if lowAddr happens to be the first member), which isn't
7132 // correct, even if the runtimes is sometimes fine with it so, in these
7133 // scenarios we select the types size instead.
7134 auto sizeSel = builder.CreateSelect(
7135 builder.CreateICmpNE(builder.getInt64(0), sizeCalc), sizeCalc,
7136 isPtrMap ? llvm::ConstantExpr::getSizeOf(builder.getPtrTy())
7137 : mapData.Sizes[mapDataOverlapIdx]);
7138 combinedInfo.Sizes.emplace_back(sizeSel);
7139 lowAddr = builder.CreateConstGEP1_32(
7140 isPtrMap ? builder.getPtrTy() : mapData.BaseType[mapDataOverlapIdx],
7141 mapData.BasePointers[mapDataOverlapIdx], 1);
7142 }
7143
7144 combinedInfo.Types.emplace_back(mapFlag);
7145 combinedInfo.DevicePointers.emplace_back(
7146 llvm::OpenMPIRBuilder::DeviceInfoTy::None);
7147 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
7148 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder));
7149 combinedInfo.BasePointers.emplace_back(
7150 mapData.BasePointers[mapDataIndex]);
7151 combinedInfo.Mappers.emplace_back(nullptr);
7152 combinedInfo.Pointers.emplace_back(lowAddr);
7153 combinedInfo.Sizes.emplace_back(builder.CreateIntCast(
7154 builder.CreatePtrDiff(builder.getInt8Ty(), highAddr, lowAddr),
7155 builder.getInt64Ty(), true));
7156 }
7157 }
7158}
7159
7161 llvm::IRBuilderBase &builder,
7162 llvm::OpenMPIRBuilder &ompBuilder,
7163 DataLayout &dl, MapInfosTy &combinedInfo,
7164 MapInfoData &mapData, uint64_t mapDataIndex,
7165 TargetDirectiveEnumTy targetDirective) {
7166 assert(!ompBuilder.Config.isTargetDevice() &&
7167 "function only supported for host device codegen");
7168
7169 auto parentClause =
7170 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
7171
7172 // If we have a partial map (no parent referenced in the map clauses of the
7173 // directive, only members) and only a single member, we do not need to bind
7174 // the map of the member to the parent, we can pass the member separately.
7175 if (parentClause.getMembers().size() == 1 && parentClause.getPartialMap()) {
7176 auto memberClause = llvm::cast<omp::MapInfoOp>(
7177 parentClause.getMembers()[0].getDefiningOp());
7178 int memberDataIdx = getMapDataMemberIdx(mapData, memberClause);
7179 // Note: Clang treats arrays with explicit bounds that fall into this
7180 // category as a parent with map case, however, it seems this isn't a
7181 // requirement, and processing them as an individual map is fine. So,
7182 // we will handle them as individual maps for the moment, as it's
7183 // difficult for us to check this as we always require bounds to be
7184 // specified currently and it's also marginally more optimal (single
7185 // map rather than two). The difference may come from the fact that
7186 // Clang maps array without bounds as pointers (which we do not
7187 // currently do), whereas we treat them as arrays in all cases
7188 // currently.
7190 builder, ompBuilder, mapData, memberDataIdx, combinedInfo,
7191 targetDirective,
7192 /*MemberOfFlag=*/llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE,
7193 /*isTargetParam=*/true, mapDataIndex);
7194 return;
7195 }
7196
7197 auto collectMapInfoIdxs =
7198 [&](llvm::SmallVectorImpl<int64_t> &mapsAndInfoIdx) {
7199 auto parentClause =
7200 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
7201 mapsAndInfoIdx.push_back(getMapDataMemberIdx(mapData, parentClause));
7202 for (auto member : parentClause.getMembers())
7203 mapsAndInfoIdx.push_back(getMapDataMemberIdx(
7204 mapData, llvm::cast<omp::MapInfoOp>(member.getDefiningOp())));
7205 };
7206
7207 llvm::SmallVector<int64_t> mapInfoIdx;
7208 collectMapInfoIdxs(mapInfoIdx);
7209
7210 llvm::omp::OpenMPOffloadMappingFlags memberOfFlag =
7211 ompBuilder.getMemberOfFlag(combinedInfo.Types.size());
7212 for (size_t i = 0; i < mapInfoIdx.size(); i++) {
7213 // Index == 0 is the parent map and if it gets here it's an unattachable
7214 // type and should have OMP_MAP_TARGET_PARAM applied and no MEMBER_OF flag.
7215 if (i == 0) {
7216 mapParentWithMembers(moduleTranslation, builder, ompBuilder, dl,
7217 combinedInfo, mapData, mapInfoIdx[i], memberOfFlag,
7218 targetDirective);
7219 } else {
7220 processIndividualMap(builder, ompBuilder, mapData, mapInfoIdx[i],
7221 combinedInfo, targetDirective, memberOfFlag,
7222 /*isTargetParam=*/false, mapDataIndex);
7223 }
7224 }
7225}
7226
7227// This is a variation on Clang's GenerateOpenMPCapturedVars, which
7228// generates different operation (e.g. load/store) combinations for
7229// arguments to the kernel, based on map capture kinds which are then
7230// utilised in the combinedInfo in place of the original Map value.
7231static void
7232createAlteredByCaptureMap(MapInfoData &mapData,
7233 LLVM::ModuleTranslation &moduleTranslation,
7234 llvm::IRBuilderBase &builder) {
7235 assert(!moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
7236 "function only supported for host device codegen");
7237 for (size_t i = 0; i < mapData.MapClause.size(); ++i) {
7238 auto mapOp = cast<omp::MapInfoOp>(mapData.MapClause[i]);
7239 bool isAttachMap =
7240 ((convertClauseMapFlags(mapOp.getMapType()) &
7241 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) ==
7242 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH);
7243
7244 // If it's declare target, skip it, it's handled separately. However, if
7245 // it's declare target, and an attach map, we want to calculate the exact
7246 // address offset so that we attach correctly.
7247 if (!mapData.IsDeclareTarget[i] ||
7248 (mapData.IsDeclareTarget[i] && isAttachMap)) {
7249 omp::VariableCaptureKind captureKind = mapOp.getMapCaptureType();
7250 bool isPtrTy = checkIfPointerMap(mapOp);
7251
7252 // Currently handles array sectioning lowerbound case, but more
7253 // logic may be required in the future. Clang invokes EmitLValue,
7254 // which has specialised logic for special Clang types such as user
7255 // defines, so it is possible we will have to extend this for
7256 // structures or other complex types. As the general idea is that this
7257 // function mimics some of the logic from Clang that we require for
7258 // kernel argument passing from host -> device.
7259 switch (captureKind) {
7260 case omp::VariableCaptureKind::ByRef: {
7261 llvm::Value *newV = mapData.Pointers[i];
7262 std::vector<llvm::Value *> offsetIdx = calculateBoundsOffset(
7263 moduleTranslation, builder, mapData.BaseType[i]->isArrayTy(),
7264 mapOp.getBounds());
7265 if (isPtrTy)
7266 newV = builder.CreateLoad(builder.getPtrTy(), newV);
7267
7268 if (!offsetIdx.empty())
7269 newV = builder.CreateInBoundsGEP(mapData.BaseType[i], newV, offsetIdx,
7270 "array_offset");
7271 mapData.Pointers[i] = newV;
7272 } break;
7273 case omp::VariableCaptureKind::ByCopy: {
7274 llvm::Type *type = mapData.BaseType[i];
7275 llvm::Value *newV;
7276 if (mapData.Pointers[i]->getType()->isPointerTy())
7277 newV = builder.CreateLoad(type, mapData.Pointers[i]);
7278 else
7279 newV = mapData.Pointers[i];
7280
7281 if (!isPtrTy) {
7282 auto curInsert = builder.saveIP();
7283 llvm::DebugLoc DbgLoc = builder.getCurrentDebugLocation();
7284 builder.restoreIP(findAllocInsertPoints(builder, moduleTranslation));
7285 auto *memTempAlloc =
7286 builder.CreateAlloca(builder.getPtrTy(), nullptr, ".casted");
7287 builder.SetCurrentDebugLocation(DbgLoc);
7288 builder.restoreIP(curInsert);
7289
7290 builder.CreateStore(newV, memTempAlloc);
7291 newV = builder.CreateLoad(builder.getPtrTy(), memTempAlloc);
7292 }
7293
7294 mapData.Pointers[i] = newV;
7295 mapData.BasePointers[i] = newV;
7296 } break;
7297 case omp::VariableCaptureKind::This:
7298 case omp::VariableCaptureKind::VLAType:
7299 mapData.MapClause[i]->emitOpError("Unhandled capture kind");
7300 break;
7301 }
7302 }
7303 }
7304}
7305
7306// Generate all map related information and fill the combinedInfo.
7307static void genMapInfos(llvm::IRBuilderBase &builder,
7308 LLVM::ModuleTranslation &moduleTranslation,
7309 DataLayout &dl, MapInfosTy &combinedInfo,
7310 MapInfoData &mapData,
7311 TargetDirectiveEnumTy targetDirective) {
7312 assert(!moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
7313 "function only supported for host device codegen");
7314 // We wish to modify some of the methods in which arguments are
7315 // passed based on their capture type by the target region, this can
7316 // involve generating new loads and stores, which changes the
7317 // MLIR value to LLVM value mapping, however, we only wish to do this
7318 // locally for the current function/target and also avoid altering
7319 // ModuleTranslation, so we remap the base pointer or pointer stored
7320 // in the map infos corresponding MapInfoData, which is later accessed
7321 // by genMapInfos and createTarget to help generate the kernel and
7322 // kernel arg structure. It primarily becomes relevant in cases like
7323 // bycopy, or byref range'd arrays. In the default case, we simply
7324 // pass thee pointer byref as both basePointer and pointer.
7325 createAlteredByCaptureMap(mapData, moduleTranslation, builder);
7326
7327 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
7328
7329 // We operate under the assumption that all vectors that are
7330 // required in MapInfoData are of equal lengths (either filled with
7331 // default constructed data or appropiate information) so we can
7332 // utilise the size from any component of MapInfoData, if we can't
7333 // something is missing from the initial MapInfoData construction.
7334 for (size_t i = 0; i < mapData.MapClause.size(); ++i) {
7335 if (mapData.IsAMember[i])
7336 continue;
7337
7338 auto mapInfoOp = dyn_cast<omp::MapInfoOp>(mapData.MapClause[i]);
7339 if (!mapInfoOp.getMembers().empty()) {
7340 processMapWithMembersOf(moduleTranslation, builder, *ompBuilder, dl,
7341 combinedInfo, mapData, i, targetDirective);
7342 continue;
7343 }
7344
7345 processIndividualMap(builder, *ompBuilder, mapData, i, combinedInfo,
7346 targetDirective);
7347 }
7348}
7349
7350static llvm::Expected<llvm::Function *>
7351emitUserDefinedMapper(Operation *declMapperOp, llvm::IRBuilderBase &builder,
7352 LLVM::ModuleTranslation &moduleTranslation,
7353 llvm::StringRef mapperFuncName,
7354 TargetDirectiveEnumTy targetDirective);
7355
7356static llvm::Expected<llvm::Function *>
7357getOrCreateUserDefinedMapperFunc(Operation *op, llvm::IRBuilderBase &builder,
7358 LLVM::ModuleTranslation &moduleTranslation,
7359 TargetDirectiveEnumTy targetDirective) {
7360 assert(!moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
7361 "function only supported for host device codegen");
7362 auto declMapperOp = cast<omp::DeclareMapperOp>(op);
7363 std::string mapperFuncName =
7364 moduleTranslation.getOpenMPBuilder()->createPlatformSpecificName(
7365 {"omp_mapper", declMapperOp.getSymName()});
7366
7367 if (auto *lookupFunc = moduleTranslation.lookupFunction(mapperFuncName))
7368 return lookupFunc;
7369
7370 // Recursive types can cause re-entrant mapper emission. The mapper function
7371 // is created by OpenMPIRBuilder before the callbacks run, so it may already
7372 // exist in the LLVM module even though it is not yet registered in the
7373 // ModuleTranslation mapping table. Reuse and register it to break the
7374 // recursion.
7375 if (llvm::Function *existingFunc =
7376 moduleTranslation.getLLVMModule()->getFunction(mapperFuncName)) {
7377 moduleTranslation.mapFunction(mapperFuncName, existingFunc);
7378 return existingFunc;
7379 }
7380
7381 return emitUserDefinedMapper(declMapperOp, builder, moduleTranslation,
7382 mapperFuncName, targetDirective);
7383}
7384
7385static llvm::Expected<llvm::Function *>
7386emitUserDefinedMapper(Operation *op, llvm::IRBuilderBase &builder,
7387 LLVM::ModuleTranslation &moduleTranslation,
7388 llvm::StringRef mapperFuncName,
7389 TargetDirectiveEnumTy targetDirective) {
7390 assert(!moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
7391 "function only supported for host device codegen");
7392 auto declMapperOp = cast<omp::DeclareMapperOp>(op);
7393 auto declMapperInfoOp = declMapperOp.getDeclareMapperInfo();
7394 if (failed(checkImplementationStatus(*declMapperInfoOp)))
7395 return llvm::make_error<PreviouslyReportedError>();
7396
7397 DataLayout dl = DataLayout(declMapperOp->getParentOfType<ModuleOp>());
7398 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
7399 llvm::Type *varType = moduleTranslation.convertType(declMapperOp.getType());
7400 SmallVector<Value> mapVars = declMapperInfoOp.getMapVars();
7401
7402 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
7403
7404 // Fill up the arrays with all the mapped variables.
7405 MapInfosTy combinedInfo;
7406 auto genMapInfoCB =
7407 [&](InsertPointTy codeGenIP, llvm::Value *ptrPHI,
7408 llvm::Value *unused2) -> llvm::OpenMPIRBuilder::MapInfosOrErrorTy {
7409 builder.restoreIP(codeGenIP);
7410 moduleTranslation.mapValue(declMapperOp.getSymVal(), ptrPHI);
7411 moduleTranslation.mapBlock(&declMapperOp.getRegion().front(),
7412 builder.GetInsertBlock());
7413 if (failed(moduleTranslation.convertBlock(declMapperOp.getRegion().front(),
7414 /*ignoreArguments=*/true,
7415 builder)))
7416 return llvm::make_error<PreviouslyReportedError>();
7417 MapInfoData mapData;
7418 collectMapDataFromMapOperands(mapData, mapVars, moduleTranslation, dl,
7419 builder);
7420 genMapInfos(builder, moduleTranslation, dl, combinedInfo, mapData,
7421 targetDirective);
7422
7423 // Drop the mapping that is no longer necessary so that the same region
7424 // can be processed multiple times.
7425 moduleTranslation.forgetMapping(declMapperOp.getRegion());
7426 return combinedInfo;
7427 };
7428
7429 auto customMapperCB = [&](unsigned i) -> llvm::Expected<llvm::Function *> {
7430 if (!combinedInfo.Mappers[i])
7431 return nullptr;
7432 return getOrCreateUserDefinedMapperFunc(combinedInfo.Mappers[i], builder,
7433 moduleTranslation, targetDirective);
7434 };
7435
7436 llvm::Expected<llvm::Function *> newFn = ompBuilder->emitUserDefinedMapper(
7437 genMapInfoCB, varType, mapperFuncName, customMapperCB,
7438 /*PreserveMemberOfFlags=*/true);
7439 if (!newFn)
7440 return newFn.takeError();
7441 if ([[maybe_unused]] llvm::Function *mappedFunc =
7442 moduleTranslation.lookupFunction(mapperFuncName)) {
7443 assert(mappedFunc == *newFn &&
7444 "mapper function mapping disagrees with emitted function");
7445 } else {
7446 moduleTranslation.mapFunction(mapperFuncName, *newFn);
7447 }
7448 return *newFn;
7449}
7450
7451static LogicalResult
7452convertOmpTargetData(Operation *op, llvm::IRBuilderBase &builder,
7453 LLVM::ModuleTranslation &moduleTranslation) {
7454 llvm::Value *ifCond = nullptr;
7455 llvm::Value *deviceID = builder.getInt64(llvm::omp::OMP_DEVICEID_UNDEF);
7456 SmallVector<Value> mapVars;
7457 SmallVector<Value> useDevicePtrVars;
7458 SmallVector<Value> useDeviceAddrVars;
7459 llvm::omp::RuntimeFunction RTLFn;
7460 DataLayout DL = DataLayout(op->getParentOfType<ModuleOp>());
7461 TargetDirectiveEnumTy targetDirective = getTargetDirectiveEnumTyFromOp(op);
7462
7463 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
7464 llvm::OpenMPIRBuilder::TargetDataInfo info(
7465 /*RequiresDevicePointerInfo=*/true,
7466 /*SeparateBeginEndCalls=*/true);
7467 assert(!ompBuilder->Config.isTargetDevice() &&
7468 "target data/enter/exit/update are host ops");
7469 bool isOffloadEntry = !ompBuilder->Config.TargetTriples.empty();
7470
7471 auto getDeviceID = [&](mlir::Value dev) -> llvm::Value * {
7472 llvm::Value *v = moduleTranslation.lookupValue(dev);
7473 return builder.CreateIntCast(v, builder.getInt64Ty(), /*isSigned=*/true);
7474 };
7475
7476 LogicalResult result =
7478 .Case([&](omp::TargetDataOp dataOp) {
7479 if (failed(checkImplementationStatus(*dataOp)))
7480 return failure();
7481
7482 if (auto ifVar = dataOp.getIfExpr())
7483 ifCond = moduleTranslation.lookupValue(ifVar);
7484
7485 if (mlir::Value devId = dataOp.getDevice())
7486 deviceID = getDeviceID(devId);
7487
7488 mapVars = dataOp.getMapVars();
7489 useDevicePtrVars = dataOp.getUseDevicePtrVars();
7490 useDeviceAddrVars = dataOp.getUseDeviceAddrVars();
7491 return success();
7492 })
7493 .Case([&](omp::TargetEnterDataOp enterDataOp) -> LogicalResult {
7494 if (failed(checkImplementationStatus(*enterDataOp)))
7495 return failure();
7496
7497 if (auto ifVar = enterDataOp.getIfExpr())
7498 ifCond = moduleTranslation.lookupValue(ifVar);
7499
7500 if (mlir::Value devId = enterDataOp.getDevice())
7501 deviceID = getDeviceID(devId);
7502
7503 RTLFn =
7504 enterDataOp.getNowait()
7505 ? llvm::omp::OMPRTL___tgt_target_data_begin_nowait_mapper
7506 : llvm::omp::OMPRTL___tgt_target_data_begin_mapper;
7507 mapVars = enterDataOp.getMapVars();
7508 info.HasNoWait = enterDataOp.getNowait();
7509 return success();
7510 })
7511 .Case([&](omp::TargetExitDataOp exitDataOp) -> LogicalResult {
7512 if (failed(checkImplementationStatus(*exitDataOp)))
7513 return failure();
7514
7515 if (auto ifVar = exitDataOp.getIfExpr())
7516 ifCond = moduleTranslation.lookupValue(ifVar);
7517
7518 if (mlir::Value devId = exitDataOp.getDevice())
7519 deviceID = getDeviceID(devId);
7520
7521 RTLFn = exitDataOp.getNowait()
7522 ? llvm::omp::OMPRTL___tgt_target_data_end_nowait_mapper
7523 : llvm::omp::OMPRTL___tgt_target_data_end_mapper;
7524 mapVars = exitDataOp.getMapVars();
7525 info.HasNoWait = exitDataOp.getNowait();
7526 return success();
7527 })
7528 .Case([&](omp::TargetUpdateOp updateDataOp) -> LogicalResult {
7529 if (failed(checkImplementationStatus(*updateDataOp)))
7530 return failure();
7531
7532 if (auto ifVar = updateDataOp.getIfExpr())
7533 ifCond = moduleTranslation.lookupValue(ifVar);
7534
7535 if (mlir::Value devId = updateDataOp.getDevice())
7536 deviceID = getDeviceID(devId);
7537
7538 RTLFn =
7539 updateDataOp.getNowait()
7540 ? llvm::omp::OMPRTL___tgt_target_data_update_nowait_mapper
7541 : llvm::omp::OMPRTL___tgt_target_data_update_mapper;
7542 mapVars = updateDataOp.getMapVars();
7543 info.HasNoWait = updateDataOp.getNowait();
7544 return success();
7545 })
7546 .DefaultUnreachable("unexpected operation");
7547
7548 if (failed(result))
7549 return failure();
7550 // Pretend we have IF(false) if we're not doing offload.
7551 if (!isOffloadEntry)
7552 ifCond = builder.getFalse();
7553
7554 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
7555 MapInfoData mapData;
7556 collectMapDataFromMapOperands(mapData, mapVars, moduleTranslation, DL,
7557 builder, useDevicePtrVars, useDeviceAddrVars);
7558
7559 // Fill up the arrays with all the mapped variables.
7560 MapInfosTy combinedInfo;
7561 auto genMapInfoCB = [&](InsertPointTy codeGenIP) -> MapInfosTy & {
7562 builder.restoreIP(codeGenIP);
7563 genMapInfos(builder, moduleTranslation, DL, combinedInfo, mapData,
7564 targetDirective);
7565 return combinedInfo;
7566 };
7567
7568 // Define a lambda to apply mappings between use_device_addr and
7569 // use_device_ptr base pointers, and their associated block arguments.
7570 auto mapUseDevice =
7571 [&moduleTranslation](
7572 llvm::OpenMPIRBuilder::DeviceInfoTy type,
7574 llvm::SmallVectorImpl<Value> &useDeviceVars, MapInfoData &mapInfoData,
7575 llvm::function_ref<llvm::Value *(llvm::Value *)> mapper = nullptr) {
7576 for (auto [arg, useDevVar] :
7577 llvm::zip_equal(blockArgs, useDeviceVars)) {
7578
7579 auto getMapBasePtr = [](omp::MapInfoOp mapInfoOp) {
7580 return mapInfoOp.getVarPtrPtr() ? mapInfoOp.getVarPtrPtr()
7581 : mapInfoOp.getVarPtr();
7582 };
7583
7584 auto useDevMap = cast<omp::MapInfoOp>(useDevVar.getDefiningOp());
7585 for (auto [mapClause, devicePointer, basePointer] : llvm::zip_equal(
7586 mapInfoData.MapClause, mapInfoData.DevicePointers,
7587 mapInfoData.BasePointers)) {
7588 auto mapOp = cast<omp::MapInfoOp>(mapClause);
7589 if (getMapBasePtr(mapOp) != getMapBasePtr(useDevMap) ||
7590 devicePointer != type)
7591 continue;
7592
7593 if (llvm::Value *devPtrInfoMap =
7594 mapper ? mapper(basePointer) : basePointer) {
7595 moduleTranslation.mapValue(arg, devPtrInfoMap);
7596 break;
7597 }
7598 }
7599 }
7600 };
7601
7602 using BodyGenTy = llvm::OpenMPIRBuilder::BodyGenTy;
7603 auto bodyGenCB = [&](InsertPointTy codeGenIP, BodyGenTy bodyGenType)
7604 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
7605 // We must always restoreIP regardless of doing anything the caller
7606 // does not restore it, leading to incorrect (no) branch generation.
7607 builder.restoreIP(codeGenIP);
7608 assert(isa<omp::TargetDataOp>(op) &&
7609 "BodyGen requested for non TargetDataOp");
7610 auto blockArgIface = cast<omp::BlockArgOpenMPOpInterface>(op);
7611 Region &region = cast<omp::TargetDataOp>(op).getRegion();
7612 switch (bodyGenType) {
7613 case BodyGenTy::Priv:
7614 // Check if any device ptr/addr info is available
7615 if (!info.DevicePtrInfoMap.empty()) {
7616 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Address,
7617 blockArgIface.getUseDeviceAddrBlockArgs(),
7618 useDeviceAddrVars, mapData,
7619 [&](llvm::Value *basePointer) -> llvm::Value * {
7620 if (!info.DevicePtrInfoMap[basePointer].second)
7621 return nullptr;
7622 return builder.CreateLoad(
7623 builder.getPtrTy(),
7624 info.DevicePtrInfoMap[basePointer].second);
7625 });
7626 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer,
7627 blockArgIface.getUseDevicePtrBlockArgs(), useDevicePtrVars,
7628 mapData, [&](llvm::Value *basePointer) {
7629 return info.DevicePtrInfoMap[basePointer].second;
7630 });
7631
7632 if (failed(inlineConvertOmpRegions(region, "omp.data.region", builder,
7633 moduleTranslation)))
7634 return llvm::make_error<PreviouslyReportedError>();
7635 }
7636 break;
7637 case BodyGenTy::DupNoPriv:
7638 if (info.DevicePtrInfoMap.empty()) {
7639 // For host device we still need to do the mapping for codegen,
7640 // otherwise it may try to lookup a missing value.
7641 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Address,
7642 blockArgIface.getUseDeviceAddrBlockArgs(),
7643 useDeviceAddrVars, mapData);
7644 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer,
7645 blockArgIface.getUseDevicePtrBlockArgs(), useDevicePtrVars,
7646 mapData);
7647 }
7648 break;
7649 case BodyGenTy::NoPriv:
7650 // If device info is available then region has already been generated
7651 if (info.DevicePtrInfoMap.empty()) {
7652 if (failed(inlineConvertOmpRegions(region, "omp.data.region", builder,
7653 moduleTranslation)))
7654 return llvm::make_error<PreviouslyReportedError>();
7655 }
7656 break;
7657 }
7658 return builder.saveIP();
7659 };
7660
7661 auto customMapperCB =
7662 [&](unsigned int i) -> llvm::Expected<llvm::Function *> {
7663 if (!combinedInfo.Mappers[i])
7664 return nullptr;
7665 info.HasMapper = true;
7666 return getOrCreateUserDefinedMapperFunc(combinedInfo.Mappers[i], builder,
7667 moduleTranslation, targetDirective);
7668 };
7669
7670 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
7672 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
7673 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
7674 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP = [&]() {
7675 if (isa<omp::TargetDataOp>(op))
7676 return ompBuilder->createTargetData(ompLoc, allocaIP, builder.saveIP(),
7677 deallocBlocks, deviceID, ifCond, info,
7678 genMapInfoCB, customMapperCB,
7679 /*MapperFunc=*/nullptr, bodyGenCB,
7680 /*DeviceAddrCB=*/nullptr);
7681 return ompBuilder->createTargetData(ompLoc, allocaIP, builder.saveIP(),
7682 deallocBlocks, deviceID, ifCond, info,
7683 genMapInfoCB, customMapperCB, &RTLFn);
7684 }();
7685
7686 if (failed(handleError(afterIP, *op)))
7687 return failure();
7688
7689 builder.restoreIP(*afterIP);
7690 return success();
7691}
7692
7693static LogicalResult
7694convertOmpDistribute(Operation &opInst, llvm::IRBuilderBase &builder,
7695 LLVM::ModuleTranslation &moduleTranslation) {
7696 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
7697 auto distributeOp = cast<omp::DistributeOp>(opInst);
7698 if (failed(checkImplementationStatus(opInst)))
7699 return failure();
7700
7701 /// Process teams op reduction in distribute if the reduction is contained in
7702 /// this specific distribute op.
7703 omp::TeamsOp teamsOp = opInst.getParentOfType<omp::TeamsOp>();
7704 bool doDistributeReduction =
7705 teamsOp && getDistributeCapturingTeamsReduction(teamsOp) == distributeOp;
7706
7707 DenseMap<Value, llvm::Value *> reductionVariableMap;
7708 unsigned numReductionVars = teamsOp ? teamsOp.getNumReductionVars() : 0;
7710 SmallVector<llvm::Value *> privateReductionVariables(numReductionVars);
7711 llvm::ArrayRef<bool> isByRef;
7712
7713 if (doDistributeReduction) {
7714 isByRef = getIsByRef(teamsOp.getReductionByref());
7715 assert(isByRef.size() == teamsOp.getNumReductionVars());
7716
7717 collectReductionDecls(teamsOp, reductionDecls);
7718 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
7719 findAllocInsertPoints(builder, moduleTranslation);
7720
7721 MutableArrayRef<BlockArgument> reductionArgs =
7722 llvm::cast<omp::BlockArgOpenMPOpInterface>(*teamsOp)
7723 .getReductionBlockArgs();
7724
7726 teamsOp, reductionArgs, builder, moduleTranslation, allocaIP,
7727 reductionDecls, privateReductionVariables, reductionVariableMap,
7728 isByRef)))
7729 return failure();
7730 }
7731
7732 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
7733 auto bodyGenCB =
7734 [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
7735 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
7736 // Save the alloca insertion point on ModuleTranslation stack for use in
7737 // nested regions.
7739 moduleTranslation, allocaIP, deallocBlocks);
7740
7741 // DistributeOp has only one region associated with it.
7742 builder.restoreIP(codeGenIP);
7743 PrivateVarsInfo privVarsInfo(distributeOp);
7744
7746 distributeOp, builder, moduleTranslation, privVarsInfo, allocaIP);
7747 if (handleError(afterAllocas, opInst).failed())
7748 return llvm::make_error<PreviouslyReportedError>();
7749
7750 if (handleError(initPrivateVars(builder, moduleTranslation, privVarsInfo),
7751 opInst)
7752 .failed())
7753 return llvm::make_error<PreviouslyReportedError>();
7754
7755 if (failed(copyFirstPrivateVars(
7756 distributeOp, builder, moduleTranslation, privVarsInfo.mlirVars,
7757 privVarsInfo.llvmVars, privVarsInfo.privatizers,
7758 distributeOp.getPrivateNeedsBarrier())))
7759 return llvm::make_error<PreviouslyReportedError>();
7760
7761 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
7762 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
7764 convertOmpOpRegions(distributeOp.getRegion(), "omp.distribute.region",
7765 builder, moduleTranslation);
7766 if (!regionBlock)
7767 return regionBlock.takeError();
7768 builder.SetInsertPoint(*regionBlock, (*regionBlock)->begin());
7769
7770 // Skip applying a workshare loop below when translating 'distribute
7771 // parallel do' (it's been already handled by this point while translating
7772 // the nested omp.wsloop).
7773 if (!isa_and_present<omp::WsloopOp>(distributeOp.getNestedWrapper())) {
7774 // TODO: Add support for clauses which are valid for DISTRIBUTE
7775 // constructs. Static schedule is the default.
7776 bool hasDistSchedule = distributeOp.getDistScheduleStatic();
7777 auto schedule = hasDistSchedule ? omp::ClauseScheduleKind::Distribute
7778 : omp::ClauseScheduleKind::Static;
7779 // dist_schedule clauses are ordered - otherise this should be false
7780 bool isOrdered = hasDistSchedule;
7781 std::optional<omp::ScheduleModifier> scheduleMod;
7782 bool isSimd = false;
7783 llvm::omp::WorksharingLoopType workshareLoopType =
7784 llvm::omp::WorksharingLoopType::DistributeStaticLoop;
7785 bool loopNeedsBarrier = false;
7786 llvm::Value *chunk = moduleTranslation.lookupValue(
7787 distributeOp.getDistScheduleChunkSize());
7788 llvm::CanonicalLoopInfo *loopInfo =
7789 findCurrentLoopInfo(moduleTranslation);
7790 llvm::OpenMPIRBuilder::InsertPointOrErrorTy wsloopIP =
7791 ompBuilder->applyWorkshareLoop(
7792 ompLoc.DL, loopInfo, allocaIP, loopNeedsBarrier,
7793 convertToScheduleKind(schedule), chunk, isSimd,
7794 scheduleMod == omp::ScheduleModifier::monotonic,
7795 scheduleMod == omp::ScheduleModifier::nonmonotonic, isOrdered,
7796 workshareLoopType, false, hasDistSchedule, chunk);
7797
7798 if (!wsloopIP)
7799 return wsloopIP.takeError();
7800 }
7801 if (failed(cleanupPrivateVars(distributeOp, builder, moduleTranslation,
7802 distributeOp.getLoc(), privVarsInfo)))
7803 return llvm::make_error<PreviouslyReportedError>();
7804
7805 return llvm::Error::success();
7806 };
7807
7809 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
7810 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
7811 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
7812 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
7813 ompBuilder->createDistribute(ompLoc, allocaIP, deallocBlocks, bodyGenCB);
7814
7815 if (failed(handleError(afterIP, opInst)))
7816 return failure();
7817
7818 builder.restoreIP(*afterIP);
7819
7820 if (doDistributeReduction) {
7821 // Process the reductions if required.
7823 teamsOp, builder, moduleTranslation, allocaIP, reductionDecls,
7824 privateReductionVariables, isByRef,
7825 /*isNoWait*/ false, /*isTeamsReduction*/ true);
7826 }
7827 return success();
7828}
7829
7830/// Lowers the FlagsAttr which is applied to the module when offloading. This
7831/// attribute contains OpenMP RTL globals that can be passed as flags to the
7832/// frontend, otherwise they are set to default
7833static LogicalResult
7834convertFlagsAttr(Operation *op, mlir::omp::FlagsAttr attribute,
7835 LLVM::ModuleTranslation &moduleTranslation) {
7836 auto offloadMod = dyn_cast<omp::OffloadModuleInterface>(op);
7837 if (!offloadMod)
7838 return op->emitOpError() << "omp flags attached to non offload module op";
7839
7840 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
7841
7842 if (offloadMod.getIsTargetDevice())
7843 ompBuilder->M.addModuleFlag(llvm::Module::Max, "openmp-device",
7844 attribute.getOpenmpDeviceVersion());
7845
7846 // The flags below are only intended to be emitted for GPU offload targets.
7847 if (!offloadMod.getIsGPU())
7848 return success();
7849
7850 if (attribute.getNoGpuLib())
7851 return success();
7852
7853 ompBuilder->createGlobalFlag(
7854 attribute.getDebugKind() /*LangOpts().OpenMPTargetDebug*/,
7855 "__omp_rtl_debug_kind");
7856 ompBuilder->createGlobalFlag(
7857 attribute
7858 .getAssumeTeamsOversubscription() /*LangOpts().OpenMPTeamSubscription*/
7859 ,
7860 "__omp_rtl_assume_teams_oversubscription");
7861 ompBuilder->createGlobalFlag(
7862 attribute
7863 .getAssumeThreadsOversubscription() /*LangOpts().OpenMPThreadSubscription*/
7864 ,
7865 "__omp_rtl_assume_threads_oversubscription");
7866 ompBuilder->createGlobalFlag(
7867 attribute.getAssumeNoThreadState() /*LangOpts().OpenMPNoThreadState*/,
7868 "__omp_rtl_assume_no_thread_state");
7869 ompBuilder->createGlobalFlag(
7870 attribute
7871 .getAssumeNoNestedParallelism() /*LangOpts().OpenMPNoNestedParallelism*/
7872 ,
7873 "__omp_rtl_assume_no_nested_parallelism");
7874 return success();
7875}
7876
7877static void getTargetEntryUniqueInfo(llvm::TargetRegionEntryInfo &targetInfo,
7878 omp::TargetOp targetOp,
7879 llvm::OpenMPIRBuilder &ompBuilder,
7880 llvm::vfs::FileSystem &vfs,
7881 llvm::StringRef parentName = "") {
7882 auto fileLoc = targetOp.getLoc()->findInstanceOf<FileLineColLoc>();
7883 assert(fileLoc && "No file found from location");
7884
7885 auto fileInfoCallBack = [&fileLoc]() {
7886 return std::pair<std::string, uint64_t>(
7887 llvm::StringRef(fileLoc.getFilename()), fileLoc.getLine());
7888 };
7889
7890 targetInfo =
7891 ompBuilder.getTargetEntryUniqueInfo(fileInfoCallBack, vfs, parentName);
7892}
7893
7894static void
7895handleDeclareTargetMapVar(MapInfoData &mapData,
7896 LLVM::ModuleTranslation &moduleTranslation,
7897 llvm::IRBuilderBase &builder, llvm::Function *func) {
7898 assert(moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice() &&
7899 "function only supported for target device codegen");
7900 llvm::IRBuilderBase::InsertPointGuard guard(builder);
7901 for (size_t i = 0; i < mapData.MapClause.size(); ++i) {
7902 // In the case of declare target mapped variables, the basePointer is
7903 // the reference pointer generated by the convertDeclareTargetAttr
7904 // method. Whereas the kernelValue is the original variable, so for
7905 // the device we must replace all uses of this original global variable
7906 // (stored in kernelValue) with the reference pointer (stored in
7907 // basePointer for declare target mapped variables), as for device the
7908 // data is mapped into this reference pointer and should be loaded
7909 // from it, the original variable is discarded. On host both exist and
7910 // metadata is generated (elsewhere in the convertDeclareTargetAttr)
7911 // function to link the two variables in the runtime and then both the
7912 // reference pointer and the pointer are assigned in the kernel argument
7913 // structure for the host.
7914 if (!mapData.IsDeclareTarget[i])
7915 continue;
7916 // If the original map value is a constant, then we have to make sure all
7917 // of it's uses within the current kernel/function that we are going to
7918 // rewrite are converted to instructions, as we will be altering the old
7919 // use (OriginalValue) from a constant to an instruction, which will be
7920 // illegal and ICE the compiler if the user is a constant expression of
7921 // some kind e.g. a constant GEP.
7922 if (auto *constant = dyn_cast<llvm::Constant>(mapData.OriginalValue[i]))
7923 convertUsersOfConstantsToInstructions(constant, func, false);
7924
7925 // The users iterator will get invalidated if we modify an element,
7926 // so we populate this vector of uses to alter each user on an
7927 // individual basis to emit its own load (rather than one load for
7928 // all).
7930 for (llvm::User *user : mapData.OriginalValue[i]->users())
7931 userVec.push_back(user);
7932
7933 for (llvm::User *user : userVec) {
7934 auto *insn = dyn_cast<llvm::Instruction>(user);
7935 if (!insn || insn->getFunction() != func)
7936 continue;
7937 auto mapOp = cast<omp::MapInfoOp>(mapData.MapClause[i]);
7938 llvm::Value *substitute = mapData.BasePointers[i];
7939 auto declTarPtr =
7940 mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtr() : mapOp.getVarPtr();
7941 if (isDeclareTargetLink(declTarPtr) ||
7942 (isDeclareTargetTo(declTarPtr) &&
7943 moduleTranslation.getOpenMPBuilder()
7944 ->Config.hasRequiresUnifiedSharedMemory())) {
7945 builder.SetCurrentDebugLocation(insn->getDebugLoc());
7946 substitute = builder.CreateLoad(mapData.BasePointers[i]->getType(),
7947 mapData.BasePointers[i]);
7948 cast<llvm::LoadInst>(substitute)->moveBefore(insn->getIterator());
7949 }
7950 user->replaceUsesOfWith(mapData.OriginalValue[i], substitute);
7951 }
7952 }
7953}
7954
7955// The createDeviceArgumentAccessor function generates
7956// instructions for retrieving (acessing) kernel
7957// arguments inside of the device kernel for use by
7958// the kernel. This enables different semantics such as
7959// the creation of temporary copies of data allowing
7960// semantics like read-only/no host write back kernel
7961// arguments.
7962//
7963// This currently implements a very light version of Clang's
7964// EmitParmDecl's handling of direct argument handling as well
7965// as a portion of the argument access generation based on
7966// capture types found at the end of emitOutlinedFunctionPrologue
7967// in Clang. The indirect path handling of EmitParmDecl's may be
7968// required for future work, but a direct 1-to-1 copy doesn't seem
7969// possible as the logic is rather scattered throughout Clang's
7970// lowering and perhaps we wish to deviate slightly.
7971//
7972// \param mapData - A container containing vectors of information
7973// corresponding to the input argument, which should have a
7974// corresponding entry in the MapInfoData containers
7975// OrigialValue's.
7976// \param arg - This is the generated kernel function argument that
7977// corresponds to the passed in input argument. We generated different
7978// accesses of this Argument, based on capture type and other Input
7979// related information.
7980// \param input - This is the host side value that will be passed to
7981// the kernel i.e. the kernel input, we rewrite all uses of this within
7982// the kernel (as we generate the kernel body based on the target's region
7983// which maintians references to the original input) to the retVal argument
7984// apon exit of this function inside of the OMPIRBuilder. This interlinks
7985// the kernel argument to future uses of it in the function providing
7986// appropriate "glue" instructions inbetween.
7987// \param retVal - This is the value that all uses of input inside of the
7988// kernel will be re-written to, the goal of this function is to generate
7989// an appropriate location for the kernel argument to be accessed from,
7990// e.g. ByRef will result in a temporary allocation location and then
7991// a store of the kernel argument into this allocated memory which
7992// will then be loaded from, ByCopy will use the allocated memory
7993// directly.
7994static llvm::IRBuilderBase::InsertPoint createDeviceArgumentAccessor(
7995 omp::TargetOp targetOp, MapInfoData &mapData, llvm::Argument &arg,
7996 llvm::Value *input, llvm::Value *&retVal, llvm::IRBuilderBase &builder,
7997 llvm::OpenMPIRBuilder &ompBuilder,
7998 LLVM::ModuleTranslation &moduleTranslation,
7999 llvm::IRBuilderBase::InsertPoint allocaIP,
8000 llvm::IRBuilderBase::InsertPoint codeGenIP,
8002 assert(ompBuilder.Config.isTargetDevice() &&
8003 "function only supported for target device codegen");
8004 builder.restoreIP(allocaIP);
8005
8006 omp::VariableCaptureKind capture = omp::VariableCaptureKind::ByRef;
8007 LLVM::TypeToLLVMIRTranslator typeToLLVMIRTranslator(
8008 ompBuilder.M.getContext());
8009 unsigned alignmentValue = 0;
8010 BlockArgument mlirArg;
8012 cast<omp::BlockArgOpenMPOpInterface>(*targetOp).getBlockArgsPairs(
8013 blockArgsPairs);
8014 // Find the associated MapInfoData entry for the current input
8015 for (size_t i = 0; i < mapData.MapClause.size(); ++i) {
8016 if (mapData.OriginalValue[i] == input) {
8017 auto mapOp = cast<omp::MapInfoOp>(mapData.MapClause[i]);
8018 capture = mapOp.getMapCaptureType();
8019 // Get information of alignment of mapped object
8020 alignmentValue = typeToLLVMIRTranslator.getPreferredAlignment(
8021 mapOp.getVarPtrType(), ompBuilder.M.getDataLayout());
8022
8023 // Find the corresponding entry block argument, which can be associated to
8024 // a map, use_device* or has_device* clause.
8025 for (auto &[val, arg] : blockArgsPairs) {
8026 if (mapOp.getResult() == val) {
8027 mlirArg = arg;
8028 break;
8029 }
8030 }
8031 assert(mlirArg && "expected to find entry block argument for map clause");
8032 break;
8033 }
8034 }
8035
8036 unsigned int allocaAS = ompBuilder.M.getDataLayout().getAllocaAddrSpace();
8037 unsigned int defaultAS =
8038 ompBuilder.M.getDataLayout().getProgramAddressSpace();
8039
8040 // Create the allocation for the argument.
8041 llvm::Value *v = nullptr;
8042 if (omp::opInSharedDeviceContext(*targetOp) &&
8044 // Use the beginning of the codeGenIP rather than the usual allocation point
8045 // for shared memory allocations because otherwise these would be done prior
8046 // to the target initialization call. Also, the exit block (where the
8047 // deallocation is placed) is only executed if the initialization call
8048 // succeeds.
8049 builder.SetInsertPoint(codeGenIP.getBlock()->getFirstInsertionPt());
8050 v = ompBuilder.createOMPAllocShared(builder, arg.getType());
8051
8052 // Create deallocations in all provided deallocation points and then restore
8053 // the insertion point to right after the new allocations.
8054 llvm::IRBuilderBase::InsertPointGuard guard(builder);
8055 for (auto deallocIP : deallocIPs) {
8056 builder.SetInsertPoint(deallocIP.getBlock(), deallocIP.getPoint());
8057 ompBuilder.createOMPFreeShared(builder, v, arg.getType());
8058 }
8059 } else {
8060 // Use the current point, which was previously set to allocaIP.
8061 v = builder.CreateAlloca(arg.getType(), allocaAS);
8062
8063 if (allocaAS != defaultAS && arg.getType()->isPointerTy())
8064 v = builder.CreateAddrSpaceCast(v, builder.getPtrTy(defaultAS));
8065 }
8066
8067 builder.CreateStore(&arg, v);
8068
8069 builder.restoreIP(codeGenIP);
8070
8071 switch (capture) {
8072 case omp::VariableCaptureKind::ByCopy: {
8073 retVal = v;
8074 break;
8075 }
8076 case omp::VariableCaptureKind::ByRef: {
8077 llvm::LoadInst *loadInst = builder.CreateAlignedLoad(
8078 v->getType(), v,
8079 ompBuilder.M.getDataLayout().getPrefTypeAlign(v->getType()));
8080 // CreateAlignedLoad function creates similar LLVM IR:
8081 // %res = load ptr, ptr %input, align 8
8082 // This LLVM IR does not contain information about alignment
8083 // of the loaded value. We need to add !align metadata to unblock
8084 // optimizer. The existence of the !align metadata on the instruction
8085 // tells the optimizer that the value loaded is known to be aligned to
8086 // a boundary specified by the integer value in the metadata node.
8087 // Example:
8088 // %res = load ptr, ptr %input, align 8, !align !align_md_node
8089 // ^ ^
8090 // | |
8091 // alignment of %input address |
8092 // |
8093 // alignment of %res object
8094 if (v->getType()->isPointerTy() && alignmentValue) {
8095 llvm::MDBuilder MDB(builder.getContext());
8096 loadInst->setMetadata(
8097 llvm::LLVMContext::MD_align,
8098 llvm::MDNode::get(builder.getContext(),
8099 MDB.createConstant(llvm::ConstantInt::get(
8100 llvm::Type::getInt64Ty(builder.getContext()),
8101 alignmentValue))));
8102 }
8103 retVal = loadInst;
8104
8105 break;
8106 }
8107 case omp::VariableCaptureKind::This:
8108 case omp::VariableCaptureKind::VLAType:
8109 // TODO: Consider returning error to use standard reporting for
8110 // unimplemented features.
8111 assert(false && "Currently unsupported capture kind");
8112 break;
8113 }
8114
8115 return builder.saveIP();
8116}
8117
8118/// Follow uses of `host_eval`-defined block arguments of the given `omp.target`
8119/// operation and populate output variables with their corresponding host value
8120/// (i.e. operand evaluated outside of the target region), based on their uses
8121/// inside of the target region.
8122///
8123/// Loop bounds and steps are only optionally populated, if output vectors are
8124/// provided.
8125static void
8126extractHostEvalClauses(omp::TargetOp targetOp, Value &numThreads,
8127 Value &numTeamsLower, Value &numTeamsUpper,
8128 Value &threadLimit,
8129 llvm::SmallVectorImpl<Value> *lowerBounds = nullptr,
8130 llvm::SmallVectorImpl<Value> *upperBounds = nullptr,
8131 llvm::SmallVectorImpl<Value> *steps = nullptr) {
8132 auto blockArgIface = llvm::cast<omp::BlockArgOpenMPOpInterface>(*targetOp);
8133 for (auto item : llvm::zip_equal(targetOp.getHostEvalVars(),
8134 blockArgIface.getHostEvalBlockArgs())) {
8135 Value hostEvalVar = std::get<0>(item), blockArg = std::get<1>(item);
8136
8137 for (Operation *user : blockArg.getUsers()) {
8139 .Case([&](omp::TeamsOp teamsOp) {
8140 if (teamsOp.getNumTeamsLower() == blockArg)
8141 numTeamsLower = hostEvalVar;
8142 else if (llvm::is_contained(teamsOp.getNumTeamsUpperVars(),
8143 blockArg))
8144 numTeamsUpper = hostEvalVar;
8145 else if (!teamsOp.getThreadLimitVars().empty() &&
8146 teamsOp.getThreadLimit(0) == blockArg)
8147 threadLimit = hostEvalVar;
8148 else
8149 llvm_unreachable("unsupported host_eval use");
8150 })
8151 .Case([&](omp::ParallelOp parallelOp) {
8152 if (!parallelOp.getNumThreadsVars().empty() &&
8153 parallelOp.getNumThreads(0) == blockArg)
8154 numThreads = hostEvalVar;
8155 else
8156 llvm_unreachable("unsupported host_eval use");
8157 })
8158 .Case([&](omp::LoopNestOp loopOp) {
8159 auto processBounds =
8160 [&](OperandRange opBounds,
8161 llvm::SmallVectorImpl<Value> *outBounds) -> bool {
8162 bool found = false;
8163 for (auto [i, lb] : llvm::enumerate(opBounds)) {
8164 if (lb == blockArg) {
8165 found = true;
8166 if (outBounds)
8167 (*outBounds)[i] = hostEvalVar;
8168 }
8169 }
8170 return found;
8171 };
8172 bool found =
8173 processBounds(loopOp.getLoopLowerBounds(), lowerBounds);
8174 found = processBounds(loopOp.getLoopUpperBounds(), upperBounds) ||
8175 found;
8176 found = processBounds(loopOp.getLoopSteps(), steps) || found;
8177 (void)found;
8178 assert(found && "unsupported host_eval use");
8179 })
8180 .DefaultUnreachable("unsupported host_eval use");
8181 }
8182 }
8183}
8184
8185/// If \p op is of the given type parameter, return it casted to that type.
8186/// Otherwise, if its immediate parent operation (or some other higher-level
8187/// parent, if \p immediateParent is false) is of that type, return that parent
8188/// casted to the given type.
8189///
8190/// If \p op is \c null or neither it or its parent(s) are of the specified
8191/// type, return a \c null operation.
8192template <typename OpTy>
8193static OpTy castOrGetParentOfType(Operation *op, bool immediateParent = false) {
8194 if (!op)
8195 return OpTy();
8196
8197 if (OpTy casted = dyn_cast<OpTy>(op))
8198 return casted;
8199
8200 if (immediateParent)
8201 return dyn_cast_if_present<OpTy>(op->getParentOp());
8202
8203 return op->getParentOfType<OpTy>();
8204}
8205
8206/// If the given \p value is defined by an \c llvm.mlir.constant operation and
8207/// it is of an integer type, return its value.
8208static std::optional<int64_t> extractConstInteger(Value value) {
8209 if (!value)
8210 return std::nullopt;
8211
8212 if (auto constOp = value.getDefiningOp<LLVM::ConstantOp>())
8213 if (auto constAttr = dyn_cast<IntegerAttr>(constOp.getValue()))
8214 return constAttr.getInt();
8215
8216 return std::nullopt;
8217}
8218
8219static uint64_t getTypeByteSize(mlir::Type type, const DataLayout &dl) {
8220 uint64_t sizeInBits = dl.getTypeSizeInBits(type);
8221 uint64_t sizeInBytes = sizeInBits / 8;
8222 return sizeInBytes;
8223}
8224
8225template <typename OpTy>
8226static uint64_t getReductionDataSize(OpTy &op) {
8227 if (op.getNumReductionVars() > 0) {
8229 collectReductionDecls(op, reductions);
8230
8232 members.reserve(reductions.size());
8233 for (omp::DeclareReductionOp &red : reductions) {
8234 // For by-ref reductions, use the actual element type rather than the
8235 // pointer type so that the buffer size matches the access pattern in
8236 // the copy/reduce callbacks generated by OMPIRBuilder.
8237 if (red.getByrefElementType())
8238 members.push_back(*red.getByrefElementType());
8239 else
8240 members.push_back(red.getType());
8241 }
8242 Operation *opp = op.getOperation();
8243 auto structType = mlir::LLVM::LLVMStructType::getLiteral(
8244 opp->getContext(), members, /*isPacked=*/false);
8245 DataLayout dl = DataLayout(opp->getParentOfType<ModuleOp>());
8246 return getTypeByteSize(structType, dl);
8247 }
8248 return 0;
8249}
8250
8251/// Populate default `MinTeams`, `MaxTeams` and `MaxThreads` to their default
8252/// values as stated by the corresponding clauses, if constant.
8253///
8254/// These default values must be set before the creation of the outlined LLVM
8255/// function for the target region, so that they can be used to initialize the
8256/// corresponding global `ConfigurationEnvironmentTy` structure.
8257static void
8258initTargetDefaultAttrs(omp::TargetOp targetOp, Operation *capturedOp,
8259 llvm::OpenMPIRBuilder::TargetKernelDefaultAttrs &attrs,
8260 bool isTargetDevice, bool isGPU) {
8261 // TODO: Handle constant 'if' clauses.
8262
8263 Value numThreads, numTeamsLower, numTeamsUpper, threadLimit;
8264 if (!isTargetDevice) {
8265 extractHostEvalClauses(targetOp, numThreads, numTeamsLower, numTeamsUpper,
8266 threadLimit);
8267 } else {
8268 // In the target device, values for these clauses are not passed as
8269 // host_eval, but instead evaluated prior to entry to the region. This
8270 // ensures values are mapped and available inside of the target region.
8271 if (auto teamsOp = castOrGetParentOfType<omp::TeamsOp>(capturedOp)) {
8272 numTeamsLower = teamsOp.getNumTeamsLower();
8273 // Handle num_teams upper bounds (only first value for now)
8274 if (!teamsOp.getNumTeamsUpperVars().empty())
8275 numTeamsUpper = teamsOp.getNumTeams(0);
8276 if (!teamsOp.getThreadLimitVars().empty())
8277 threadLimit = teamsOp.getThreadLimit(0);
8278 }
8279
8280 if (auto parallelOp = castOrGetParentOfType<omp::ParallelOp>(capturedOp)) {
8281 if (!parallelOp.getNumThreadsVars().empty())
8282 numThreads = parallelOp.getNumThreads(0);
8283 }
8284 }
8285
8286 // Handle clauses impacting the number of teams.
8287
8288 int32_t minTeamsVal = 1, maxTeamsVal = -1;
8289 if (castOrGetParentOfType<omp::TeamsOp>(capturedOp)) {
8290 // TODO: Use `hostNumTeamsLower` to initialize `minTeamsVal`. For now,
8291 // match clang and set min and max to the same value.
8292 if (numTeamsUpper) {
8293 if (auto val = extractConstInteger(numTeamsUpper))
8294 minTeamsVal = maxTeamsVal = *val;
8295 } else {
8296 minTeamsVal = maxTeamsVal = 0;
8297 }
8298 } else if (castOrGetParentOfType<omp::ParallelOp>(capturedOp,
8299 /*immediateParent=*/true) ||
8301 /*immediateParent=*/true)) {
8302 minTeamsVal = maxTeamsVal = 1;
8303 } else {
8304 minTeamsVal = maxTeamsVal = -1;
8305 }
8306
8307 // Handle clauses impacting the number of threads.
8308
8309 auto setMaxValueFromClause = [](Value clauseValue, int32_t &result) {
8310 if (!clauseValue)
8311 return;
8312
8313 if (auto val = extractConstInteger(clauseValue))
8314 result = *val;
8315
8316 // Found an applicable clause, so it's not undefined. Mark as unknown
8317 // because it's not constant.
8318 if (result < 0)
8319 result = 0;
8320 };
8321
8322 // Extract 'thread_limit' clause from 'target' and 'teams' directives.
8323 int32_t targetThreadLimitVal = -1, teamsThreadLimitVal = -1;
8324 if (!targetOp.getThreadLimitVars().empty())
8325 setMaxValueFromClause(targetOp.getThreadLimit(0), targetThreadLimitVal);
8326 setMaxValueFromClause(threadLimit, teamsThreadLimitVal);
8327
8328 // Extract 'max_threads' clause from 'parallel' or set to 1 if it's SIMD.
8329 int32_t maxThreadsVal = -1;
8331 setMaxValueFromClause(numThreads, maxThreadsVal);
8332 else if (castOrGetParentOfType<omp::SimdOp>(capturedOp,
8333 /*immediateParent=*/true))
8334 maxThreadsVal = 1;
8335
8336 // For max values, < 0 means unset, == 0 means set but unknown. Select the
8337 // minimum value between 'max_threads' and 'thread_limit' clauses that were
8338 // set.
8339 int32_t combinedMaxThreadsVal = targetThreadLimitVal;
8340 if (combinedMaxThreadsVal < 0 ||
8341 (teamsThreadLimitVal >= 0 && teamsThreadLimitVal < combinedMaxThreadsVal))
8342 combinedMaxThreadsVal = teamsThreadLimitVal;
8343
8344 if (combinedMaxThreadsVal < 0 ||
8345 (maxThreadsVal >= 0 && maxThreadsVal < combinedMaxThreadsVal))
8346 combinedMaxThreadsVal = maxThreadsVal;
8347
8348 int32_t reductionDataSize = 0;
8349 if (isGPU && capturedOp) {
8350 if (auto teamsOp = castOrGetParentOfType<omp::TeamsOp>(capturedOp))
8351 reductionDataSize = getReductionDataSize(teamsOp);
8352 }
8353
8354 // Update kernel bounds structure for the `OpenMPIRBuilder` to use.
8355 // Use the kernel_type attribute set by the frontend instead of analyzing IR.
8356 omp::TargetExecMode execMode = targetOp.getKernelType();
8357 switch (execMode) {
8358 case omp::TargetExecMode::bare:
8359 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_BARE;
8360 break;
8361 case omp::TargetExecMode::generic:
8362 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_GENERIC;
8363 break;
8364 case omp::TargetExecMode::spmd:
8365 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_SPMD;
8366 break;
8367 case omp::TargetExecMode::spmd_no_loop:
8368 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_SPMD_NO_LOOP;
8369 break;
8370 }
8371 attrs.MinTeams = minTeamsVal;
8372 attrs.MaxTeams.front() = maxTeamsVal;
8373 attrs.MinThreads = 1;
8374 attrs.MaxThreads.front() = combinedMaxThreadsVal;
8375 attrs.ReductionDataSize = reductionDataSize;
8376}
8377
8378/// Gather LLVM runtime values for all clauses evaluated in the host that are
8379/// passed to the kernel invocation.
8380///
8381/// This function must be called only when compiling for the host. Also, it will
8382/// only provide correct results if it's called after the body of \c targetOp
8383/// has been fully generated.
8384static void
8385initTargetRuntimeAttrs(llvm::IRBuilderBase &builder,
8386 LLVM::ModuleTranslation &moduleTranslation,
8387 omp::TargetOp targetOp, Operation *capturedOp,
8388 llvm::OpenMPIRBuilder::TargetKernelRuntimeAttrs &attrs) {
8389 omp::LoopNestOp loopOp = castOrGetParentOfType<omp::LoopNestOp>(capturedOp);
8390 unsigned numLoops = loopOp ? loopOp.getNumLoops() : 0;
8391
8392 Value numThreads, numTeamsLower, numTeamsUpper, teamsThreadLimit;
8393 llvm::SmallVector<Value> lowerBounds(numLoops), upperBounds(numLoops),
8394 steps(numLoops);
8395 extractHostEvalClauses(targetOp, numThreads, numTeamsLower, numTeamsUpper,
8396 teamsThreadLimit, &lowerBounds, &upperBounds, &steps);
8397
8398 // TODO: Handle constant 'if' clauses.
8399 if (!targetOp.getThreadLimitVars().empty()) {
8400 Value targetThreadLimit = targetOp.getThreadLimit(0);
8401 attrs.TargetThreadLimit.front() =
8402 moduleTranslation.lookupValue(targetThreadLimit);
8403 }
8404
8405 // The __kmpc_push_num_teams_51 function expects int32 as the arguments. So,
8406 // truncate or sign extend lower and upper num_teams bounds as well as
8407 // thread_limit to match int32 ABI requirements for the OpenMP runtime.
8408 if (numTeamsLower)
8409 attrs.MinTeams = builder.CreateSExtOrTrunc(
8410 moduleTranslation.lookupValue(numTeamsLower), builder.getInt32Ty());
8411
8412 if (numTeamsUpper)
8413 attrs.MaxTeams.front() = builder.CreateSExtOrTrunc(
8414 moduleTranslation.lookupValue(numTeamsUpper), builder.getInt32Ty());
8415
8416 if (teamsThreadLimit)
8417 attrs.TeamsThreadLimit.front() = builder.CreateSExtOrTrunc(
8418 moduleTranslation.lookupValue(teamsThreadLimit), builder.getInt32Ty());
8419
8420 if (numThreads)
8421 attrs.MaxThreads = moduleTranslation.lookupValue(numThreads);
8422
8423 if (targetOp.hasHostEvalTripCount()) {
8424 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8425 attrs.LoopTripCount = nullptr;
8426
8427 // To calculate the trip count, we multiply together the trip counts of
8428 // every collapsed canonical loop. We don't need to create the loop nests
8429 // here, since we're only interested in the trip count.
8430 for (auto [loopLower, loopUpper, loopStep] :
8431 llvm::zip_equal(lowerBounds, upperBounds, steps)) {
8432 llvm::Value *lowerBound = moduleTranslation.lookupValue(loopLower);
8433 llvm::Value *upperBound = moduleTranslation.lookupValue(loopUpper);
8434 llvm::Value *step = moduleTranslation.lookupValue(loopStep);
8435
8436 if (!lowerBound || !upperBound || !step) {
8437 attrs.LoopTripCount = nullptr;
8438 break;
8439 }
8440
8441 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
8442 llvm::Value *tripCount = ompBuilder->calculateCanonicalLoopTripCount(
8443 loc, lowerBound, upperBound, step, /*IsSigned=*/true,
8444 loopOp.getLoopInclusive());
8445
8446 if (!attrs.LoopTripCount) {
8447 attrs.LoopTripCount = tripCount;
8448 continue;
8449 }
8450
8451 // TODO: Enable UndefinedSanitizer to diagnose an overflow here.
8452 attrs.LoopTripCount = builder.CreateMul(attrs.LoopTripCount, tripCount,
8453 {}, /*HasNUW=*/true);
8454 }
8455 }
8456
8457 attrs.DeviceID = builder.getInt64(llvm::omp::OMP_DEVICEID_UNDEF);
8458 if (mlir::Value devId = targetOp.getDevice()) {
8459 attrs.DeviceID = moduleTranslation.lookupValue(devId);
8460 attrs.DeviceID =
8461 builder.CreateSExtOrTrunc(attrs.DeviceID, builder.getInt64Ty());
8462 }
8463}
8464
8465static llvm::omp::OMPDynGroupprivateFallbackType
8466getDynGroupprivateFallbackType(omp::FallbackModifierAttr fallbackAttr) {
8467 omp::FallbackModifier fb = fallbackAttr ? fallbackAttr.getValue()
8468 : omp::FallbackModifier::default_mem;
8469 switch (fb) {
8470 case omp::FallbackModifier::abort:
8471 return llvm::omp::OMPDynGroupprivateFallbackType::Abort;
8472 case omp::FallbackModifier::null:
8473 return llvm::omp::OMPDynGroupprivateFallbackType::Null;
8474 case omp::FallbackModifier::default_mem:
8475 return llvm::omp::OMPDynGroupprivateFallbackType::DefaultMem;
8476 }
8477
8478 llvm_unreachable("unexpected dyn_groupprivate fallback type");
8479}
8480
8481static LogicalResult
8482convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
8483 LLVM::ModuleTranslation &moduleTranslation) {
8484 auto targetOp = cast<omp::TargetOp>(opInst);
8485
8486 // The current debug location already has the DISubprogram for the outlined
8487 // function that will be created for the target op. We save it here so that
8488 // we can set it on the outlined function.
8489 llvm::DebugLoc outlinedFnLoc = builder.getCurrentDebugLocation();
8490 if (failed(checkImplementationStatus(opInst)))
8491 return failure();
8492
8493 // During the handling of target op, we will generate instructions in the
8494 // parent function like call to the oulined function or branch to a new
8495 // BasicBlock. We set the debug location here to parent function so that those
8496 // get the correct debug locations. For outlined functions, the normal MLIR op
8497 // conversion will automatically pick the correct location.
8498 llvm::BasicBlock *parentBB = builder.GetInsertBlock();
8499 assert(parentBB && "No insert block is set for the builder");
8500 llvm::Function *parentLLVMFn = parentBB->getParent();
8501 assert(parentLLVMFn && "Parent Function must be valid");
8502 if (llvm::DISubprogram *SP = parentLLVMFn->getSubprogram())
8503 builder.SetCurrentDebugLocation(llvm::DILocation::get(
8504 parentLLVMFn->getContext(), outlinedFnLoc.getLine(),
8505 outlinedFnLoc.getCol(), SP, outlinedFnLoc.getInlinedAt()));
8506
8507 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8508 bool isTargetDevice = ompBuilder->Config.isTargetDevice();
8509 bool isGPU = ompBuilder->Config.isGPU();
8510
8511 auto parentFn = opInst.getParentOfType<LLVM::LLVMFuncOp>();
8512 auto argIface = cast<omp::BlockArgOpenMPOpInterface>(opInst);
8513 auto &targetRegion = targetOp.getRegion();
8514 // Holds the private vars that have been mapped along with the block
8515 // argument that corresponds to the MapInfoOp corresponding to the private
8516 // var in question. So, for instance:
8517 //
8518 // %10 = omp.map.info var_ptr(%6#0 : !fir.ref<!fir.box<!fir.heap<i32>>>, ..)
8519 // omp.target map_entries(%10 -> %arg0) private(@box.privatizer %6#0-> %arg1)
8520 //
8521 // Then, %10 has been created so that the descriptor can be used by the
8522 // privatizer @box.privatizer on the device side. Here we'd record {%6#0,
8523 // %arg0} in the mappedPrivateVars map.
8524 llvm::DenseMap<Value, Value> mappedPrivateVars;
8525 DataLayout dl = DataLayout(opInst.getParentOfType<ModuleOp>());
8526 SmallVector<Value> mapVars = targetOp.getMapVars();
8527 SmallVector<Value> hdaVars = targetOp.getHasDeviceAddrVars();
8528 ArrayRef<BlockArgument> mapBlockArgs = argIface.getMapBlockArgs();
8529 ArrayRef<BlockArgument> hdaBlockArgs = argIface.getHasDeviceAddrBlockArgs();
8530 llvm::Function *llvmOutlinedFn = nullptr;
8531 TargetDirectiveEnumTy targetDirective =
8532 getTargetDirectiveEnumTyFromOp(&opInst);
8533
8534 // TODO: It can also be false if a compile-time constant `false` IF clause is
8535 // specified.
8536 bool isOffloadEntry =
8537 isTargetDevice || !ompBuilder->Config.TargetTriples.empty();
8538
8539 // For some private variables, the MapsForPrivatizedVariablesPass
8540 // creates MapInfoOp instances. Go through the private variables and
8541 // the mapped variables so that during codegeneration we are able
8542 // to quickly look up the corresponding map variable, if any for each
8543 // private variable.
8544 if (!targetOp.getPrivateVars().empty() && !targetOp.getMapVars().empty()) {
8545 OperandRange privateVars = targetOp.getPrivateVars();
8546 std::optional<ArrayAttr> privateSyms = targetOp.getPrivateSyms();
8547 std::optional<DenseI64ArrayAttr> privateMapIndices =
8548 targetOp.getPrivateMapsAttr();
8549
8550 for (auto [privVarIdx, privVarSymPair] :
8551 llvm::enumerate(llvm::zip_equal(privateVars, *privateSyms))) {
8552 auto privVar = std::get<0>(privVarSymPair);
8553 auto privSym = std::get<1>(privVarSymPair);
8554
8555 SymbolRefAttr privatizerName = llvm::cast<SymbolRefAttr>(privSym);
8556 omp::PrivateClauseOp privatizer =
8557 findPrivatizer(targetOp, privatizerName);
8558
8559 if (!privatizer.needsMap())
8560 continue;
8561
8562 mlir::Value mappedValue =
8563 targetOp.getMappedValueForPrivateVar(privVarIdx);
8564 assert(mappedValue && "Expected to find mapped value for a privatized "
8565 "variable that needs mapping");
8566
8567 // The MapInfoOp defining the map var isn't really needed later.
8568 // So, we don't store it in any datastructure. Instead, we just
8569 // do some sanity checks on it right now.
8570 auto mapInfoOp = mappedValue.getDefiningOp<omp::MapInfoOp>();
8571 [[maybe_unused]] Type varType = mapInfoOp.getVarPtrType();
8572
8573 // Check #1: Check that the type of the private variable matches
8574 // the type of the variable being mapped.
8575 if (!isa<LLVM::LLVMPointerType>(privVar.getType()))
8576 assert(
8577 varType == privVar.getType() &&
8578 "Type of private var doesn't match the type of the mapped value");
8579
8580 // Ok, only 1 sanity check for now.
8581 // Record the block argument corresponding to this mapvar.
8582 mappedPrivateVars.insert(
8583 {privVar,
8584 targetRegion.getArgument(argIface.getMapBlockArgsStart() +
8585 (*privateMapIndices)[privVarIdx])});
8586 }
8587 }
8588
8589 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
8590 auto bodyCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
8591 ArrayRef<llvm::BasicBlock *> deallocBlocks)
8592 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
8593 llvm::IRBuilderBase::InsertPointGuard guard(builder);
8594 builder.SetCurrentDebugLocation(llvm::DebugLoc());
8595 // Forward target-cpu and target-features function attributes from the
8596 // original function to the new outlined function.
8597 llvm::Function *llvmParentFn =
8598 moduleTranslation.lookupFunction(parentFn.getName());
8599 llvmOutlinedFn = codeGenIP.getBlock()->getParent();
8600 assert(llvmParentFn && llvmOutlinedFn &&
8601 "Both parent and outlined functions must exist at this point");
8602
8603 if (outlinedFnLoc && llvmParentFn->getSubprogram())
8604 llvmOutlinedFn->setSubprogram(outlinedFnLoc->getScope()->getSubprogram());
8605
8606 if (auto attr = llvmParentFn->getFnAttribute("target-cpu");
8607 attr.isStringAttribute())
8608 llvmOutlinedFn->addFnAttr(attr);
8609
8610 if (auto attr = llvmParentFn->getFnAttribute("target-features");
8611 attr.isStringAttribute())
8612 llvmOutlinedFn->addFnAttr(attr);
8613
8614 for (auto [arg, mapOp] : llvm::zip_equal(mapBlockArgs, mapVars)) {
8615 auto mapInfoOp = cast<omp::MapInfoOp>(mapOp.getDefiningOp());
8616 llvm::Value *mapOpValue =
8617 moduleTranslation.lookupValue(mapInfoOp.getVarPtr());
8618 moduleTranslation.mapValue(arg, mapOpValue);
8619 }
8620 for (auto [arg, mapOp] : llvm::zip_equal(hdaBlockArgs, hdaVars)) {
8621 auto mapInfoOp = cast<omp::MapInfoOp>(mapOp.getDefiningOp());
8622 llvm::Value *mapOpValue =
8623 moduleTranslation.lookupValue(mapInfoOp.getVarPtr());
8624 moduleTranslation.mapValue(arg, mapOpValue);
8625 }
8626
8627 // Do privatization after moduleTranslation has already recorded
8628 // mapped values.
8629 PrivateVarsInfo privateVarsInfo(targetOp);
8630
8632 allocatePrivateVars(targetOp, builder, moduleTranslation,
8633 privateVarsInfo, allocaIP, &mappedPrivateVars);
8634
8635 if (failed(handleError(afterAllocas, *targetOp)))
8636 return llvm::make_error<PreviouslyReportedError>();
8637
8638 builder.restoreIP(codeGenIP);
8639 if (handleError(initPrivateVars(builder, moduleTranslation, privateVarsInfo,
8640 &mappedPrivateVars),
8641 *targetOp)
8642 .failed())
8643 return llvm::make_error<PreviouslyReportedError>();
8644
8645 if (failed(copyFirstPrivateVars(
8646 targetOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
8647 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
8648 targetOp.getPrivateNeedsBarrier(), &mappedPrivateVars)))
8649 return llvm::make_error<PreviouslyReportedError>();
8650
8652 moduleTranslation, allocaIP, deallocBlocks);
8654 targetRegion, "omp.target", builder, moduleTranslation);
8655
8656 if (failed(handleError(exitBlock, *targetOp)))
8657 return llvm::make_error<PreviouslyReportedError>();
8658
8659 builder.SetInsertPoint(exitBlock.get()->getTerminator());
8660
8661 if (failed(cleanupPrivateVars(targetOp, builder, moduleTranslation,
8662 targetOp.getLoc(), privateVarsInfo)))
8663 return llvm::make_error<PreviouslyReportedError>();
8664
8665 return builder.saveIP();
8666 };
8667
8668 StringRef parentName = parentFn.getName();
8669
8670 llvm::TargetRegionEntryInfo entryInfo;
8671
8672 getTargetEntryUniqueInfo(entryInfo, targetOp,
8673 *moduleTranslation.getOpenMPBuilder(),
8674 moduleTranslation.getFileSystem(), parentName);
8675
8676 MapInfoData mapData;
8677 collectMapDataFromMapOperands(mapData, mapVars, moduleTranslation, dl,
8678 builder, /*useDevPtrOperands=*/{},
8679 /*useDevAddrOperands=*/{}, hdaVars);
8680
8681 MapInfosTy combinedInfos;
8682 auto genMapInfoCB =
8683 [&](llvm::OpenMPIRBuilder::InsertPointTy codeGenIP) -> MapInfosTy & {
8684 builder.restoreIP(codeGenIP);
8685 genMapInfos(builder, moduleTranslation, dl, combinedInfos, mapData,
8686 targetDirective);
8687
8688 // Append a null entry for the implicit dyn_ptr argument so the argument
8689 // count sent to the runtime already includes it.
8690 auto *nullPtr = llvm::Constant::getNullValue(builder.getPtrTy());
8691 combinedInfos.BasePointers.push_back(nullPtr);
8692 combinedInfos.Pointers.push_back(nullPtr);
8693 combinedInfos.DevicePointers.push_back(
8694 llvm::OpenMPIRBuilder::DeviceInfoTy::None);
8695 combinedInfos.Sizes.push_back(builder.getInt64(0));
8696 combinedInfos.Types.push_back(
8697 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TARGET_PARAM |
8698 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL);
8699 if (!combinedInfos.Names.empty())
8700 combinedInfos.Names.push_back(nullPtr);
8701 combinedInfos.Mappers.push_back(nullptr);
8702
8703 return combinedInfos;
8704 };
8705
8706 auto argAccessorCB = [&](llvm::Argument &arg, llvm::Value *input,
8707 llvm::Value *&retVal, InsertPointTy allocaIP,
8708 InsertPointTy codeGenIP,
8710 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
8711 llvm::IRBuilderBase::InsertPointGuard guard(builder);
8712 builder.SetCurrentDebugLocation(llvm::DebugLoc());
8713 // We just return the unaltered argument for the host function
8714 // for now, some alterations may be required in the future to
8715 // keep host fallback functions working identically to the device
8716 // version (e.g. pass ByCopy values should be treated as such on
8717 // host and device, currently not always the case)
8718 if (!isTargetDevice) {
8719 retVal = cast<llvm::Value>(&arg);
8720 return codeGenIP;
8721 }
8722
8723 return createDeviceArgumentAccessor(targetOp, mapData, arg, input, retVal,
8724 builder, *ompBuilder, moduleTranslation,
8725 allocaIP, codeGenIP, deallocIPs);
8726 };
8727
8728 llvm::OpenMPIRBuilder::TargetKernelRuntimeAttrs runtimeAttrs;
8729 llvm::OpenMPIRBuilder::TargetKernelDefaultAttrs defaultAttrs;
8730 Operation *targetCapturedOp =
8731 cast<omp::ComposableOpInterface>(*targetOp).findCapturedOp();
8732 initTargetDefaultAttrs(targetOp, targetCapturedOp, defaultAttrs,
8733 isTargetDevice, isGPU);
8734
8735 // Collect host-evaluated values needed to properly launch the kernel from the
8736 // host.
8737 if (!isTargetDevice)
8738 initTargetRuntimeAttrs(builder, moduleTranslation, targetOp,
8739 targetCapturedOp, runtimeAttrs);
8740
8741 // Pass host-evaluated values as parameters to the kernel / host fallback,
8742 // except if they are constants. In any case, map the MLIR block argument to
8743 // the corresponding LLVM values.
8745 SmallVector<Value> hostEvalVars = targetOp.getHostEvalVars();
8746 ArrayRef<BlockArgument> hostEvalBlockArgs = argIface.getHostEvalBlockArgs();
8747 for (auto [arg, var] : llvm::zip_equal(hostEvalBlockArgs, hostEvalVars)) {
8748 llvm::Value *value = moduleTranslation.lookupValue(var);
8749 moduleTranslation.mapValue(arg, value);
8750
8751 if (!llvm::isa<llvm::Constant>(value))
8752 kernelInput.push_back(value);
8753 }
8754
8755 for (size_t i = 0, e = mapData.OriginalValue.size(); i != e; ++i) {
8756 // 1) Declare target arguments are not passed to kernels as arguments.
8757 // 2) Attach maps are not passed in as arguments to kernels.
8758 // 3) Children of record objects are not passed in as arguments.
8759 // TODO: We currently do not handle cases where a member is explicitly
8760 // passed in as an argument, this will likley need to be handled in
8761 // the near future, rather than using IsAMember, it may be better to
8762 // test if the relevant BlockArg is used within the target region and
8763 // then use that as a basis for exclusion in the kernel inputs.
8764 bool isAttachMap = (mapData.Types[i] &
8765 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) ==
8766 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH;
8767 if (!mapData.IsDeclareTarget[i] && !mapData.IsAMember[i] && !isAttachMap)
8768 kernelInput.push_back(mapData.OriginalValue[i]);
8769 }
8770
8772 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
8773 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
8774
8775 llvm::OpenMPIRBuilder::DependenciesInfo dds;
8776 if (failed(buildDependData(
8777 targetOp.getDependVars(), targetOp.getDependKinds(),
8778 targetOp.getDependIterated(), targetOp.getDependIteratedKinds(),
8779 builder, moduleTranslation, dds)))
8780 return failure();
8781
8782 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
8783
8784 llvm::OpenMPIRBuilder::TargetDataInfo info(
8785 /*RequiresDevicePointerInfo=*/false,
8786 /*SeparateBeginEndCalls=*/true);
8787
8788 auto customMapperCB =
8789 [&](unsigned int i) -> llvm::Expected<llvm::Function *> {
8790 if (!combinedInfos.Mappers[i])
8791 return nullptr;
8792 info.HasMapper = true;
8793 return getOrCreateUserDefinedMapperFunc(combinedInfos.Mappers[i], builder,
8794 moduleTranslation, targetDirective);
8795 };
8796
8797 llvm::Value *ifCond = nullptr;
8798 if (Value targetIfCond = targetOp.getIfExpr())
8799 ifCond = moduleTranslation.lookupValue(targetIfCond);
8800
8801 Value dynGroupPrivateSize = targetOp.getDynGroupprivateSize();
8802 llvm::Value *dynSizeVal = nullptr;
8803 if (dynGroupPrivateSize) {
8804 dynSizeVal = moduleTranslation.lookupValue(dynGroupPrivateSize);
8805 dynSizeVal = builder.CreateIntCast(dynSizeVal, builder.getInt32Ty(),
8806 /*isSigned=*/false);
8807 }
8808
8809 llvm::omp::OMPDynGroupprivateFallbackType fallbackType =
8810 getDynGroupprivateFallbackType(targetOp.getDynGroupprivateFallbackAttr());
8811
8812 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
8813 moduleTranslation.getOpenMPBuilder()->createTarget(
8814 ompLoc, isOffloadEntry, allocaIP, builder.saveIP(), deallocBlocks,
8815 info, entryInfo, defaultAttrs, runtimeAttrs, ifCond, kernelInput,
8816 genMapInfoCB, bodyCB, argAccessorCB, customMapperCB, dds,
8817 targetOp.getNowait(), dynSizeVal, fallbackType);
8818
8819 if (failed(handleError(afterIP, opInst)))
8820 return failure();
8821
8822 builder.restoreIP(*afterIP);
8823
8824 if (dds.DepArray)
8825 builder.CreateFree(dds.DepArray);
8826
8827 // Remap access operations to declare target reference pointers for the
8828 // device, essentially generating extra loadop's as necessary
8829 if (moduleTranslation.getOpenMPBuilder()->Config.isTargetDevice())
8830 handleDeclareTargetMapVar(mapData, moduleTranslation, builder,
8831 llvmOutlinedFn);
8832
8833 return success();
8834}
8835
8836static LogicalResult
8837convertDeclareTargetAttr(Operation *op, mlir::omp::DeclareTargetAttr attribute,
8838 llvm::OpenMPIRBuilder *ompBuilder,
8839 LLVM::ModuleTranslation &moduleTranslation) {
8840 // Amend omp.declare_target by deleting the IR of the outlined functions
8841 // created for target regions. They cannot be filtered out from MLIR earlier
8842 // because the omp.target operation inside must be translated to LLVM, but
8843 // the wrapper functions themselves must not remain at the end of the
8844 // process. We know that functions where omp.declare_target does not match
8845 // omp.is_target_device at this stage can only be wrapper functions because
8846 // those that aren't are removed earlier as an MLIR transformation pass.
8847 if (FunctionOpInterface funcOp = dyn_cast<FunctionOpInterface>(op)) {
8848 if (auto offloadMod = dyn_cast<omp::OffloadModuleInterface>(
8849 op->getParentOfType<ModuleOp>().getOperation())) {
8850 if (!offloadMod.getIsTargetDevice())
8851 return success();
8852
8853 omp::DeclareTargetDeviceType declareType =
8854 attribute.getDeviceType().getValue();
8855
8856 if (declareType == omp::DeclareTargetDeviceType::host) {
8857 llvm::Function *llvmFunc =
8858 moduleTranslation.lookupFunction(funcOp.getName());
8859 llvmFunc->dropAllReferences();
8860 llvmFunc->eraseFromParent();
8861
8862 // Invalidate the builder's current insertion point, as it now points to
8863 // a deleted block.
8864 ompBuilder->Builder.ClearInsertionPoint();
8865 ompBuilder->Builder.SetCurrentDebugLocation(llvm::DebugLoc());
8866 } else if (llvm::Function *llvmFunc =
8867 moduleTranslation.lookupFunction(funcOp.getName())) {
8868 // Device-side declare target functions are externally visible by
8869 // default so they can be referenced from other device translation
8870 // units. That also prevents the offload LTO from internalizing and
8871 // deleting them when they end up unused in the final device image.
8872 // Such dead functions can still reference internal LDS and trigger
8873 // spurious "local memory global used by non-kernel function" backend
8874 // warnings. Marking them hidden keeps the symbol usable within the
8875 // device image's linkage unit while letting LTO drop it when nothing
8876 // references it; symbols that must stay reachable (e.g. via an offload
8877 // entry that takes their address) are kept alive by that reference.
8878 if (!llvmFunc->isDeclaration() && llvmFunc->hasExternalLinkage() &&
8879 llvmFunc->getVisibility() == llvm::GlobalValue::DefaultVisibility)
8880 llvmFunc->setVisibility(llvm::GlobalValue::HiddenVisibility);
8881 }
8882 }
8883 return success();
8884 }
8885
8886 if (LLVM::GlobalOp gOp = dyn_cast<LLVM::GlobalOp>(op)) {
8887 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
8888 if (auto *gVal = llvmModule->getNamedValue(gOp.getSymName())) {
8889 auto *gVar = cast<llvm::GlobalVariable>(gVal);
8890 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
8891 bool isDeclaration = gOp.isDeclaration();
8892 bool isExternallyVisible =
8893 gOp.getVisibility() != mlir::SymbolTable::Visibility::Private;
8894 auto loc = op->getLoc()->findInstanceOf<FileLineColLoc>();
8895 llvm::StringRef mangledName = gOp.getSymName();
8896 mlir::omp::DeclareTargetCaptureClause captureClause =
8897 attribute.getCaptureClause().getValue();
8898 auto captureClauseKind = convertToCaptureClauseKind(captureClause);
8899 auto deviceClause =
8900 convertToDeviceClauseKind(attribute.getDeviceType().getValue());
8901 // unused for MLIR at the moment, required in Clang for book
8902 // keeping
8903 std::vector<llvm::GlobalVariable *> generatedRefs;
8904
8905 std::vector<llvm::Triple> targetTriple;
8906 auto targetTripleAttr = dyn_cast_or_null<mlir::StringAttr>(
8907 op->getParentOfType<mlir::ModuleOp>()->getAttr(
8908 LLVM::LLVMDialect::getTargetTripleAttrName()));
8909 if (targetTripleAttr)
8910 targetTriple.emplace_back(targetTripleAttr.data());
8911
8912 auto fileInfoCallBack = [&loc]() {
8913 std::string filename = "";
8914 std::uint64_t lineNo = 0;
8915
8916 if (loc) {
8917 filename = loc.getFilename().str();
8918 lineNo = loc.getLine();
8919 }
8920
8921 return std::pair<std::string, std::uint64_t>(llvm::StringRef(filename),
8922 lineNo);
8923 };
8924
8925 bool requiresUSM = ompBuilder->Config.hasRequiresUnifiedSharedMemory();
8926 bool isToOrEnter =
8927 captureClause == omp::DeclareTargetCaptureClause::to ||
8928 captureClause == omp::DeclareTargetCaptureClause::enter;
8929 bool isHostOnly = attribute.getDeviceType().getValue() ==
8930 omp::DeclareTargetDeviceType::host;
8931
8932 // A to/enter declare-target variable needs a device-resident,
8933 // name-resolvable copy and a host offloading entry. A local-linkage
8934 // global provides neither, so we promote it to external.
8935 if (isToOrEnter && !isHostOnly && !requiresUSM &&
8936 gVar->hasLocalLinkage()) {
8937 gVar->setLinkage(llvm::GlobalValue::ExternalLinkage);
8938 isExternallyVisible = true;
8939
8940 // Clear the stale dso_local flag so it is referenced like a
8941 // module-scope declare target global.
8942 if (ompBuilder->Config.isTargetDevice())
8943 gVar->setDSOLocal(false);
8944 }
8945
8946 llvm::vfs::FileSystem &vfs = moduleTranslation.getFileSystem();
8947 ompBuilder->registerTargetGlobalVariable(
8948 captureClauseKind, deviceClause, isDeclaration, isExternallyVisible,
8949 ompBuilder->getTargetEntryUniqueInfo(fileInfoCallBack, vfs),
8950 mangledName, generatedRefs, /*OpenMPSimd*/ false, targetTriple,
8951 /*GlobalInitializer*/ nullptr, /*VariableLinkage*/ nullptr,
8952 gVal->getType(), gVal);
8953
8954 if (ompBuilder->Config.isTargetDevice() &&
8955 (captureClause == omp::DeclareTargetCaptureClause::link ||
8956 requiresUSM)) {
8957 llvm::Type *ptrTy = gVal->getType();
8958 // For USM the global type becomes a pointer handle, as opposed to the
8959 // globals original type.
8960 if (requiresUSM)
8961 ptrTy = llvm::PointerType::get(llvmModule->getContext(), 0);
8962 bool addrGlobalCreated = ompBuilder->getAddrOfDeclareTargetVar(
8963 captureClauseKind, deviceClause, isDeclaration, isExternallyVisible,
8964 ompBuilder->getTargetEntryUniqueInfo(fileInfoCallBack, vfs),
8965 mangledName, generatedRefs, /*OpenMPSimd*/ false, targetTriple,
8966 ptrTy, /*GlobalInitializer*/ nullptr,
8967 /*VariableLinkage*/ nullptr);
8968
8969 // For indirectly-accessed global pointers, we rely on "internal"
8970 // linkage to optimize out the unneeded full-variable storage later,
8971 // since we can't prevent the LLVM dialect from generating globals
8972 // without also breaking target lowering.
8973 if (addrGlobalCreated)
8974 gVar->setLinkage(llvm::GlobalValue::InternalLinkage);
8975 }
8976
8977 // Mark 'device_type(host) enter(...)' variables as external in the device
8978 // since they're not supposed to have their own copy. This will cause
8979 // linker errors if accesses are attempted from the target device.
8980 if (ompBuilder->Config.isTargetDevice() && isHostOnly && isToOrEnter) {
8981 gVar->setLinkage(llvm::GlobalValue::ExternalLinkage);
8982 gVar->setInitializer(nullptr);
8983 }
8984 }
8985 }
8986
8987 return success();
8988}
8989
8990namespace {
8991
8992/// Implementation of the dialect interface that converts operations belonging
8993/// to the OpenMP dialect to LLVM IR.
8994class OpenMPDialectLLVMIRTranslationInterface
8995 : public LLVMTranslationDialectInterface {
8996public:
8997 using LLVMTranslationDialectInterface::LLVMTranslationDialectInterface;
8998
8999 /// Translates the given operation to LLVM IR using the provided IR builder
9000 /// and saving the state in `moduleTranslation`.
9001 LogicalResult
9002 convertOperation(Operation *op, llvm::IRBuilderBase &builder,
9003 LLVM::ModuleTranslation &moduleTranslation) const final;
9004
9005 /// Given an OpenMP MLIR attribute, create the corresponding LLVM-IR,
9006 /// runtime calls, or operation amendments
9007 LogicalResult
9008 amendOperation(Operation *op, ArrayRef<llvm::Instruction *> instructions,
9009 NamedAttribute attribute,
9010 LLVM::ModuleTranslation &moduleTranslation) const final;
9011
9012 /// Records the LLVM alloc pointer produced for an OMP ALLOCATE variable so
9013 /// that the paired omp.allocate_free op can generate the matching
9014 /// __kmpc_free call.
9015 void registerAllocatedPtr(Value var, llvm::Value *ptr) const {
9016 ompAllocatedPtrs[var] = ptr;
9017 }
9018
9019 /// Returns the LLVM alloc pointer previously registered for var, or
9020 /// nullptr if no allocation was recorded.
9021 llvm::Value *lookupAllocatedPtr(Value var) const {
9022 auto it = ompAllocatedPtrs.find(var);
9023 return it != ompAllocatedPtrs.end() ? it->second : nullptr;
9024 }
9025
9026private:
9027 /// Maps each MLIR variable value that appeared in an omp.allocate_dir op to
9028 /// the LLVM pointer returned by the corresponding __kmpc_alloc call. The
9029 /// paired omp.allocate_free op looks up these pointers to emit __kmpc_free.
9030 mutable DenseMap<Value, llvm::Value *> ompAllocatedPtrs;
9031};
9032
9033} // namespace
9034
9035LogicalResult OpenMPDialectLLVMIRTranslationInterface::amendOperation(
9036 Operation *op, ArrayRef<llvm::Instruction *> instructions,
9037 NamedAttribute attribute,
9038 LLVM::ModuleTranslation &moduleTranslation) const {
9039 return llvm::StringSwitch<llvm::function_ref<LogicalResult(Attribute)>>(
9040 attribute.getName())
9041 .Case("omp.is_target_device",
9042 [&](Attribute attr) {
9043 if (auto deviceAttr = dyn_cast<BoolAttr>(attr)) {
9044 llvm::OpenMPIRBuilderConfig &config =
9045 moduleTranslation.getOpenMPBuilder()->Config;
9046 config.setIsTargetDevice(deviceAttr.getValue());
9047 return success();
9048 }
9049 return failure();
9050 })
9051 .Case("omp.is_gpu",
9052 [&](Attribute attr) {
9053 if (auto gpuAttr = dyn_cast<BoolAttr>(attr)) {
9054 llvm::OpenMPIRBuilderConfig &config =
9055 moduleTranslation.getOpenMPBuilder()->Config;
9056 config.setIsGPU(gpuAttr.getValue());
9057 return success();
9058 }
9059 return failure();
9060 })
9061 .Case("omp.host_ir_filepath",
9062 [&](Attribute attr) {
9063 if (auto filepathAttr = dyn_cast<StringAttr>(attr)) {
9064 llvm::OpenMPIRBuilder *ompBuilder =
9065 moduleTranslation.getOpenMPBuilder();
9066 ompBuilder->loadOffloadInfoMetadata(
9067 moduleTranslation.getFileSystem(), filepathAttr.getValue());
9068 return success();
9069 }
9070 return failure();
9071 })
9072 .Case("omp.flags",
9073 [&](Attribute attr) {
9074 if (auto rtlAttr = dyn_cast<omp::FlagsAttr>(attr))
9075 return convertFlagsAttr(op, rtlAttr, moduleTranslation);
9076 return failure();
9077 })
9078 .Case("omp.version",
9079 [&](Attribute attr) {
9080 if (auto versionAttr = dyn_cast<omp::VersionAttr>(attr)) {
9081 llvm::OpenMPIRBuilder *ompBuilder =
9082 moduleTranslation.getOpenMPBuilder();
9083 ompBuilder->M.addModuleFlag(llvm::Module::Max, "openmp",
9084 versionAttr.getVersion());
9085 return success();
9086 }
9087 return failure();
9088 })
9089 .Case("omp.declare_target",
9090 [&](Attribute attr) {
9091 if (auto declareTargetAttr =
9092 dyn_cast<omp::DeclareTargetAttr>(attr)) {
9093 llvm::OpenMPIRBuilder *ompBuilder =
9094 moduleTranslation.getOpenMPBuilder();
9095 return convertDeclareTargetAttr(op, declareTargetAttr,
9096 ompBuilder, moduleTranslation);
9097 }
9098 return failure();
9099 })
9100 .Case("omp.requires",
9101 [&](Attribute attr) {
9102 if (auto requiresAttr = dyn_cast<omp::ClauseRequiresAttr>(attr)) {
9103 using Requires = omp::ClauseRequires;
9104 Requires flags = requiresAttr.getValue();
9105 llvm::OpenMPIRBuilderConfig &config =
9106 moduleTranslation.getOpenMPBuilder()->Config;
9107 config.setHasRequiresReverseOffload(
9108 bitEnumContainsAll(flags, Requires::reverse_offload));
9109 config.setHasRequiresUnifiedAddress(
9110 bitEnumContainsAll(flags, Requires::unified_address));
9111 config.setHasRequiresUnifiedSharedMemory(
9112 bitEnumContainsAll(flags, Requires::unified_shared_memory));
9113 config.setHasRequiresDynamicAllocators(
9114 bitEnumContainsAll(flags, Requires::dynamic_allocators));
9115 return success();
9116 }
9117 return failure();
9118 })
9119 .Case("omp.target_triples",
9120 [&](Attribute attr) {
9121 if (auto triplesAttr = dyn_cast<ArrayAttr>(attr)) {
9122 llvm::OpenMPIRBuilderConfig &config =
9123 moduleTranslation.getOpenMPBuilder()->Config;
9124 config.TargetTriples.clear();
9125 config.TargetTriples.reserve(triplesAttr.size());
9126 for (Attribute tripleAttr : triplesAttr) {
9127 if (auto tripleStrAttr = dyn_cast<StringAttr>(tripleAttr))
9128 config.TargetTriples.emplace_back(tripleStrAttr.getValue());
9129 else
9130 return failure();
9131 }
9132 return success();
9133 }
9134 return failure();
9135 })
9136 .Default([](Attribute) {
9137 // Fall through for omp attributes that do not require lowering.
9138 return success();
9139 })(attribute.getValue());
9140
9141 return failure();
9142}
9143
9144// Returns true if the operation is not inside a TargetOp, it is part of a
9145// function and that function is not declare target.
9146static bool isHostDeviceOp(Operation *op) {
9147 // Assumes no reverse offloading
9148 if (op->getParentOfType<omp::TargetOp>())
9149 return false;
9150
9151 if (auto parentFn = op->getParentOfType<LLVM::LLVMFuncOp>()) {
9152 if (auto declareTargetIface =
9153 llvm::dyn_cast<mlir::omp::DeclareTargetInterface>(
9154 parentFn.getOperation()))
9155 if (declareTargetIface.isDeclareTarget() &&
9156 declareTargetIface.getDeclareTargetDeviceType() !=
9157 mlir::omp::DeclareTargetDeviceType::host)
9158 return false;
9159
9160 return true;
9161 }
9162
9163 return false;
9164}
9165
9166static llvm::Function *getOmpTargetAlloc(llvm::IRBuilderBase &builder,
9167 llvm::Module *llvmModule) {
9168 llvm::Type *i64Ty = builder.getInt64Ty();
9169 llvm::Type *i32Ty = builder.getInt32Ty();
9170 llvm::Type *returnType = builder.getPtrTy(0);
9171 llvm::FunctionType *fnType =
9172 llvm::FunctionType::get(returnType, {i64Ty, i32Ty}, false);
9173 llvm::Function *func = cast<llvm::Function>(
9174 llvmModule->getOrInsertFunction("omp_target_alloc", fnType).getCallee());
9175 return func;
9176}
9177
9178template <typename T>
9179static llvm::Value *
9180getAllocationSize(llvm::IRBuilderBase &builder,
9181 LLVM::ModuleTranslation &moduleTranslation, T op) {
9182 llvm::DataLayout dataLayout =
9183 moduleTranslation.getLLVMModule()->getDataLayout();
9184 llvm::Type *llvmHeapTy =
9185 moduleTranslation.convertType(op.getMemElemTypeAttr().getValue());
9186
9187 auto alignment = op.getMemAlignment();
9188 llvm::TypeSize typeSize = llvm::alignTo(
9189 dataLayout.getTypeStoreSize(llvmHeapTy),
9190 alignment ? *alignment : dataLayout.getABITypeAlign(llvmHeapTy).value());
9191
9192 llvm::Value *allocSize = builder.getInt64(typeSize.getFixedValue());
9193 return builder.CreateMul(
9194 allocSize,
9195 builder.CreateIntCast(moduleTranslation.lookupValue(op.getMemArraySize()),
9196 builder.getInt64Ty(),
9197 /*isSigned=*/false));
9198}
9199
9200template <>
9201llvm::Value *getAllocationSize(llvm::IRBuilderBase &builder,
9202 LLVM::ModuleTranslation &moduleTranslation,
9203 omp::TargetAllocMemOp op) {
9204 llvm::DataLayout dataLayout =
9205 moduleTranslation.getLLVMModule()->getDataLayout();
9206 llvm::Type *llvmHeapTy = moduleTranslation.convertType(op.getAllocatedType());
9207 llvm::TypeSize typeSize = dataLayout.getTypeAllocSize(llvmHeapTy);
9208 llvm::Value *allocSize = builder.getInt64(typeSize.getFixedValue());
9209 for (auto typeParam : op.getTypeparams()) {
9210 allocSize = builder.CreateMul(
9211 allocSize,
9212 builder.CreateIntCast(moduleTranslation.lookupValue(typeParam),
9213 builder.getInt64Ty(),
9214 /*isSigned=*/false));
9215 }
9216 return allocSize;
9217}
9218
9219static LogicalResult
9220convertTargetAllocMemOp(Operation &opInst, llvm::IRBuilderBase &builder,
9221 LLVM::ModuleTranslation &moduleTranslation) {
9222 auto allocMemOp = cast<omp::TargetAllocMemOp>(opInst);
9223 if (!allocMemOp)
9224 return failure();
9225
9226 // Get "omp_target_alloc" function
9227 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
9228 llvm::Function *ompTargetAllocFunc = getOmpTargetAlloc(builder, llvmModule);
9229 // Get the corresponding device value in llvm
9230 mlir::Value deviceNum = allocMemOp.getDevice();
9231 llvm::Value *llvmDeviceNum = moduleTranslation.lookupValue(deviceNum);
9232 // Get the allocation size.
9233 llvm::Value *allocSize =
9234 getAllocationSize(builder, moduleTranslation, allocMemOp);
9235 // Create call to "omp_target_alloc" with the args as translated llvm values.
9236 llvm::CallInst *call =
9237 builder.CreateCall(ompTargetAllocFunc, {allocSize, llvmDeviceNum});
9238 llvm::Value *resultI64 = builder.CreatePtrToInt(call, builder.getInt64Ty());
9239
9240 // Map the result
9241 moduleTranslation.mapValue(allocMemOp.getResult(), resultI64);
9242 return success();
9243}
9244
9245static LogicalResult
9246convertAllocSharedMemOp(omp::AllocSharedMemOp allocMemOp,
9247 llvm::IRBuilderBase &builder,
9248 LLVM::ModuleTranslation &moduleTranslation) {
9249 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9250 llvm::Value *size = getAllocationSize(builder, moduleTranslation, allocMemOp);
9251 moduleTranslation.mapValue(allocMemOp.getResult(),
9252 ompBuilder->createOMPAllocShared(builder, size));
9253 return success();
9254}
9255
9256static LogicalResult
9257convertAllocateDirOp(Operation &opInst, llvm::IRBuilderBase &builder,
9258 LLVM::ModuleTranslation &moduleTranslation,
9259 const OpenMPDialectLLVMIRTranslationInterface &ompIface) {
9260 auto allocateDirOp = cast<omp::AllocateDirOp>(opInst);
9261 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9262
9263 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
9264 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
9265 llvm::DataLayout dataLayout = llvmModule->getDataLayout();
9266 SmallVector<Value> vars = allocateDirOp.getVarList();
9267 std::optional<int64_t> alignAttr = allocateDirOp.getAlign();
9268
9269 llvm::Value *allocator;
9270 if (auto allocatorVar = allocateDirOp.getAllocator()) {
9271 allocator = moduleTranslation.lookupValue(allocatorVar);
9272 if (allocator->getType()->isIntegerTy())
9273 allocator = builder.CreateIntToPtr(allocator, builder.getPtrTy());
9274 else if (allocator->getType()->isPointerTy())
9275 allocator = builder.CreatePointerBitCastOrAddrSpaceCast(
9276 allocator, builder.getPtrTy());
9277 } else {
9278 allocator = llvm::ConstantPointerNull::get(builder.getPtrTy());
9279 }
9280
9281 for (Value var : vars) {
9282 llvm::Type *llvmVarTy = moduleTranslation.convertType(var.getType());
9283
9284 // Opaque pointers lose element type. Trace to GlobalOp for type
9285 // Falls back to llvmVarTy when not from a global.
9286 llvm::Type *typeToInspect = llvmVarTy;
9287 if (llvmVarTy->isPointerTy()) {
9288 Value baseVar = getBaseValueForTypeLookup(var);
9289 if (Operation *globalOp = getGlobalOpFromValue(baseVar)) {
9290 if (auto gop = dyn_cast<LLVM::GlobalOp>(globalOp))
9291 typeToInspect = moduleTranslation.convertType(gop.getGlobalType());
9292 }
9293 }
9294
9295 llvm::Value *size;
9296 if (auto arrTy = llvm::dyn_cast<llvm::ArrayType>(typeToInspect)) {
9297 llvm::Value *elementCount = builder.getInt64(1);
9298 llvm::Type *currentType = arrTy;
9299 while (auto nestedArrTy = llvm::dyn_cast<llvm::ArrayType>(currentType)) {
9300 elementCount = builder.CreateMul(
9301 elementCount, builder.getInt64(nestedArrTy->getNumElements()));
9302 currentType = nestedArrTy->getElementType();
9303 }
9304 uint64_t elemSizeInBits = dataLayout.getTypeSizeInBits(currentType);
9305 size =
9306 builder.CreateMul(elementCount, builder.getInt64(elemSizeInBits / 8));
9307 } else {
9308 size = builder.getInt64(
9309 dataLayout.getTypeStoreSize(typeToInspect).getFixedValue());
9310 }
9311
9312 uint64_t alignValue =
9313 alignAttr ? alignAttr.value()
9314 : dataLayout.getABITypeAlign(typeToInspect).value();
9315 llvm::Value *alignConst = builder.getInt64(alignValue);
9316 // Align the size: ((size + align - 1) / align) * align
9317 size = builder.CreateAdd(size, builder.getInt64(alignValue - 1), "", true);
9318 size = builder.CreateUDiv(size, alignConst);
9319 size = builder.CreateMul(size, alignConst, "", true);
9320
9321 std::string allocName =
9322 ompBuilder->createPlatformSpecificName({".void.addr"});
9323 llvm::CallInst *allocCall;
9324 if (alignAttr.has_value()) {
9325 allocCall = ompBuilder->createOMPAlignedAlloc(
9326 ompLoc, builder.getInt64(alignAttr.value()), size, allocator,
9327 allocName);
9328 } else {
9329 allocCall =
9330 ompBuilder->createOMPAlloc(ompLoc, size, allocator, allocName);
9331 }
9332 // Record the alloc pointer keyed by the MLIR variable value.
9333 ompIface.registerAllocatedPtr(var, allocCall);
9334 }
9335
9336 return success();
9337}
9338
9339static LogicalResult
9340convertAllocateFreeOp(Operation &opInst, llvm::IRBuilderBase &builder,
9341 LLVM::ModuleTranslation &moduleTranslation,
9342 const OpenMPDialectLLVMIRTranslationInterface &ompIface) {
9343 auto freeOp = cast<omp::AllocateFreeOp>(opInst);
9344 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9345 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
9346
9347 llvm::Value *allocator;
9348 if (auto allocatorVar = freeOp.getAllocator()) {
9349 allocator = moduleTranslation.lookupValue(allocatorVar);
9350 if (allocator->getType()->isIntegerTy())
9351 allocator = builder.CreateIntToPtr(allocator, builder.getPtrTy());
9352 else if (allocator->getType()->isPointerTy())
9353 allocator = builder.CreatePointerBitCastOrAddrSpaceCast(
9354 allocator, builder.getPtrTy());
9355 } else {
9356 allocator = llvm::ConstantPointerNull::get(builder.getPtrTy());
9357 }
9358
9359 // Emit __kmpc_free for each variable in reverse allocation order.
9360 SmallVector<Value> vars = freeOp.getVarList();
9361 for (Value var : llvm::reverse(vars)) {
9362 llvm::Value *allocPtr = ompIface.lookupAllocatedPtr(var);
9363 if (!allocPtr)
9364 return opInst.emitError("omp.allocate_free: no allocation recorded");
9365 ompBuilder->createOMPFree(ompLoc, allocPtr, allocator, "");
9366 }
9367
9368 return success();
9369}
9370
9371static llvm::Function *getOmpTargetFree(llvm::IRBuilderBase &builder,
9372 llvm::Module *llvmModule) {
9373 llvm::Type *ptrTy = builder.getPtrTy(0);
9374 llvm::Type *i32Ty = builder.getInt32Ty();
9375 llvm::Type *voidTy = builder.getVoidTy();
9376 llvm::FunctionType *fnType =
9377 llvm::FunctionType::get(voidTy, {ptrTy, i32Ty}, false);
9378 llvm::Function *func = dyn_cast<llvm::Function>(
9379 llvmModule->getOrInsertFunction("omp_target_free", fnType).getCallee());
9380 return func;
9381}
9382
9383static LogicalResult
9384convertTargetFreeMemOp(Operation &opInst, llvm::IRBuilderBase &builder,
9385 LLVM::ModuleTranslation &moduleTranslation) {
9386 auto freeMemOp = cast<omp::TargetFreeMemOp>(opInst);
9387 if (!freeMemOp)
9388 return failure();
9389
9390 // Get "omp_target_free" function
9391 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
9392 llvm::Function *ompTragetFreeFunc = getOmpTargetFree(builder, llvmModule);
9393 // Get the corresponding device value in llvm
9394 mlir::Value deviceNum = freeMemOp.getDevice();
9395 llvm::Value *llvmDeviceNum = moduleTranslation.lookupValue(deviceNum);
9396 // Get the corresponding heapref value in llvm
9397 mlir::Value heapref = freeMemOp.getHeapref();
9398 llvm::Value *llvmHeapref = moduleTranslation.lookupValue(heapref);
9399 // Convert heapref int to ptr and call "omp_target_free"
9400 llvm::Value *intToPtr =
9401 builder.CreateIntToPtr(llvmHeapref, builder.getPtrTy(0));
9402 builder.CreateCall(ompTragetFreeFunc, {intToPtr, llvmDeviceNum});
9403 return success();
9404}
9405
9406static LogicalResult
9407convertFreeSharedMemOp(omp::FreeSharedMemOp freeMemOp,
9408 llvm::IRBuilderBase &builder,
9409 LLVM::ModuleTranslation &moduleTranslation) {
9410 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9411 llvm::Value *size = getAllocationSize(builder, moduleTranslation, freeMemOp);
9412 ompBuilder->createOMPFreeShared(
9413 builder, moduleTranslation.lookupValue(freeMemOp.getHeapref()), size);
9414 return success();
9415}
9416
9417/// Converts an OpenMP groupprivate operation into LLVM IR.
9418static LogicalResult
9419convertOmpGroupprivate(Operation &opInst, llvm::IRBuilderBase &builder,
9420 LLVM::ModuleTranslation &moduleTranslation) {
9421 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9422 auto groupprivateOp = cast<omp::GroupprivateOp>(opInst);
9423
9424 if (failed(checkImplementationStatus(opInst)))
9425 return failure();
9426
9427 bool isTargetDevice = ompBuilder->Config.isTargetDevice();
9428
9429 // Determine whether group-private storage should be allocated based on
9430 // device_type. When not specified, default to 'any' (allocate on both).
9431 bool shouldAllocate = true;
9432 switch (groupprivateOp.getDeviceType().value_or(
9433 mlir::omp::DeclareTargetDeviceType::any)) {
9434 case mlir::omp::DeclareTargetDeviceType::host:
9435 shouldAllocate = !isTargetDevice;
9436 break;
9437 case mlir::omp::DeclareTargetDeviceType::nohost:
9438 shouldAllocate = isTargetDevice;
9439 break;
9440 case mlir::omp::DeclareTargetDeviceType::any:
9441 shouldAllocate = true;
9442 break;
9443 }
9444
9445 // Look up the global variable directly by symbol name.
9447 &opInst, groupprivateOp.getSymNameAttr());
9448 if (!global)
9449 return opInst.emitError()
9450 << "expected symbol '" << groupprivateOp.getSymName()
9451 << "' to reference an LLVM global variable";
9452
9453 llvm::GlobalValue *globalValue = moduleTranslation.lookupGlobal(global);
9454 llvm::Type *varType = moduleTranslation.convertType(global.getType());
9455 std::string varName = globalValue->getName().str();
9456
9457 llvm::Value *resultPtr;
9458 if (shouldAllocate && isTargetDevice) {
9459 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
9460 llvm::Triple targetTriple(llvmModule->getTargetTriple());
9461 unsigned sharedAddressSpace;
9462 if (targetTriple.isAMDGCN())
9463 sharedAddressSpace = llvm::AMDGPUAS::LOCAL_ADDRESS;
9464 else if (targetTriple.isNVPTX())
9465 sharedAddressSpace = llvm::NVPTXAS::ADDRESS_SPACE_SHARED;
9466 else
9467 return opInst.emitError() << "groupprivate is not supported for target: "
9468 << targetTriple.str();
9469 llvm::GlobalVariable *sharedVar = new llvm::GlobalVariable(
9470 *llvmModule, varType, /*isConstant=*/false,
9471 llvm::GlobalValue::InternalLinkage, llvm::PoisonValue::get(varType),
9472 varName, /*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal,
9473 sharedAddressSpace,
9474 /*isExternallyInitialized=*/false);
9475 resultPtr = sharedVar;
9476 } else {
9477 if (shouldAllocate && !isTargetDevice)
9478 opInst.emitWarning("groupprivate directive is currently ignored on the "
9479 "host, using original global");
9480 resultPtr = globalValue;
9481 }
9482
9483 moduleTranslation.mapValue(opInst.getResult(0), resultPtr);
9484 return success();
9485}
9486
9487/// Given an OpenMP MLIR operation, create the corresponding LLVM IR (including
9488/// OpenMP runtime calls).
9489LogicalResult OpenMPDialectLLVMIRTranslationInterface::convertOperation(
9490 Operation *op, llvm::IRBuilderBase &builder,
9491 LLVM::ModuleTranslation &moduleTranslation) const {
9492 llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
9493
9494 if (ompBuilder->Config.isTargetDevice() &&
9495 !isa<omp::TargetOp, omp::MapInfoOp, omp::TerminatorOp, omp::YieldOp>(
9496 op) &&
9497 isHostDeviceOp(op))
9498 return op->emitOpError() << "unsupported host op found in device";
9499
9500 // For each loop, introduce one stack frame to hold loop information. Ensure
9501 // this is only done for the outermost loop wrapper to prevent introducing
9502 // multiple stack frames for a single loop. Initially set to null, the loop
9503 // information structure is initialized during translation of the nested
9504 // omp.loop_nest operation, making it available to translation of all loop
9505 // wrappers after their body has been successfully translated.
9506 bool isOutermostLoopWrapper =
9507 isa_and_present<omp::LoopWrapperInterface>(op) &&
9508 !dyn_cast_if_present<omp::LoopWrapperInterface>(op->getParentOp());
9509
9510 // The TASKLOOP construct is implemented with an outer taskloop.context
9511 // operation which is not a loop wrapper, containing an inner taskloop
9512 // operation which is a loop wrapper. The stack frame should be pushed when
9513 // translating the outer taskloop.context and popped when translating the
9514 // inner taskloop which is a loop wrapper. We need access to the loop
9515 // information in the outer taskloop context so we need to create it and pop
9516 // it around the taskloop context not the inner loop wrapper.
9517 if (isa<omp::TaskloopContextOp>(op))
9518 isOutermostLoopWrapper = true;
9519 else if (isa<omp::TaskloopWrapperOp>(op))
9520 isOutermostLoopWrapper = false;
9521
9522 if (isOutermostLoopWrapper)
9523 moduleTranslation.stackPush<OpenMPLoopInfoStackFrame>();
9524
9525 auto result =
9526 llvm::TypeSwitch<Operation *, LogicalResult>(op)
9527 .Case([&](omp::BarrierOp op) -> LogicalResult {
9529 return failure();
9530
9531 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
9532 ompBuilder->createBarrier(builder.saveIP(),
9533 llvm::omp::OMPD_barrier);
9534 LogicalResult res = handleError(afterIP, *op);
9535 if (res.succeeded()) {
9536 // If the barrier generated a cancellation check, the insertion
9537 // point might now need to be changed to a new continuation block
9538 builder.restoreIP(*afterIP);
9539 }
9540 return res;
9541 })
9542 .Case([&](omp::TaskyieldOp op) {
9544 return failure();
9545
9546 ompBuilder->createTaskyield(builder.saveIP());
9547 return success();
9548 })
9549 .Case([&](omp::FlushOp op) {
9551 return failure();
9552
9553 // No support in Openmp runtime function (__kmpc_flush) to accept
9554 // the argument list.
9555 // OpenMP standard states the following:
9556 // "An implementation may implement a flush with a list by ignoring
9557 // the list, and treating it the same as a flush without a list."
9558 //
9559 // The argument list is discarded so that, flush with a list is
9560 // treated same as a flush without a list.
9561 ompBuilder->createFlush(builder.saveIP());
9562 return success();
9563 })
9564 .Case([&](omp::ParallelOp op) {
9565 return convertOmpParallel(op, builder, moduleTranslation);
9566 })
9567 .Case([&](omp::MaskedOp) {
9568 return convertOmpMasked(*op, builder, moduleTranslation);
9569 })
9570 .Case([&](omp::MasterOp) {
9571 return convertOmpMaster(*op, builder, moduleTranslation);
9572 })
9573 .Case([&](omp::CriticalOp) {
9574 return convertOmpCritical(*op, builder, moduleTranslation);
9575 })
9576 .Case([&](omp::OrderedRegionOp) {
9577 return convertOmpOrderedRegion(*op, builder, moduleTranslation);
9578 })
9579 .Case([&](omp::OrderedOp) {
9580 return convertOmpOrdered(*op, builder, moduleTranslation);
9581 })
9582 .Case([&](omp::WsloopOp) {
9583 return convertOmpWsloop(*op, builder, moduleTranslation);
9584 })
9585 .Case([&](omp::SimdOp) {
9586 return convertOmpSimd(*op, builder, moduleTranslation);
9587 })
9588 .Case([&](omp::AtomicReadOp) {
9589 return convertOmpAtomicRead(*op, builder, moduleTranslation);
9590 })
9591 .Case([&](omp::AtomicWriteOp) {
9592 return convertOmpAtomicWrite(*op, builder, moduleTranslation);
9593 })
9594 .Case([&](omp::AtomicUpdateOp op) {
9595 return convertOmpAtomicUpdate(op, builder, moduleTranslation);
9596 })
9597 .Case([&](omp::AtomicCaptureOp op) {
9598 return convertOmpAtomicCapture(op, builder, moduleTranslation);
9599 })
9600 .Case([&](omp::AtomicCompareOp op) {
9601 return convertOmpAtomicCompare(op, builder, moduleTranslation);
9602 })
9603 .Case([&](omp::CancelOp op) {
9604 return convertOmpCancel(op, builder, moduleTranslation);
9605 })
9606 .Case([&](omp::CancellationPointOp op) {
9607 return convertOmpCancellationPoint(op, builder, moduleTranslation);
9608 })
9609 .Case([&](omp::SectionsOp) {
9610 return convertOmpSections(*op, builder, moduleTranslation);
9611 })
9612 .Case([&](omp::ScopeOp op) {
9613 return convertOmpScope(op, builder, moduleTranslation);
9614 })
9615 .Case([&](omp::SingleOp op) {
9616 return convertOmpSingle(op, builder, moduleTranslation);
9617 })
9618 .Case([&](omp::TeamsOp op) {
9619 return convertOmpTeams(op, builder, moduleTranslation);
9620 })
9621 .Case([&](omp::TaskOp op) {
9622 return convertOmpTaskOp(op, builder, moduleTranslation);
9623 })
9624 .Case([&](omp::TaskloopWrapperOp op) {
9625 return convertOmpTaskloopWrapperOp(op, builder, moduleTranslation);
9626 })
9627 .Case([&](omp::TaskloopContextOp op) {
9628 return convertOmpTaskloopContextOp(op, builder, moduleTranslation);
9629 })
9630 .Case([&](omp::TaskgroupOp op) {
9631 return convertOmpTaskgroupOp(op, builder, moduleTranslation);
9632 })
9633 .Case([&](omp::TaskwaitOp op) {
9634 return convertOmpTaskwaitOp(op, builder, moduleTranslation);
9635 })
9636 .Case<omp::YieldOp, omp::TerminatorOp, omp::DeclareMapperOp,
9637 omp::DeclareMapperInfoOp, omp::DeclareReductionOp,
9638 omp::CriticalDeclareOp>([](auto op) {
9639 // `yield` and `terminator` can be just omitted. The block structure
9640 // was created in the region that handles their parent operation.
9641 // `declare_reduction` will be used by reductions and is not
9642 // converted directly, skip it.
9643 // `declare_mapper` and `declare_mapper.info` are handled whenever
9644 // they are referred to through a `map` clause.
9645 // `critical.declare` is only used to declare names of critical
9646 // sections which will be used by `critical` ops and hence can be
9647 // ignored for lowering. The OpenMP IRBuilder will create unique
9648 // name for critical section names.
9649 return success();
9650 })
9651 .Case([&](omp::ThreadprivateOp) {
9652 return convertOmpThreadprivate(*op, builder, moduleTranslation);
9653 })
9654 .Case<omp::TargetDataOp, omp::TargetEnterDataOp,
9655 omp::TargetExitDataOp, omp::TargetUpdateOp>([&](auto op) {
9656 return convertOmpTargetData(op, builder, moduleTranslation);
9657 })
9658 .Case([&](omp::TargetOp) {
9659 return convertOmpTarget(*op, builder, moduleTranslation);
9660 })
9661 .Case([&](omp::DistributeOp) {
9662 return convertOmpDistribute(*op, builder, moduleTranslation);
9663 })
9664 .Case([&](omp::LoopNestOp) {
9665 return convertOmpLoopNest(*op, builder, moduleTranslation);
9666 })
9667 .Case<omp::MapInfoOp, omp::MapBoundsOp, omp::PrivateClauseOp,
9668 omp::AffinityEntryOp, omp::IteratorOp>([&](auto op) {
9669 // No-op, should be handled by relevant owning operations e.g.
9670 // TargetOp, TargetEnterDataOp, TargetExitDataOp, TargetDataOp
9671 // etc. and then discarded
9672 return success();
9673 })
9674 .Case([&](omp::NewCliOp op) {
9675 // Meta-operation: Doesn't do anything by itself, but used to
9676 // identify a loop.
9677 return success();
9678 })
9679 .Case([&](omp::CanonicalLoopOp op) {
9680 return convertOmpCanonicalLoopOp(op, builder, moduleTranslation);
9681 })
9682 .Case([&](omp::UnrollHeuristicOp op) {
9683 // FIXME: Handling omp.unroll_heuristic as an executable requires
9684 // that the generator (e.g. omp.canonical_loop) has been seen first.
9685 // For construct that require all codegen to occur inside a callback
9686 // (e.g. OpenMPIRBilder::createParallel), all codegen of that
9687 // contained region including their transformations must occur at
9688 // the omp.canonical_loop.
9689 return applyUnrollHeuristic(op, builder, moduleTranslation);
9690 })
9691 .Case([&](omp::TileOp op) {
9692 return applyTile(op, builder, moduleTranslation);
9693 })
9694 .Case([&](omp::FuseOp op) {
9695 return applyFuse(op, builder, moduleTranslation);
9696 })
9697 .Case([&](omp::TargetAllocMemOp) {
9698 return convertTargetAllocMemOp(*op, builder, moduleTranslation);
9699 })
9700 .Case([&](omp::TargetFreeMemOp) {
9701 return convertTargetFreeMemOp(*op, builder, moduleTranslation);
9702 })
9703 .Case([&](omp::AllocateDirOp) {
9704 return convertAllocateDirOp(*op, builder, moduleTranslation, *this);
9705 })
9706 .Case([&](omp::AllocateFreeOp) {
9707 return convertAllocateFreeOp(*op, builder, moduleTranslation,
9708 *this);
9709 })
9710 .Case([&](omp::AllocSharedMemOp op) {
9711 return convertAllocSharedMemOp(op, builder, moduleTranslation);
9712 })
9713 .Case([&](omp::FreeSharedMemOp op) {
9714 return convertFreeSharedMemOp(op, builder, moduleTranslation);
9715 })
9716 .Case([&](omp::GroupprivateOp) {
9717 return convertOmpGroupprivate(*op, builder, moduleTranslation);
9718 })
9719 .Default([&](Operation *inst) {
9720 return inst->emitError()
9721 << "not yet implemented: " << inst->getName();
9722 });
9723
9724 if (isOutermostLoopWrapper)
9725 moduleTranslation.stackPop();
9726
9727 return result;
9728}
9729
9731 registry.insert<omp::OpenMPDialect>();
9732 registry.addExtension(+[](MLIRContext *ctx, omp::OpenMPDialect *dialect) {
9733 dialect->addInterfaces<OpenMPDialectLLVMIRTranslationInterface>();
9734 });
9735}
9736
9738 DialectRegistry registry;
9740 context.appendDialectRegistry(registry);
9741}
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 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 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 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 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
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.