MLIR 24.0.0git
OpenMPToLLVMIRTranslation.cpp
Go to the documentation of this file.
1//===- OpenMPToLLVMIRTranslation.cpp - Translate OpenMP dialect to LLVM IR-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements a translation between the MLIR OpenMP dialect and LLVM
10// IR.
11//
12//===----------------------------------------------------------------------===//
21#include "mlir/IR/Operation.h"
23#include "mlir/Support/LLVM.h"
26
27#include "llvm/ADT/ArrayRef.h"
28#include "llvm/ADT/SmallVector.h"
29#include "llvm/ADT/TypeSwitch.h"
30#include "llvm/Frontend/OpenMP/OMPConstants.h"
31#include "llvm/Frontend/OpenMP/OMPIRBuilder.h"
32#include "llvm/IR/Constants.h"
33#include "llvm/IR/DebugInfoMetadata.h"
34#include "llvm/IR/DerivedTypes.h"
35#include "llvm/IR/IRBuilder.h"
36#include "llvm/IR/MDBuilder.h"
37#include "llvm/IR/ReplaceConstant.h"
38#include "llvm/Support/AMDGPUAddrSpace.h"
39#include "llvm/Support/CommandLine.h"
40#include "llvm/Support/FileSystem.h"
41#include "llvm/Support/MathExtras.h"
42#include "llvm/Support/NVPTXAddrSpace.h"
43#include "llvm/Support/SaveAndRestore.h"
44#include "llvm/Support/VirtualFileSystem.h"
45#include "llvm/TargetParser/Triple.h"
46#include "llvm/Transforms/Utils/ModuleUtils.h"
47
48#include <cstdint>
49#include <iterator>
50#include <numeric>
51#include <optional>
52#include <utility>
53
54using namespace mlir;
55
57getPrivateDataAddress(llvm::IRBuilderBase &builder,
58 LLVM::ModuleTranslation &moduleTranslation,
59 omp::PrivateClauseOp privatizer, llvm::Value *var);
60
61namespace {
62/// Stores the option value so the OpenMP-to-LLVM IR translation can read it
63/// outside registerOpenMPTranslationCLOptions(). The local cl::opt avoids a
64/// global constructor in MLIR and writes to this variable through cl::location.
65/// Without registration, false keeps Virtual enabled for eligible loops.
66static bool disableVirtualCollapse = false;
67
68static llvm::omp::ScheduleKind
69convertToScheduleKind(std::optional<omp::ClauseScheduleKind> schedKind) {
70 if (!schedKind.has_value())
71 return llvm::omp::OMP_SCHEDULE_Default;
72 switch (schedKind.value()) {
73 case omp::ClauseScheduleKind::Static:
74 return llvm::omp::OMP_SCHEDULE_Static;
75 case omp::ClauseScheduleKind::Dynamic:
76 return llvm::omp::OMP_SCHEDULE_Dynamic;
77 case omp::ClauseScheduleKind::Guided:
78 return llvm::omp::OMP_SCHEDULE_Guided;
79 case omp::ClauseScheduleKind::Auto:
80 return llvm::omp::OMP_SCHEDULE_Auto;
81 case omp::ClauseScheduleKind::Runtime:
82 return llvm::omp::OMP_SCHEDULE_Runtime;
83 case omp::ClauseScheduleKind::Distribute:
84 return llvm::omp::OMP_SCHEDULE_Distribute;
85 }
86 llvm_unreachable("unhandled schedule clause argument");
87}
88
89/// ModuleTranslation stack frame for OpenMP operations. This keeps track of the
90/// blocks in which allocas are emitted.
91class OpenMPAllocStackFrame
92 : public StateStackFrameBase<OpenMPAllocStackFrame> {
93public:
95
96 explicit OpenMPAllocStackFrame(
97 llvm::BasicBlock *allocBlock,
98 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks)
99 : allocBlock(allocBlock), deallocBlocks(deallocBlocks) {}
100 llvm::BasicBlock *allocBlock;
101 llvm::SmallVector<llvm::BasicBlock *> deallocBlocks;
102};
103
104/// Stack frame to hold a \see llvm::CanonicalLoopInfo representing the
105/// collapsed canonical loop information corresponding to an \c omp.loop_nest
106/// operation.
107class OpenMPLoopInfoStackFrame
108 : public StateStackFrameBase<OpenMPLoopInfoStackFrame> {
109public:
110 MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(OpenMPLoopInfoStackFrame)
111 llvm::CanonicalLoopInfo *loopInfo = nullptr;
112};
113
114/// OpenMP state that lives as long as the module translation: the
115/// OpenMPIRBuilder associated with the LLVM module being constructed and the
116/// not yet consumed canonical loop handles.
117class OpenMPTranslationState : public LLVM::ModuleTranslation::Extension {
118public:
120
121 explicit OpenMPTranslationState(llvm::Module &module) : ompBuilder(module) {
122 // Flags represented as top-level OpenMP dialect attributes are set in
123 // `OpenMPDialectLLVMIRTranslationInterface::amendOperation()`. Here we set
124 // the default configuration.
125 llvm::OpenMPIRBuilderConfig config(
126 /* IsTargetDevice = */ false, /* IsGPU = */ false,
127 /* OpenMPOffloadMandatory = */ false,
128 /* HasRequiresReverseOffload = */ false,
129 /* HasRequiresUnifiedAddress = */ false,
130 /* HasRequiresUnifiedSharedMemory = */ false,
131 /* HasRequiresDynamicAllocators = */ false);
132 config.setDefaultTargetAS(module.getDataLayout().getProgramAddressSpace());
133 config.setRuntimeCC(module.getTargetTriple().isSPIRV()
134 ? llvm::CallingConv::SPIR_FUNC
135 : llvm::CallingConv::C);
136 ompBuilder.setConfig(std::move(config));
137 ompBuilder.initialize();
138 }
139
140 ~OpenMPTranslationState() override {
141 if (!ompBuilder.isFinalized())
142 ompBuilder.finalize();
143 }
144
145 LogicalResult finalize() override {
146 ompBuilder.finalize();
147 return success();
148 }
149
150 /// Builder for LLVM IR generation of OpenMP constructs.
151 llvm::OpenMPIRBuilder ompBuilder;
152
153 /// List of not yet consumed MLIR loop handles (represented by an omp.new_cli
154 /// operation which creates a value of type CanonicalLoopInfoType) and their
155 /// LLVM-IR representation as CanonicalLoopInfo which is managed by the
156 /// OpenMPIRBuilder.
158};
159} // namespace
160
161static OpenMPTranslationState &
163 return moduleTranslation.getOrCreateExtension<OpenMPTranslationState>(
164 *moduleTranslation.getLLVMModule());
165}
166
167/// Returns the OpenMP IR builder associated with the LLVM IR module being
168/// constructed.
169static llvm::OpenMPIRBuilder *
171 return &getOpenMPState(moduleTranslation).ompBuilder;
172}
173
174/// Find the LLVM-IR loop that represents an MLIR loop.
175static llvm::CanonicalLoopInfo *
177 auto cli = mlir.getDefiningOp<omp::NewCliOp>();
178 llvm::CanonicalLoopInfo *result =
179 getOpenMPState(moduleTranslation).loopMapping.lookup(cli);
180 assert(result && "attempt to get non-existing loop");
181 return result;
182}
183
184/// Mark an OpenMP loop as having been consumed.
185static void invalidateOmpLoop(LLVM::ModuleTranslation &moduleTranslation,
186 Value mlir) {
187 auto cli = mlir.getDefiningOp<omp::NewCliOp>();
188 getOpenMPState(moduleTranslation).loopMapping.erase(cli);
189}
190
191/// Map an MLIR OpenMP dialect CanonicalLoopInfo to its lowered LLVM-IR
192/// OpenMPIRBuilder CanonicalLoopInfo.
193static void mapOmpLoop(LLVM::ModuleTranslation &moduleTranslation, Value mlir,
194 llvm::CanonicalLoopInfo *llvm) {
195 assert(llvm && "argument must be non-null");
196 auto cli = mlir.getDefiningOp<omp::NewCliOp>();
197 llvm::CanonicalLoopInfo *&cur =
198 getOpenMPState(moduleTranslation).loopMapping[cli];
199 assert(cur == nullptr && "attempting to map a loop that is already mapped");
200 cur = llvm;
201}
202
203namespace {
204/// Custom error class to signal translation errors that don't need reporting,
205/// since encountering them will have already triggered relevant error messages.
206///
207/// Its purpose is to serve as the glue between MLIR failures represented as
208/// \see LogicalResult instances and \see llvm::Error instances used to
209/// propagate errors through the \see llvm::OpenMPIRBuilder. Generally, when an
210/// error of the first type is raised, a message is emitted directly (the \see
211/// LogicalResult itself does not hold any information). If we need to forward
212/// this error condition as an \see llvm::Error while avoiding triggering some
213/// redundant error reporting later on, we need a custom \see llvm::ErrorInfo
214/// class to just signal this situation has happened.
215///
216/// For example, this class should be used to trigger errors from within
217/// callbacks passed to the \see OpenMPIRBuilder when they were triggered by the
218/// translation of their own regions. This unclutters the error log from
219/// redundant messages.
220class PreviouslyReportedError
221 : public llvm::ErrorInfo<PreviouslyReportedError> {
222public:
223 void log(raw_ostream &) const override {
224 // Do not log anything.
225 }
226
227 std::error_code convertToErrorCode() const override {
228 llvm_unreachable(
229 "PreviouslyReportedError doesn't support ECError conversion");
230 }
231
232 // Used by ErrorInfo::classID.
233 static char ID;
234};
235
236char PreviouslyReportedError::ID = 0;
237
238/*
239 * Custom class for processing linear clause for omp.wsloop
240 * and omp.simd. Linear clause translation requires setup,
241 * initialization, update, and finalization at varying
242 * basic blocks in the IR. This class helps maintain
243 * internal state to allow consistent translation in
244 * each of these stages.
245 */
246
247class LinearClauseProcessor {
248
249private:
250 SmallVector<llvm::Type *> linearVarTypes;
251 SmallVector<llvm::Value *> linearSteps;
252 // The `linear` privatizer, original and private copy of each linear var.
253 SmallVector<omp::PrivateClauseOp> linearPrivatizers;
254 SmallVector<llvm::Value *> linearOrigVars;
255 SmallVector<llvm::Value *> linearPrivateVars;
256 // The value of each linear var on entry to the loop.
257 SmallVector<llvm::Value *> linearStartVals;
258 llvm::BasicBlock *linearFinalizationBB;
259 llvm::BasicBlock *linearExitBB;
260 llvm::BasicBlock *linearLastIterExitBB;
261 std::optional<size_t> linearLoopIVIndex;
262
263 // Address of the value of linear var `index` held by `var`.
264 llvm::Expected<llvm::Value *>
265 getAddress(llvm::IRBuilderBase &builder,
266 LLVM::ModuleTranslation &moduleTranslation, size_t index,
267 llvm::Value *var) {
268 return getPrivateDataAddress(builder, moduleTranslation,
269 linearPrivatizers[index], var);
270 }
271
272 // Copy the current value of each linear var back to the original.
273 llvm::Error storeLinearVars(llvm::IRBuilderBase &builder,
274 LLVM::ModuleTranslation &moduleTranslation) {
275 for (size_t index = 0; index < linearOrigVars.size(); index++) {
276 llvm::Expected<llvm::Value *> from = getAddress(
277 builder, moduleTranslation, index, linearPrivateVars[index]);
278 if (!from)
279 return from.takeError();
280 llvm::LoadInst *linearVarTemp =
281 builder.CreateLoad(linearVarTypes[index], *from);
282 llvm::Expected<llvm::Value *> to =
283 getAddress(builder, moduleTranslation, index, linearOrigVars[index]);
284 if (!to)
285 return to.takeError();
286 builder.CreateStore(linearVarTemp, *to);
287 }
288 return llvm::Error::success();
289 }
290
291public:
292 // Register type for the linear variables
293 void registerType(LLVM::ModuleTranslation &moduleTranslation,
294 mlir::Attribute &ty) {
295 linearVarTypes.push_back(moduleTranslation.convertType(
296 mlir::cast<mlir::TypeAttr>(ty).getValue()));
297 }
298
299 // Register a linear var, allocating space for its start value at `allocaIP`
300 void createLinearVar(llvm::IRBuilderBase &builder,
301 const llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
302 omp::PrivateClauseOp privatizer, llvm::Value *origVar,
303 llvm::Value *privateVar, int idx) {
304 llvm::IRBuilderBase::InsertPointGuard guard(builder);
305 builder.SetInsertPoint(allocaIP.getNodeParent()->getTerminator());
306 linearStartVals.push_back(
307 builder.CreateAlloca(linearVarTypes[idx], nullptr, ".linear_var"));
308 linearPrivatizers.push_back(privatizer);
309 linearOrigVars.push_back(origVar);
310 linearPrivateVars.push_back(privateVar);
311 }
312
313 // Initialize linear step
314 inline void initLinearStep(LLVM::ModuleTranslation &moduleTranslation,
315 mlir::Value &linearStep) {
316 linearSteps.push_back(moduleTranslation.lookupValue(linearStep));
317 }
318
319 // Emit IR for initialization of linear variables
320 llvm::Error initLinearVar(llvm::IRBuilderBase &builder,
321 LLVM::ModuleTranslation &moduleTranslation,
322 llvm::BasicBlock *loopPreHeader) {
323 builder.SetInsertPoint(loopPreHeader->getTerminator());
324 for (size_t index = 0; index < linearOrigVars.size(); index++) {
325 llvm::Expected<llvm::Value *> orig =
326 getAddress(builder, moduleTranslation, index, linearOrigVars[index]);
327 if (!orig)
328 return orig.takeError();
329 llvm::LoadInst *linearVarLoad =
330 builder.CreateLoad(linearVarTypes[index], *orig);
331 builder.CreateStore(linearVarLoad, linearStartVals[index]);
332 }
333 return llvm::Error::success();
334 }
335
336 // Find linear iteration variable and save it for later updates
337 LogicalResult initLinearIV(omp::SimdOp simdOp) {
338 auto loopOp = cast<omp::LoopNestOp>(simdOp.getWrappedLoop());
339 // NOTE iteration variables can only be linear in non-nested loops.
340 if (loopOp.getIVs().size() != 1)
341 return success();
342 // Currently, frontends using `omp.simd` always generate a store from the
343 // `omp.loop_nest`'s IV to the corresponding iteration variable, possibly
344 // through the data address of its private copy.
345 //
346 // TODO Add an attribute to `omp.loop_nest` that explicitly lists the
347 // variables that correspond to the loop induction variables.
348 MutableArrayRef<BlockArgument> linearArgs =
349 cast<omp::BlockArgOpenMPOpInterface>(*simdOp).getLinearBlockArgs();
350 SmallVector<Value> ivValues{loopOp.getIVs().front()};
351 for (Operation *user : loopOp.getIVs().front().getUsers())
352 if (isa<LLVM::SExtOp, LLVM::ZExtOp, LLVM::TruncOp>(user))
353 ivValues.push_back(user->getResult(0));
354
355 for (Value ivValue : ivValues) {
356 for (Operation *user : ivValue.getUsers()) {
357 auto storeOp = dyn_cast<LLVM::StoreOp>(user);
358 if (!storeOp || storeOp.getValue() != ivValue)
359 continue;
360 // Look through loads, GEPs and copies of a private copy, e.g. of a
361 // descriptor, to find the linear block argument the IV is stored to.
362 Value addr = storeOp.getAddr();
363 while (true) {
364 Operation *def = addr.getDefiningOp();
365 if (auto load = dyn_cast_if_present<LLVM::LoadOp>(def)) {
366 addr = load.getAddr();
367 } else if (auto gep = dyn_cast_if_present<LLVM::GEPOp>(def)) {
368 addr = gep.getBase();
369 } else {
370 auto copy = llvm::find_if(addr.getUsers(), [&](Operation *user) {
371 auto memcpy = dyn_cast<LLVM::MemcpyOp>(user);
372 return memcpy && memcpy.getDst() == addr &&
373 llvm::is_contained(linearArgs, memcpy.getSrc());
374 });
375 if (copy == addr.getUsers().end())
376 break;
377 addr = cast<LLVM::MemcpyOp>(*copy).getSrc();
378 }
379 }
380 auto *it = llvm::find(linearArgs, addr);
381 if (it == linearArgs.end())
382 continue;
383 size_t index = std::distance(linearArgs.begin(), it);
384 if (linearLoopIVIndex && *linearLoopIVIndex != index)
385 return simdOp.emitError(
386 "Could not determine the linear variable associated with the "
387 "loop nest induction variable");
388 linearLoopIVIndex = index;
389 }
390 }
391 return success();
392 }
393
394 // Emit IR for updating Linear variables
395 llvm::Error updateLinearVar(llvm::IRBuilderBase &builder,
396 LLVM::ModuleTranslation &moduleTranslation,
397 llvm::BasicBlock *loopBody,
398 llvm::Value *loopInductionVar) {
399 builder.SetInsertPoint(loopBody->getTerminator());
400 for (size_t index = 0; index < linearStartVals.size(); index++) {
401 llvm::Type *linearVarType = linearVarTypes[index];
402 llvm::Value *iv = loopInductionVar;
403 llvm::Value *step = linearSteps[index];
404 llvm::Expected<llvm::Value *> result = getAddress(
405 builder, moduleTranslation, index, linearPrivateVars[index]);
406 if (!result)
407 return result.takeError();
408
409 if (!iv->getType()->isIntegerTy())
410 llvm_unreachable("OpenMP loop induction variable must be an integer "
411 "type");
412
413 if (linearVarType->isIntegerTy()) {
414 // Integer path: normalize all arithmetic to linearVarType
415 iv = builder.CreateSExtOrTrunc(iv, linearVarType);
416 step = builder.CreateSExtOrTrunc(step, linearVarType);
417
418 llvm::LoadInst *linearVarStart =
419 builder.CreateLoad(linearVarType, linearStartVals[index]);
420 llvm::Value *mulInst = builder.CreateMul(iv, step);
421 llvm::Value *addInst = builder.CreateAdd(linearVarStart, mulInst);
422 builder.CreateStore(addInst, *result);
423 } else if (linearVarType->isFloatingPointTy()) {
424 // Float path: perform multiply in integer, then convert to float
425 step = builder.CreateSExtOrTrunc(step, iv->getType());
426 llvm::Value *mulInst = builder.CreateMul(iv, step);
427
428 llvm::LoadInst *linearVarStart =
429 builder.CreateLoad(linearVarType, linearStartVals[index]);
430 llvm::Value *mulFp = builder.CreateSIToFP(mulInst, linearVarType);
431 llvm::Value *addInst = builder.CreateFAdd(linearVarStart, mulFp);
432 builder.CreateStore(addInst, *result);
433 } else {
434 llvm_unreachable(
435 "Linear variable must be of integer or floating-point type");
436 }
437 }
438 return llvm::Error::success();
439 }
440
441 // Emit IR for updating linear iteration variables on loop exit
442 llvm::Error updateLinearIV(llvm::IRBuilderBase &builder,
443 LLVM::ModuleTranslation &moduleTranslation) {
444 if (!linearLoopIVIndex)
445 return llvm::Error::success();
446 size_t index = *linearLoopIVIndex;
447
448 // Add one more step to the linear iteration variable
449 llvm::Type *varType = linearVarTypes[index];
450 if (!varType->isIntegerTy())
451 llvm_unreachable("Linear iteration variable must be of integer type");
452 llvm::Expected<llvm::Value *> var =
453 getAddress(builder, moduleTranslation, index, linearPrivateVars[index]);
454 if (!var)
455 return var.takeError();
456 llvm::Value *step = builder.CreateSExtOrTrunc(linearSteps[index], varType);
457 llvm::Value *val = builder.CreateLoad(varType, *var);
458 llvm::Value *addInst = builder.CreateAdd(val, step);
459 builder.CreateStore(addInst, *var);
460 return llvm::Error::success();
461 }
462
463 // Linear variable finalization is conditional on the last logical iteration.
464 // Create BB splits to manage the same.
465 void splitLinearFiniBB(llvm::IRBuilderBase &builder,
466 llvm::BasicBlock *loopExit) {
467 linearFinalizationBB = loopExit->splitBasicBlock(
468 loopExit->getTerminator(), "omp_loop.linear_finalization");
469 linearExitBB = linearFinalizationBB->splitBasicBlock(
470 linearFinalizationBB->getTerminator(), "omp_loop.linear_exit");
471 linearLastIterExitBB = linearFinalizationBB->splitBasicBlock(
472 linearFinalizationBB->getTerminator(), "omp_loop.linear_lastiter_exit");
473 }
474
475 // Finalize the linear vars
476 llvm::OpenMPIRBuilder::InsertPointOrErrorTy
477 finalizeLinearVar(llvm::IRBuilderBase &builder,
478 LLVM::ModuleTranslation &moduleTranslation,
479 llvm::Value *lastIter) {
480 // Emit condition to check whether last logical iteration is being executed
481 builder.SetInsertPoint(linearFinalizationBB->getTerminator());
482 llvm::Value *loopLastIterLoad = builder.CreateLoad(
483 llvm::Type::getInt32Ty(builder.getContext()), lastIter);
484 llvm::Value *isLast =
485 builder.CreateCmp(llvm::CmpInst::ICMP_NE, loopLastIterLoad,
486 llvm::ConstantInt::get(
487 llvm::Type::getInt32Ty(builder.getContext()), 0));
488 // Store the linear variable values to original variables.
489 builder.SetInsertPoint(linearLastIterExitBB->getTerminator());
490 if (llvm::Error err = storeLinearVars(builder, moduleTranslation))
491 return std::move(err);
492
493 // Create conditional branch such that the linear variable
494 // values are stored to original variables only at the
495 // last logical iteration
496 builder.SetInsertPoint(linearFinalizationBB->getTerminator());
497 builder.CreateCondBr(isLast, linearLastIterExitBB, linearExitBB);
498 linearFinalizationBB->getTerminator()->eraseFromParent();
499 // Emit barrier
500 builder.SetInsertPoint(linearExitBB->getTerminator());
501 return getOpenMPBuilder(moduleTranslation)
502 ->createBarrier(builder, llvm::omp::OMPD_barrier);
503 }
504
505 // Emit stores for linear variables. Useful in case of SIMD
506 // construct.
507 llvm::Error
508 emitStoresForLinearVar(llvm::IRBuilderBase &builder,
509 LLVM::ModuleTranslation &moduleTranslation) {
510 return storeLinearVars(builder, moduleTranslation);
511 }
512};
513} // namespace
514
515/// Looks up from the operation from and returns the PrivateClauseOp with
516/// name symbolName
517static omp::PrivateClauseOp findPrivatizer(Operation *from,
518 SymbolRefAttr symbolName) {
519 omp::PrivateClauseOp privatizer =
521 symbolName);
522 assert(privatizer && "privatizer not found in the symbol table");
523 return privatizer;
524}
525
526/// Check whether translation to LLVM IR for the given operation is currently
527/// supported. If not, descriptive diagnostics will be emitted to let users know
528/// this is a not-yet-implemented feature.
529///
530/// \returns success if no unimplemented features are needed to translate the
531/// given operation.
532static LogicalResult checkImplementationStatus(Operation &op) {
533 auto todo = [&op](StringRef clauseName) {
534 return op.emitError() << "not yet implemented: Unhandled clause "
535 << clauseName << " in " << op.getName()
536 << " operation";
537 };
538
539 auto checkAllocate = [&todo](auto op, LogicalResult &result) {
540 if (!op.getAllocateVars().empty() || !op.getAllocatorVars().empty())
541 result = todo("allocate");
542 };
543 auto checkBare = [&todo](auto op, LogicalResult &result) {
544 if (op.getKernelType() == omp::TargetExecMode::bare)
545 result = todo("ompx_bare");
546 };
547 auto checkDepend = [&todo](auto op, LogicalResult &result) {
548 if (!op.getDependVars().empty() || op.getDependKinds() ||
549 !op.getDependIterated().empty() || op.getDependIteratedKinds())
550 result = todo("depend");
551 };
552 auto checkHint = [](auto op, LogicalResult &) {
553 if (op.getHint())
554 op.emitWarning("hint clause discarded");
555 };
556 auto checkInReduction = [&todo](auto op, LogicalResult &result) {
557 if (isa<omp::TargetOp, omp::TaskOp, omp::TaskloopContextOp>(
558 op.getOperation())) {
559 if (auto byrefAttr = op.getInReductionByref()) {
560 for (bool isByRef : *byrefAttr) {
561 if (isByRef) {
562 result = todo("in_reduction with byref modifier");
563 return;
564 }
565 }
566 }
567 if (isa<omp::TargetOp>(op.getOperation())) {
568 if (auto inReductionSyms = op.getInReductionSyms()) {
569 for (auto sym :
570 (*inReductionSyms).template getAsRange<SymbolRefAttr>()) {
571 auto decl =
573 op, sym);
574 assert(decl &&
575 "symbol resolution should be guaranteed by the op verifier");
576 if (decl.getInitializerRegion().front().getNumArguments() != 1) {
577 result = todo("in_reduction with two-argument initializer");
578 return;
579 }
580 if (!decl.getCleanupRegion().empty()) {
581 result = todo("in_reduction with cleanup region");
582 return;
583 }
584 }
585 }
586 }
587 } else if (!op.getInReductionVars().empty() || op.getInReductionByref() ||
588 op.getInReductionSyms()) {
589 result = todo("in_reduction");
590 }
591 };
592 auto checkNowait = [&todo](auto op, LogicalResult &result) {
593 if (op.getNowait())
594 result = todo("nowait");
595 };
596 auto checkOrder = [&todo](auto op, LogicalResult &result) {
597 if (op.getOrder() || op.getOrderMod())
598 result = todo("order");
599 };
600 auto checkPrivate = [&todo](auto op, LogicalResult &result) {
601 if (!op.getPrivateVars().empty() || op.getPrivateSyms())
602 result = todo("privatization");
603 };
604 auto checkReduction = [&todo](auto op, LogicalResult &result) {
605 if (isa<omp::TeamsOp>(op))
606 if (!op.getReductionVars().empty() || op.getReductionByref() ||
607 op.getReductionSyms())
608 result = todo("reduction");
609 for (unsigned i = 0, e = op.getNumReductionVars(); i != e; ++i) {
610 omp::ReductionModifier mod = op.getReductionModifier(i);
611 if (mod == omp::ReductionModifier::defaultmod)
612 continue;
613 // Task modifiers are supported only on parallel and worksharing
614 // constructs, and only for non-byref reduction variables.
615 bool taskModifierSupported =
616 mod == omp::ReductionModifier::task &&
617 isa<omp::ParallelOp, omp::WsloopOp, omp::SectionsOp>(op);
618 if (!taskModifierSupported) {
619 result = todo("reduction with modifier");
620 } else if (auto byref = op.getReductionByref()) {
621 if ((*byref)[i])
622 result = todo("task reduction modifier with by-ref reduction");
623 }
624 }
625 };
626 auto checkTaskReductionByref = [&todo](auto op, LogicalResult &result) {
627 if (auto byrefAttr = op.getTaskReductionByref())
628 for (bool isByRef : *byrefAttr)
629 if (isByRef) {
630 result = todo("task_reduction with byref modifier");
631 return;
632 }
633 };
634 auto checkReductionByref = [&todo](auto op, LogicalResult &result) {
635 if (auto byrefAttr = op.getReductionByref())
636 for (bool isByRef : *byrefAttr)
637 if (isByRef) {
638 result = todo("reduction with byref modifier");
639 return;
640 }
641 };
642 auto checkNumTeams = [&todo](auto op, LogicalResult &result) {
643 if (op.hasNumTeamsMultiDim())
644 result = todo("num_teams with multi-dimensional values");
645 };
646 auto checkNumThreads = [&todo](auto op, LogicalResult &result) {
647 if (op.hasNumThreadsMultiDim())
648 result = todo("num_threads with multi-dimensional values");
649 };
650
651 auto checkThreadLimit = [&todo](auto op, LogicalResult &result) {
652 if (op.hasThreadLimitMultiDim())
653 result = todo("thread_limit with multi-dimensional values");
654 };
655 auto checkMap = [&todo](auto op, LogicalResult &result) {
656 if (!op.getMapIterated().empty())
657 result = todo("map/motion clause with iterator modifier");
658 };
659
660 auto checkDynGroupprivate = [&todo](auto op, LogicalResult &result) {
661 if (op.getDynGroupprivateSize())
662 result = todo("dyn_groupprivate");
663 };
664
665 LogicalResult result = success();
667 .Case([&](omp::DistributeOp op) {
668 checkAllocate(op, result);
669 checkOrder(op, result);
670 })
671 .Case([&](omp::SectionsOp op) {
672 checkAllocate(op, result);
673 checkPrivate(op, result);
674 checkReduction(op, result);
675 })
676 .Case([&](omp::ScopeOp op) { checkReduction(op, result); })
677 .Case([&](omp::SingleOp op) {
678 checkAllocate(op, result);
679 checkPrivate(op, result);
680 })
681 .Case([&](omp::TeamsOp op) {
682 checkAllocate(op, result);
683 checkPrivate(op, result);
684 checkNumTeams(op, result);
685 checkThreadLimit(op, result);
686 checkDynGroupprivate(op, result);
687 })
688 .Case([&](omp::TaskOp op) {
689 checkAllocate(op, result);
690 checkInReduction(op, result);
691 })
692 .Case([&](omp::TaskgroupOp op) {
693 checkAllocate(op, result);
694 checkTaskReductionByref(op, result);
695 })
696 .Case([&](omp::DispatchOp op) {
697 // OpenMP 5.1 dispatch creates an explicit task; nowait controls whether
698 // it is included. Diagnose unsupported asynchronous tasking before 5.2,
699 // where the nowait property has no effect on dispatch.
701 op->getParentOfType<ModuleOp>(), /*fallback=*/51);
702 if (version < 52)
703 checkNowait(op, result);
704 })
705 .Case([&](omp::TaskloopContextOp op) {
706 checkAllocate(op, result);
707 checkInReduction(op, result);
708 checkReduction(op, result);
709 checkReductionByref(op, result);
710 })
711 .Case([&](omp::WsloopOp op) {
712 checkAllocate(op, result);
713 checkOrder(op, result);
714 checkReduction(op, result);
715 })
716 .Case([&](omp::ParallelOp op) {
717 checkReduction(op, result);
718 checkNumThreads(op, result);
719 })
720 .Case([&](omp::SimdOp op) { checkReduction(op, result); })
721 .Case<omp::AtomicReadOp, omp::AtomicWriteOp, omp::AtomicUpdateOp,
722 omp::AtomicCaptureOp>([&](auto op) { checkHint(op, result); })
723 .Case([&](omp::AtomicCompareOp op) {
724 checkHint(op, result);
725 Region &region = op.getRegion();
726 if (region.empty())
727 return;
728 mlir::Type argType = region.front().getArgument(0).getType();
729 auto structTy = dyn_cast<LLVM::LLVMStructType>(argType);
730 if (!structTy)
731 return;
732 DataLayout dl = DataLayout(op->getParentOfType<ModuleOp>());
733 unsigned totalBits = dl.getTypeSizeInBits(structTy);
734 if (totalBits > 128)
735 result = todo("compare for complex types wider than 128 bits");
736 })
737 .Case<omp::TargetEnterDataOp, omp::TargetExitDataOp>([&](auto op) {
738 checkDepend(op, result);
739 checkMap(op, result);
740 })
741 .Case([&](omp::TargetUpdateOp op) {
742 checkDepend(op, result);
743 checkMap(op, result);
744 })
745 .Case([&](omp::TargetOp op) {
746 checkAllocate(op, result);
747 checkBare(op, result);
748 checkInReduction(op, result);
749 checkMap(op, result);
750 checkThreadLimit(op, result);
751 })
752 .Case([&](omp::TargetDataOp op) { checkMap(op, result); })
753 .Case([&](omp::DeclareMapperInfoOp op) { checkMap(op, result); })
754 .Default([](Operation &) {
755 // Assume all clauses for an operation can be translated unless they are
756 // checked above.
757 });
758 return result;
759}
760
761static LogicalResult handleError(llvm::Error error, Operation &op) {
762 LogicalResult result = success();
763 if (error) {
764 llvm::handleAllErrors(
765 std::move(error),
766 [&](const PreviouslyReportedError &) { result = failure(); },
767 [&](const llvm::ErrorInfoBase &err) {
768 result = op.emitError(err.message());
769 });
770 }
771 return result;
772}
773
774template <typename T>
775static LogicalResult handleError(llvm::Expected<T> &result, Operation &op) {
776 if (!result)
777 return handleError(result.takeError(), op);
778
779 return success();
780}
781
782/// Find the insertion point for allocas given the current insertion point for
783/// normal operations in the builder.
784static llvm::OpenMPIRBuilder::InsertPointTy findAllocInsertPoints(
785 llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation,
786 llvm::SmallVectorImpl<llvm::BasicBlock *> *deallocBlocks = nullptr) {
787 // If there is an allocation insertion point on stack, i.e. we are in a nested
788 // operation and a specific point was provided by some surrounding operation,
789 // use it.
790 llvm::OpenMPIRBuilder::InsertPointTy allocInsertPoint;
791 llvm::ArrayRef<llvm::BasicBlock *> deallocInsertPoints;
792 WalkResult walkResult = moduleTranslation.stackWalk<OpenMPAllocStackFrame>(
793 [&](OpenMPAllocStackFrame &frame) {
794 allocInsertPoint = frame.allocBlock->getFirstInsertionPt();
795 deallocInsertPoints = frame.deallocBlocks;
796 return WalkResult::interrupt();
797 });
798 // In cases with multiple levels of outlining, the tree walk might find an
799 // insertion point that is inside the original function while the builder
800 // insertion point is inside the outlined function. We need to make sure that
801 // we do not use it in those cases.
802 if (walkResult.wasInterrupted() &&
803 allocInsertPoint.getNodeParent()->getParent() ==
804 builder.GetInsertBlock()->getParent()) {
805 if (deallocBlocks)
806 deallocBlocks->insert(deallocBlocks->end(), deallocInsertPoints.begin(),
807 deallocInsertPoints.end());
808 return allocInsertPoint;
809 }
810
811 // Otherwise, insert to the entry block of the surrounding function.
812 // If the current IRBuilder InsertPoint is the function's entry, it cannot
813 // also be used for alloca insertion which would result in insertion order
814 // confusion. Create a new BasicBlock for the Builder and use the entry block
815 // for the allocs.
816 // TODO: Create a dedicated alloca BasicBlock at function creation such that
817 // we do not need to move the current InsertPoint here.
818 if (builder.GetInsertBlock() ==
819 &builder.GetInsertBlock()->getParent()->getEntryBlock()) {
820 assert(builder.GetInsertPoint() == builder.GetInsertBlock()->end() &&
821 "Assuming end of basic block");
822 llvm::BasicBlock *entryBB = llvm::BasicBlock::Create(
823 builder.getContext(), "entry", builder.GetInsertBlock()->getParent(),
824 builder.GetInsertBlock()->getNextNode());
825 builder.CreateBr(entryBB);
826 builder.SetInsertPoint(entryBB);
827 }
828
829 // Collect exit blocks, which is where explicit deallocations should happen in
830 // this case.
831 if (deallocBlocks) {
832 for (llvm::BasicBlock &block : *builder.GetInsertBlock()->getParent()) {
833 // TODO: This currently results in no blocks being added to the list when
834 // all exit blocks of the enclosing function have not been lowered before
835 // this is reached.
836 llvm::Instruction *terminator = block.getTerminatorOrNull();
837 if (isa_and_present<llvm::ReturnInst>(terminator))
838 deallocBlocks->emplace_back(&block);
839 }
840 }
841
842 llvm::BasicBlock &funcEntryBlock =
843 builder.GetInsertBlock()->getParent()->getEntryBlock();
844 return funcEntryBlock.getFirstInsertionPt();
845}
846
847/// Find the loop information structure for the loop nest being translated. It
848/// will return a `null` value unless called from the translation function for
849/// a loop wrapper operation after successfully translating its body.
850static llvm::CanonicalLoopInfo *
852 llvm::CanonicalLoopInfo *loopInfo = nullptr;
853 moduleTranslation.stackWalk<OpenMPLoopInfoStackFrame>(
854 [&](OpenMPLoopInfoStackFrame &frame) {
855 loopInfo = frame.loopInfo;
856 return WalkResult::interrupt();
857 });
858 return loopInfo;
859}
860
861/// Converts the given region that appears within an OpenMP dialect operation to
862/// LLVM IR, creating a branch from the `sourceBlock` to the entry block of the
863/// region, and a branch from any block with an successor-less OpenMP terminator
864/// to `continuationBlock`. Populates `continuationBlockPHIs` with the PHI nodes
865/// of the continuation block if provided.
867 Region &region, StringRef blockName, llvm::IRBuilderBase &builder,
868 LLVM::ModuleTranslation &moduleTranslation,
869 SmallVectorImpl<llvm::PHINode *> *continuationBlockPHIs = nullptr) {
870 bool isLoopWrapper = isa<omp::LoopWrapperInterface>(region.getParentOp());
871
872 llvm::BasicBlock *continuationBlock =
873 splitBB(builder, true, "omp.region.cont");
874 llvm::BasicBlock *sourceBlock = builder.GetInsertBlock();
875
876 llvm::LLVMContext &llvmContext = builder.getContext();
877 for (Block &bb : region) {
878 llvm::BasicBlock *llvmBB = llvm::BasicBlock::Create(
879 llvmContext, blockName, builder.GetInsertBlock()->getParent(),
880 builder.GetInsertBlock()->getNextNode());
881 moduleTranslation.mapBlock(&bb, llvmBB);
882 }
883
884 llvm::Instruction *sourceTerminator = sourceBlock->getTerminator();
885
886 // Terminators (namely YieldOp) may be forwarding values to the region that
887 // need to be available in the continuation block. Collect the types of these
888 // operands in preparation of creating PHI nodes. This is skipped for loop
889 // wrapper operations, for which we know in advance they have no terminators.
890 SmallVector<llvm::Type *> continuationBlockPHITypes;
891 unsigned numYields = 0;
892
893 if (!isLoopWrapper) {
894 bool operandsProcessed = false;
895 for (Block &bb : region.getBlocks()) {
896 if (omp::YieldOp yield = dyn_cast<omp::YieldOp>(bb.getTerminator())) {
897 if (!operandsProcessed) {
898 for (unsigned i = 0, e = yield->getNumOperands(); i < e; ++i) {
899 continuationBlockPHITypes.push_back(
900 moduleTranslation.convertType(yield->getOperand(i).getType()));
901 }
902 operandsProcessed = true;
903 } else {
904 assert(continuationBlockPHITypes.size() == yield->getNumOperands() &&
905 "mismatching number of values yielded from the region");
906 for (unsigned i = 0, e = yield->getNumOperands(); i < e; ++i) {
907 llvm::Type *operandType =
908 moduleTranslation.convertType(yield->getOperand(i).getType());
909 (void)operandType;
910 assert(continuationBlockPHITypes[i] == operandType &&
911 "values of mismatching types yielded from the region");
912 }
913 }
914 numYields++;
915 }
916 }
917 }
918
919 // Insert PHI nodes in the continuation block for any values forwarded by the
920 // terminators in this region.
921 if (!continuationBlockPHITypes.empty())
922 assert(
923 continuationBlockPHIs &&
924 "expected continuation block PHIs if converted regions yield values");
925 if (continuationBlockPHIs) {
926 llvm::IRBuilderBase::InsertPointGuard guard(builder);
927 continuationBlockPHIs->reserve(continuationBlockPHITypes.size());
928 builder.SetInsertPoint(continuationBlock->begin());
929 for (llvm::Type *ty : continuationBlockPHITypes)
930 continuationBlockPHIs->push_back(builder.CreatePHI(ty, numYields));
931 }
932
933 // Convert blocks one by one in topological order to ensure
934 // defs are converted before uses.
936 for (Block *bb : blocks) {
937 llvm::BasicBlock *llvmBB = moduleTranslation.lookupBlock(bb);
938 // Retarget the branch of the entry block to the entry block of the
939 // converted region (regions are single-entry).
940 if (bb->isEntryBlock()) {
941 assert(sourceTerminator->getNumSuccessors() == 1 &&
942 "provided entry block has multiple successors");
943 assert(sourceTerminator->getSuccessor(0) == continuationBlock &&
944 "ContinuationBlock is not the successor of the entry block");
945 sourceTerminator->setSuccessor(0, llvmBB);
946 }
947
948 llvm::IRBuilderBase::InsertPointGuard guard(builder);
949 if (failed(
950 moduleTranslation.convertBlock(*bb, bb->isEntryBlock(), builder)))
951 return llvm::make_error<PreviouslyReportedError>();
952
953 // Create a direct branch here for loop wrappers to prevent their lack of a
954 // terminator from causing a crash below.
955 if (isLoopWrapper) {
956 builder.CreateBr(continuationBlock);
957 continue;
958 }
959
960 // Special handling for `omp.yield` and `omp.terminator` (we may have more
961 // than one): they return the control to the parent OpenMP dialect operation
962 // so replace them with the branch to the continuation block. We handle this
963 // here to avoid relying inter-function communication through the
964 // ModuleTranslation class to set up the correct insertion point. This is
965 // also consistent with MLIR's idiom of handling special region terminators
966 // in the same code that handles the region-owning operation.
967 Operation *terminator = bb->getTerminator();
968 if (isa<omp::TerminatorOp, omp::YieldOp>(terminator)) {
969 builder.CreateBr(continuationBlock);
970
971 for (unsigned i = 0, e = terminator->getNumOperands(); i < e; ++i)
972 (*continuationBlockPHIs)[i]->addIncoming(
973 moduleTranslation.lookupValue(terminator->getOperand(i)), llvmBB);
974 }
975 }
976 // After all blocks have been traversed and values mapped, connect the PHI
977 // nodes to the results of preceding blocks.
978 LLVM::detail::connectPHINodes(region, moduleTranslation);
979
980 // Remove the blocks and values defined in this region from the mapping since
981 // they are not visible outside of this region. This allows the same region to
982 // be converted several times, that is cloned, without clashes, and slightly
983 // speeds up the lookups.
984 moduleTranslation.forgetMapping(region);
985
986 return continuationBlock;
987}
988
989/// Convert ProcBindKind from MLIR-generated enum to LLVM enum.
990static llvm::omp::ProcBindKind getProcBindKind(omp::ClauseProcBindKind kind) {
991 switch (kind) {
992 case omp::ClauseProcBindKind::Close:
993 return llvm::omp::ProcBindKind::OMP_PROC_BIND_close;
994 case omp::ClauseProcBindKind::Master:
995 return llvm::omp::ProcBindKind::OMP_PROC_BIND_master;
996 case omp::ClauseProcBindKind::Primary:
997 return llvm::omp::ProcBindKind::OMP_PROC_BIND_primary;
998 case omp::ClauseProcBindKind::Spread:
999 return llvm::omp::ProcBindKind::OMP_PROC_BIND_spread;
1000 }
1001 llvm_unreachable("Unknown ClauseProcBindKind kind");
1002}
1003
1004/// Convert 'dispatch' operation into LLVM IR.
1005static LogicalResult
1006convertOmpDispatch(Operation &opInst, llvm::IRBuilderBase &builder,
1007 LLVM::ModuleTranslation &moduleTranslation) {
1008 auto dispatchOp = cast<omp::DispatchOp>(opInst);
1009
1010 if (failed(checkImplementationStatus(opInst)))
1011 return failure();
1012
1013 auto &region = dispatchOp.getRegion();
1014 auto result = convertOmpOpRegions(region, "omp.dispatch.region", builder,
1015 moduleTranslation);
1016 if (!result)
1017 return handleError(result.takeError(), opInst);
1018 builder.SetInsertPoint(*result);
1019 return success();
1020}
1021
1022/// Converts an OpenMP 'masked' operation into LLVM IR using OpenMPIRBuilder.
1023static LogicalResult
1024convertOmpMasked(Operation &opInst, llvm::IRBuilderBase &builder,
1025 LLVM::ModuleTranslation &moduleTranslation) {
1026 auto maskedOp = cast<omp::MaskedOp>(opInst);
1027 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
1028
1029 if (failed(checkImplementationStatus(opInst)))
1030 return failure();
1031
1032 auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
1033 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) {
1034 // MaskedOp has only one region associated with it.
1035 auto &region = maskedOp.getRegion();
1036 builder.restoreIP(codeGenIP);
1037 return convertOmpOpRegions(region, "omp.masked.region", builder,
1038 moduleTranslation)
1039 .takeError();
1040 };
1041
1042 // TODO: Perform finalization actions for variables. This has to be
1043 // called for variables which have destructors/finalizers.
1044 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
1045
1046 llvm::Value *filterVal = nullptr;
1047 if (auto filterVar = maskedOp.getFilteredThreadId()) {
1048 filterVal = moduleTranslation.lookupValue(filterVar);
1049 } else {
1050 llvm::LLVMContext &llvmContext = builder.getContext();
1051 filterVal =
1052 llvm::ConstantInt::get(llvm::Type::getInt32Ty(llvmContext), /*V=*/0);
1053 }
1054 assert(filterVal != nullptr);
1055 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
1056 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
1057 getOpenMPBuilder(moduleTranslation)
1058 ->createMasked(ompLoc, bodyGenCB, finiCB, filterVal);
1059
1060 if (failed(handleError(afterIP, opInst)))
1061 return failure();
1062
1063 builder.restoreIP(*afterIP);
1064 return success();
1065}
1066
1067/// Converts an OpenMP 'master' operation into LLVM IR using OpenMPIRBuilder.
1068static LogicalResult
1069convertOmpMaster(Operation &opInst, llvm::IRBuilderBase &builder,
1070 LLVM::ModuleTranslation &moduleTranslation) {
1071 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
1072 auto masterOp = cast<omp::MasterOp>(opInst);
1073
1074 if (failed(checkImplementationStatus(opInst)))
1075 return failure();
1076
1077 auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
1078 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) {
1079 // MasterOp has only one region associated with it.
1080 auto &region = masterOp.getRegion();
1081 builder.restoreIP(codeGenIP);
1082 return convertOmpOpRegions(region, "omp.master.region", builder,
1083 moduleTranslation)
1084 .takeError();
1085 };
1086
1087 // TODO: Perform finalization actions for variables. This has to be
1088 // called for variables which have destructors/finalizers.
1089 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
1090
1091 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
1092 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
1093 getOpenMPBuilder(moduleTranslation)
1094 ->createMaster(ompLoc, bodyGenCB, finiCB);
1095
1096 if (failed(handleError(afterIP, opInst)))
1097 return failure();
1098
1099 builder.restoreIP(*afterIP);
1100 return success();
1101}
1102
1103/// Converts an OpenMP 'critical' operation into LLVM IR using OpenMPIRBuilder.
1104static LogicalResult
1105convertOmpCritical(Operation &opInst, llvm::IRBuilderBase &builder,
1106 LLVM::ModuleTranslation &moduleTranslation) {
1107 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
1108 auto criticalOp = cast<omp::CriticalOp>(opInst);
1109
1110 if (failed(checkImplementationStatus(opInst)))
1111 return failure();
1112
1113 auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
1114 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) {
1115 // CriticalOp has only one region associated with it.
1116 auto &region = cast<omp::CriticalOp>(opInst).getRegion();
1117 builder.restoreIP(codeGenIP);
1118 return convertOmpOpRegions(region, "omp.critical.region", builder,
1119 moduleTranslation)
1120 .takeError();
1121 };
1122
1123 // TODO: Perform finalization actions for variables. This has to be
1124 // called for variables which have destructors/finalizers.
1125 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
1126
1127 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
1128 llvm::LLVMContext &llvmContext = moduleTranslation.getLLVMContext();
1129 llvm::Constant *hint = nullptr;
1130
1131 // If it has a name, it probably has a hint too.
1132 if (criticalOp.getNameAttr()) {
1133 // The verifiers in OpenMP Dialect guarentee that all the pointers are
1134 // non-null
1135 auto symbolRef = cast<SymbolRefAttr>(criticalOp.getNameAttr());
1136 auto criticalDeclareOp =
1138 symbolRef);
1139 hint =
1140 llvm::ConstantInt::get(llvm::Type::getInt32Ty(llvmContext),
1141 static_cast<int>(criticalDeclareOp.getHint()));
1142 }
1143 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
1144 getOpenMPBuilder(moduleTranslation)
1145 ->createCritical(ompLoc, bodyGenCB, finiCB,
1146 criticalOp.getName().value_or(""), hint);
1147
1148 if (failed(handleError(afterIP, opInst)))
1149 return failure();
1150
1151 builder.restoreIP(*afterIP);
1152 return success();
1153}
1154
1155namespace {
1156/// A util to collect info needed to convert delayed privatizers from MLIR to
1157/// LLVM.
1158struct PrivateVarsInfo {
1159 struct AllocatorPrivateInfo {
1160 llvm::Value *allocatedPtr;
1161 llvm::Value *allocator;
1162 };
1163
1164 template <typename OP>
1165 PrivateVarsInfo(OP op)
1166 : blockArgs(llvm::to_vector(
1167 cast<omp::BlockArgOpenMPOpInterface>(*op).getPrivateBlockArgs())) {
1168 mlirVars.reserve(blockArgs.size());
1169 llvmVars.reserve(blockArgs.size());
1170 collectPrivatizationDecls<OP>(op);
1171
1172 for (mlir::Value privateVar : op.getPrivateVars())
1173 mlirVars.push_back(privateVar);
1174 }
1175
1176 /// Adds the linear variables of `op`, which are privatized by their
1177 /// `linear` privatizers, after the private variables.
1178 template <typename OP>
1179 void addLinearVars(OP op) {
1180 numPrivateVars = blockArgs.size();
1181 llvm::append_range(
1182 blockArgs,
1183 cast<omp::BlockArgOpenMPOpInterface>(*op).getLinearBlockArgs());
1184 llvm::append_range(mlirVars, op.getLinearVars());
1185 if (std::optional<ArrayAttr> attr = op.getLinearSyms())
1186 for (auto symbolRef : attr->getAsRange<SymbolRefAttr>())
1187 privatizers.push_back(findPrivatizer(op, symbolRef));
1188 }
1189
1190 /// Number of private variables before the linear ones.
1191 size_t numPrivateVars = 0;
1192 SmallVector<BlockArgument> blockArgs;
1193 SmallVector<mlir::Value> mlirVars;
1194 SmallVector<llvm::Value *> llvmVars;
1195 SmallVector<omp::PrivateClauseOp> privatizers;
1196 llvm::DenseMap<Value, llvm::Value *> convertedAllocators;
1197 SmallVector<AllocatorPrivateInfo> allocatorPrivates;
1198
1199private:
1200 /// Populates `privatizations` with privatization declarations used for the
1201 /// given op.
1202 template <class OP>
1203 void collectPrivatizationDecls(OP op) {
1204 std::optional<ArrayAttr> attr = op.getPrivateSyms();
1205 if (!attr)
1206 return;
1207
1208 privatizers.reserve(privatizers.size() + attr->size());
1209 for (auto symbolRef : attr->getAsRange<SymbolRefAttr>()) {
1210 privatizers.push_back(findPrivatizer(op, symbolRef));
1211 }
1212 }
1213};
1214} // namespace
1215
1216/// Populates `reductions` with reduction declarations used in the given op.
1217template <typename T>
1218static void
1221 std::optional<ArrayAttr> attr = op.getReductionSyms();
1222 if (!attr)
1223 return;
1224
1225 reductions.reserve(reductions.size() + op.getNumReductionVars());
1226 for (auto symbolRef : attr->getAsRange<SymbolRefAttr>()) {
1227 reductions.push_back(
1229 op, symbolRef));
1230 }
1231}
1232
1233/// Look up and validate the declare_reduction ops referenced by a
1234/// reduction-like clause on the omp.taskloop.context translation path. Only
1235/// the non-byref, single-init-arg, no-cleanup form is supported in this
1236/// initial cut; richer shapes are rejected here with a diagnostic. \p syms
1237/// is the clause's symbol list (e.g. `getReductionSyms()` or
1238/// `getInReductionSyms()`), \p opName is the textual op name used in
1239/// diagnostics, and \p clauseName distinguishes "reduction" from
1240/// "in_reduction" in those diagnostics.
1242 Operation *contextOp, std::optional<ArrayAttr> syms, StringRef opName,
1243 StringRef clauseName, SmallVectorImpl<omp::DeclareReductionOp> &out) {
1244 if (!syms)
1245 return success();
1246 out.reserve(out.size() + syms->size());
1247 for (auto sym : syms->getAsRange<SymbolRefAttr>()) {
1249 contextOp, sym);
1250 if (!decl)
1251 return contextOp->emitError()
1252 << "failed to resolve " << clauseName
1253 << " declare_reduction symbol " << sym.getRootReference() << " in "
1254 << opName;
1255 if (decl.getInitializerRegion().front().getNumArguments() != 1)
1256 return contextOp->emitError()
1257 << "not yet implemented: " << clauseName
1258 << " with two-argument initializer in " << opName;
1259 if (!decl.getCleanupRegion().empty())
1260 return contextOp->emitError() << "not yet implemented: " << clauseName
1261 << " with cleanup region in " << opName;
1262 if (decl.getReductionRegion().empty())
1263 return contextOp->emitError()
1264 << clauseName << " declare_reduction is missing a combiner region";
1265 out.push_back(decl);
1266 }
1267 return success();
1268}
1269
1270/// Translates the blocks contained in the given region and appends them to at
1271/// the current insertion point of `builder`. The operations of the entry block
1272/// are appended to the current insertion block. If set, `continuationBlockArgs`
1273/// is populated with translated values that correspond to the values
1274/// omp.yield'ed from the region.
1275static LogicalResult inlineConvertOmpRegions(
1276 Region &region, StringRef blockName, llvm::IRBuilderBase &builder,
1277 LLVM::ModuleTranslation &moduleTranslation,
1278 SmallVectorImpl<llvm::Value *> *continuationBlockArgs = nullptr) {
1279 if (region.empty())
1280 return success();
1281
1282 // Special case for single-block regions that don't create additional blocks:
1283 // insert operations without creating additional blocks.
1284 if (region.hasOneBlock()) {
1285 llvm::BasicBlock *insertBlock = builder.GetInsertBlock();
1286 llvm::Instruction *potentialTerminator =
1287 insertBlock->empty() ? nullptr : &insertBlock->back();
1288
1289 if (potentialTerminator && potentialTerminator->isTerminator()) {
1290 assert(builder.GetInsertPoint() == potentialTerminator->getIterator() &&
1291 "Insertion point should be the terminator");
1292 potentialTerminator->removeFromParent();
1293 // Reset insertion point to the end of the block.
1294 builder.SetInsertPoint(insertBlock);
1295 }
1296 moduleTranslation.mapBlock(&region.front(), insertBlock);
1297
1298 // convertBlock leaves the location of the region's last op current. Restore
1299 // the caller's, as convertOmpOpRegions does for every block it converts.
1300 llvm::DebugLoc loc = builder.getCurrentDebugLocation();
1301 if (failed(moduleTranslation.convertBlock(
1302 region.front(), /*ignoreArguments=*/true, builder)))
1303 return failure();
1304 builder.SetCurrentDebugLocation(loc);
1305
1306 // The continuation arguments are simply the translated terminator operands.
1307 if (continuationBlockArgs)
1308 llvm::append_range(
1309 *continuationBlockArgs,
1310 moduleTranslation.lookupValues(region.front().back().getOperands()));
1311
1312 // Drop the mapping that is no longer necessary so that the same region can
1313 // be processed multiple times.
1314 moduleTranslation.forgetMapping(region);
1315
1316 if (potentialTerminator && potentialTerminator->isTerminator()) {
1317 llvm::BasicBlock *block = builder.GetInsertBlock();
1318 if (block->empty()) {
1319 // this can happen for really simple reduction init regions e.g.
1320 // %0 = llvm.mlir.constant(0 : i32) : i32
1321 // omp.yield(%0 : i32)
1322 // because the llvm.mlir.constant (MLIR op) isn't converted into any
1323 // llvm op
1324 potentialTerminator->insertInto(block, block->begin());
1325 } else {
1326 potentialTerminator->insertAfter(&block->back());
1327 }
1328 }
1329
1330 return success();
1331 }
1332
1334 llvm::Expected<llvm::BasicBlock *> continuationBlock =
1335 convertOmpOpRegions(region, blockName, builder, moduleTranslation, &phis);
1336
1337 if (failed(handleError(continuationBlock, *region.getParentOp())))
1338 return failure();
1339
1340 if (continuationBlockArgs)
1341 llvm::append_range(*continuationBlockArgs, phis);
1342 builder.SetInsertPoint((*continuationBlock)->getFirstInsertionPt());
1343 return success();
1344}
1345
1346namespace {
1347/// Owning equivalents of OpenMPIRBuilder::(Atomic)ReductionGen that are used to
1348/// store lambdas with capture.
1349using OwningReductionGen =
1350 std::function<llvm::OpenMPIRBuilder::InsertPointOrErrorTy(
1351 llvm::OpenMPIRBuilder::InsertPointTy, llvm::Value *, llvm::Value *,
1352 llvm::Value *&)>;
1353using OwningAtomicReductionGen =
1354 std::function<llvm::OpenMPIRBuilder::InsertPointOrErrorTy(
1355 llvm::OpenMPIRBuilder::InsertPointTy, llvm::Type *, llvm::Value *,
1356 llvm::Value *)>;
1357using OwningDataPtrPtrReductionGen =
1358 std::function<llvm::OpenMPIRBuilder::InsertPointOrErrorTy(
1359 llvm::OpenMPIRBuilder::InsertPointTy, llvm::Value *, llvm::Value *&)>;
1360} // namespace
1361
1362/// Create an OpenMPIRBuilder-compatible reduction generator for the given
1363/// reduction declaration. The generator uses `builder` but ignores its
1364/// insertion point.
1365static OwningReductionGen
1366makeReductionGen(omp::DeclareReductionOp decl, llvm::IRBuilderBase &builder,
1367 LLVM::ModuleTranslation &moduleTranslation) {
1368 // The lambda is mutable because we need access to non-const methods of decl
1369 // (which aren't actually mutating it), and we must capture decl by-value to
1370 // avoid the dangling reference after the parent function returns.
1371 OwningReductionGen gen =
1372 [&, decl](llvm::OpenMPIRBuilder::InsertPointTy insertPoint,
1373 llvm::Value *lhs, llvm::Value *rhs,
1374 llvm::Value *&result) mutable
1375 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
1376 moduleTranslation.mapValue(decl.getReductionLhsArg(), lhs);
1377 moduleTranslation.mapValue(decl.getReductionRhsArg(), rhs);
1378 builder.restoreIP(insertPoint);
1380 if (failed(inlineConvertOmpRegions(decl.getReductionRegion(),
1381 "omp.reduction.nonatomic.body", builder,
1382 moduleTranslation, &phis)))
1383 return llvm::createStringError(
1384 "failed to inline `combiner` region of `omp.declare_reduction`");
1385 result = llvm::getSingleElement(phis);
1386 return builder.saveIP();
1387 };
1388 return gen;
1389}
1390
1391/// Create an OpenMPIRBuilder-compatible atomic reduction generator for the
1392/// given reduction declaration. The generator uses `builder` but ignores its
1393/// insertion point. Returns null if there is no atomic region available in the
1394/// reduction declaration.
1395static OwningAtomicReductionGen
1396makeAtomicReductionGen(omp::DeclareReductionOp decl,
1397 llvm::IRBuilderBase &builder,
1398 LLVM::ModuleTranslation &moduleTranslation) {
1399 if (decl.getAtomicReductionRegion().empty())
1400 return OwningAtomicReductionGen();
1401
1402 // The lambda is mutable because we need access to non-const methods of decl
1403 // (which aren't actually mutating it), and we must capture decl by-value to
1404 // avoid the dangling reference after the parent function returns.
1405 OwningAtomicReductionGen atomicGen =
1406 [&, decl](llvm::OpenMPIRBuilder::InsertPointTy insertPoint, llvm::Type *,
1407 llvm::Value *lhs, llvm::Value *rhs) mutable
1408 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
1409 moduleTranslation.mapValue(decl.getAtomicReductionLhsArg(), lhs);
1410 moduleTranslation.mapValue(decl.getAtomicReductionRhsArg(), rhs);
1411 builder.restoreIP(insertPoint);
1413 if (failed(inlineConvertOmpRegions(decl.getAtomicReductionRegion(),
1414 "omp.reduction.atomic.body", builder,
1415 moduleTranslation, &phis)))
1416 return llvm::createStringError(
1417 "failed to inline `atomic` region of `omp.declare_reduction`");
1418 assert(phis.empty());
1419 return builder.saveIP();
1420 };
1421 return atomicGen;
1422}
1423
1424/// Create an OpenMPIRBuilder-compatible `data_ptr_ptr` reduction generator for
1425/// the given reduction declaration. The generator uses `builder` but ignores
1426/// its insertion point. Returns null if there is no `data_ptr_ptr` region
1427/// available in the reduction declaration.
1428static OwningDataPtrPtrReductionGen
1429makeRefDataPtrGen(omp::DeclareReductionOp decl, llvm::IRBuilderBase &builder,
1430 LLVM::ModuleTranslation &moduleTranslation, bool isByRef) {
1431 if (!isByRef || decl.getDataPtrPtrRegion().empty())
1432 return OwningDataPtrPtrReductionGen();
1433
1434 OwningDataPtrPtrReductionGen refDataPtrGen =
1435 [&, decl](llvm::OpenMPIRBuilder::InsertPointTy insertPoint,
1436 llvm::Value *byRefVal, llvm::Value *&result) mutable
1437 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
1438 moduleTranslation.mapValue(decl.getDataPtrPtrRegionArg(), byRefVal);
1439 builder.restoreIP(insertPoint);
1441 if (failed(inlineConvertOmpRegions(decl.getDataPtrPtrRegion(),
1442 "omp.data_ptr_ptr.body", builder,
1443 moduleTranslation, &phis)))
1444 return llvm::createStringError(
1445 "failed to inline `data_ptr_ptr` region of `omp.declare_reduction`");
1446 result = llvm::getSingleElement(phis);
1447 return builder.saveIP();
1448 };
1449
1450 return refDataPtrGen;
1451}
1452
1453/// Converts an OpenMP 'ordered' operation into LLVM IR using OpenMPIRBuilder.
1454static LogicalResult
1455convertOmpOrdered(Operation &opInst, llvm::IRBuilderBase &builder,
1456 LLVM::ModuleTranslation &moduleTranslation) {
1457 auto orderedOp = cast<omp::OrderedOp>(opInst);
1458
1459 if (failed(checkImplementationStatus(opInst)))
1460 return failure();
1461
1462 omp::ClauseDepend dependType = *orderedOp.getDoacrossDependType();
1463 bool isDependSource = dependType == omp::ClauseDepend::dependsource;
1464 unsigned numLoops = *orderedOp.getDoacrossNumLoops();
1465 SmallVector<llvm::Value *> vecValues =
1466 moduleTranslation.lookupValues(orderedOp.getDoacrossDependVars());
1467
1468 size_t indexVecValues = 0;
1469 while (indexVecValues < vecValues.size()) {
1470 SmallVector<llvm::Value *> storeValues;
1471 storeValues.reserve(numLoops);
1472 for (unsigned i = 0; i < numLoops; i++) {
1473 storeValues.push_back(vecValues[indexVecValues]);
1474 indexVecValues++;
1475 }
1476 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
1477 findAllocInsertPoints(builder, moduleTranslation);
1478 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
1479 builder.restoreIP(getOpenMPBuilder(moduleTranslation)
1480 ->createOrderedDepend(ompLoc, allocaIP, numLoops,
1481 storeValues, ".cnt.addr",
1482 isDependSource));
1483 }
1484 return success();
1485}
1486
1487/// Converts an OpenMP 'ordered_region' operation into LLVM IR using
1488/// OpenMPIRBuilder.
1489static LogicalResult
1490convertOmpOrderedRegion(Operation &opInst, llvm::IRBuilderBase &builder,
1491 LLVM::ModuleTranslation &moduleTranslation) {
1492 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
1493 auto orderedRegionOp = cast<omp::OrderedRegionOp>(opInst);
1494
1495 if (failed(checkImplementationStatus(opInst)))
1496 return failure();
1497
1498 auto bodyGenCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
1499 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) {
1500 // OrderedOp has only one region associated with it.
1501 auto &region = cast<omp::OrderedRegionOp>(opInst).getRegion();
1502 builder.restoreIP(codeGenIP);
1503 return convertOmpOpRegions(region, "omp.ordered.region", builder,
1504 moduleTranslation)
1505 .takeError();
1506 };
1507
1508 // TODO: Perform finalization actions for variables. This has to be
1509 // called for variables which have destructors/finalizers.
1510 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
1511
1512 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
1513 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
1514 getOpenMPBuilder(moduleTranslation)
1515 ->createOrderedThreadsSimd(ompLoc, bodyGenCB, finiCB,
1516 !orderedRegionOp.getParLevelSimd());
1517
1518 if (failed(handleError(afterIP, opInst)))
1519 return failure();
1520
1521 builder.restoreIP(*afterIP);
1522 return success();
1523}
1524
1525namespace {
1526/// Contains the arguments for an LLVM store operation
1527struct DeferredStore {
1528 DeferredStore(llvm::Value *value, llvm::Value *address)
1529 : value(value), address(address) {}
1530
1531 llvm::Value *value;
1532 llvm::Value *address;
1533};
1534} // namespace
1535
1536/// Allocate space for privatized reduction variables.
1537/// `deferredStores` contains information to create store operations which needs
1538/// to be inserted after all allocas
1539template <typename T>
1540static LogicalResult
1542 llvm::IRBuilderBase &builder,
1543 LLVM::ModuleTranslation &moduleTranslation,
1544 const llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
1546 SmallVectorImpl<llvm::Value *> &privateReductionVariables,
1547 DenseMap<Value, llvm::Value *> &reductionVariableMap,
1548 SmallVectorImpl<DeferredStore> &deferredStores,
1549 llvm::ArrayRef<bool> isByRefs) {
1550 llvm::IRBuilderBase::InsertPointGuard guard(builder);
1551 builder.SetInsertPoint(allocaIP.getNodeParent()->getTerminator());
1552
1553 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
1554 bool useDeviceSharedMem = omp::opInSharedDeviceContext(*op);
1555
1556 // delay creating stores until after all allocas
1557 deferredStores.reserve(op.getNumReductionVars());
1558
1559 for (std::size_t i = 0; i < op.getNumReductionVars(); ++i) {
1560 Region &allocRegion = reductionDecls[i].getAllocRegion();
1561 if (isByRefs[i]) {
1562 if (allocRegion.empty())
1563 continue;
1564
1566 if (failed(inlineConvertOmpRegions(allocRegion, "omp.reduction.alloc",
1567 builder, moduleTranslation, &phis)))
1568 return op.emitError(
1569 "failed to inline `alloc` region of `omp.declare_reduction`");
1570
1571 assert(phis.size() == 1 && "expected one allocation to be yielded");
1572 builder.SetInsertPoint(allocaIP.getNodeParent()->getTerminator());
1573
1574 // Allocate reduction variable (which is a pointer to the real reduction
1575 // variable allocated in the inlined region)
1576 llvm::Type *ptrTy = builder.getPtrTy();
1577 llvm::Type *varTy =
1578 moduleTranslation.convertType(reductionDecls[i].getType());
1579 llvm::Value *var;
1580 if (useDeviceSharedMem) {
1581 var = ompBuilder->createOMPAllocShared(builder, varTy);
1582 } else {
1583 var = builder.CreateAlloca(varTy);
1584 var = builder.CreatePointerBitCastOrAddrSpaceCast(var, ptrTy);
1585 }
1586
1587 llvm::Value *castPhi =
1588 builder.CreatePointerBitCastOrAddrSpaceCast(phis[0], ptrTy);
1589
1590 deferredStores.emplace_back(castPhi, var);
1591
1592 privateReductionVariables[i] = var;
1593 moduleTranslation.mapValue(reductionArgs[i], castPhi);
1594 reductionVariableMap.try_emplace(op.getReductionVars()[i], castPhi);
1595 } else {
1596 assert(allocRegion.empty() &&
1597 "allocaction is implicit for by-val reduction");
1598
1599 llvm::Type *ptrTy = builder.getPtrTy();
1600 llvm::Type *varTy =
1601 moduleTranslation.convertType(reductionDecls[i].getType());
1602 llvm::Value *var;
1603 if (useDeviceSharedMem) {
1604 var = ompBuilder->createOMPAllocShared(builder, varTy);
1605 } else {
1606 var = builder.CreateAlloca(varTy);
1607 var = builder.CreatePointerBitCastOrAddrSpaceCast(var, ptrTy);
1608 }
1609
1610 moduleTranslation.mapValue(reductionArgs[i], var);
1611 privateReductionVariables[i] = var;
1612 reductionVariableMap.try_emplace(op.getReductionVars()[i], var);
1613 }
1614 }
1615
1616 return success();
1617}
1618
1619/// Map input arguments to reduction initialization region
1620template <typename T>
1621static void
1623 llvm::IRBuilderBase &builder,
1625 DenseMap<Value, llvm::Value *> &reductionVariableMap,
1626 unsigned i) {
1627 // map input argument to the initialization region
1628 mlir::omp::DeclareReductionOp &reduction = reductionDecls[i];
1629 Region &initializerRegion = reduction.getInitializerRegion();
1630 Block &entry = initializerRegion.front();
1631
1632 mlir::Value mlirSource = loop.getReductionVars()[i];
1633 llvm::Value *llvmSource = moduleTranslation.lookupValue(mlirSource);
1634 llvm::Value *origVal = llvmSource;
1635 // If a non-pointer value is expected, load the value from the source pointer.
1636 if (!isa<LLVM::LLVMPointerType>(
1637 reduction.getInitializerMoldArg().getType()) &&
1638 isa<LLVM::LLVMPointerType>(mlirSource.getType())) {
1639 origVal =
1640 builder.CreateLoad(moduleTranslation.convertType(
1641 reduction.getInitializerMoldArg().getType()),
1642 llvmSource, "omp_orig");
1643 }
1644 moduleTranslation.mapValue(reduction.getInitializerMoldArg(), origVal);
1645
1646 if (entry.getNumArguments() > 1) {
1647 llvm::Value *allocation =
1648 reductionVariableMap.lookup(loop.getReductionVars()[i]);
1649 moduleTranslation.mapValue(reduction.getInitializerAllocArg(), allocation);
1650 }
1651}
1652
1653static void
1654setInsertPointForPossiblyEmptyBlock(llvm::IRBuilderBase &builder,
1655 llvm::BasicBlock *block = nullptr) {
1656 if (block == nullptr)
1657 block = builder.GetInsertBlock();
1658
1659 if (!block->hasTerminator())
1660 builder.SetInsertPoint(block);
1661 else
1662 builder.SetInsertPoint(block->getTerminator());
1663}
1664
1665/// Inline reductions' `init` regions. This functions assumes that the
1666/// `builder`'s insertion point is where the user wants the `init` regions to be
1667/// inlined; i.e. it does not try to find a proper insertion location for the
1668/// `init` regions. It also leaves the `builder's insertions point in a state
1669/// where the user can continue the code-gen directly afterwards.
1670template <typename OP>
1671static LogicalResult
1672initReductionVars(OP op, ArrayRef<BlockArgument> reductionArgs,
1673 llvm::IRBuilderBase &builder,
1674 LLVM::ModuleTranslation &moduleTranslation,
1675 llvm::BasicBlock *latestAllocaBlock,
1677 SmallVectorImpl<llvm::Value *> &privateReductionVariables,
1678 DenseMap<Value, llvm::Value *> &reductionVariableMap,
1679 llvm::ArrayRef<bool> isByRef,
1680 SmallVectorImpl<DeferredStore> &deferredStores) {
1681 if (op.getNumReductionVars() == 0)
1682 return success();
1683
1684 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
1685 bool useDeviceSharedMem = omp::opInSharedDeviceContext(*op);
1686
1687 llvm::BasicBlock *initBlock = splitBB(builder, true, "omp.reduction.init");
1688 SmallVector<llvm::Value *> byRefVars(op.getNumReductionVars());
1689 {
1690 llvm::IRBuilderBase::InsertPointGuard guard(builder);
1691 auto allocaIP = latestAllocaBlock->getTerminator()->getIterator();
1692 builder.restoreIP(allocaIP);
1693
1694 for (unsigned i = 0; i < op.getNumReductionVars(); ++i) {
1695 if (isByRef[i]) {
1696 if (!reductionDecls[i].getAllocRegion().empty())
1697 continue;
1698
1699 // TODO: remove after all users of by-ref are updated to use the alloc
1700 // region: Allocate reduction variable (which is a pointer to the real
1701 // reduciton variable allocated in the inlined region)
1702 llvm::Type *varTy =
1703 moduleTranslation.convertType(reductionDecls[i].getType());
1704 if (useDeviceSharedMem)
1705 byRefVars[i] = ompBuilder->createOMPAllocShared(builder, varTy);
1706 else
1707 byRefVars[i] = builder.CreateAlloca(varTy);
1708 }
1709 }
1710 }
1711
1712 setInsertPointForPossiblyEmptyBlock(builder, initBlock);
1713
1714 // store result of the alloc region to the allocated pointer to the real
1715 // reduction variable
1716 for (auto [data, addr] : deferredStores)
1717 builder.CreateStore(data, addr);
1718
1719 // Before the loop, store the initial values of reductions into reduction
1720 // variables. Although this could be done after allocas, we don't want to mess
1721 // up with the alloca insertion point.
1722 for (unsigned i = 0; i < op.getNumReductionVars(); ++i) {
1724
1725 // map block argument to initializer region
1726 mapInitializationArgs(op, moduleTranslation, builder, reductionDecls,
1727 reductionVariableMap, i);
1728
1729 // TODO In some cases (specially on the GPU), the init regions may
1730 // contains stack alloctaions. If the region is inlined in a loop, this is
1731 // problematic. Instead of just inlining the region, handle allocations by
1732 // hoisting fixed length allocations to the function entry and using
1733 // stacksave and restore for variable length ones.
1734 if (failed(inlineConvertOmpRegions(reductionDecls[i].getInitializerRegion(),
1735 "omp.reduction.neutral", builder,
1736 moduleTranslation, &phis)))
1737 return failure();
1738
1739 assert(phis.size() == 1 && "expected one value to be yielded from the "
1740 "reduction neutral element declaration region");
1741
1743
1744 if (isByRef[i]) {
1745 if (!reductionDecls[i].getAllocRegion().empty())
1746 // done in allocReductionVars
1747 continue;
1748
1749 // TODO: this path can be removed once all users of by-ref are updated to
1750 // use an alloc region
1751
1752 // Store the result of the inlined region to the allocated reduction var
1753 // ptr
1754 builder.CreateStore(phis[0], byRefVars[i]);
1755
1756 privateReductionVariables[i] = byRefVars[i];
1757 moduleTranslation.mapValue(reductionArgs[i], phis[0]);
1758 reductionVariableMap.try_emplace(op.getReductionVars()[i], phis[0]);
1759 } else {
1760 // for by-ref case the store is inside of the reduction region
1761 builder.CreateStore(phis[0], privateReductionVariables[i]);
1762 // the rest was handled in allocByValReductionVars
1763 }
1764
1765 // forget the mapping for the initializer region because we might need a
1766 // different mapping if this reduction declaration is re-used for a
1767 // different variable
1768 moduleTranslation.forgetMapping(reductionDecls[i].getInitializerRegion());
1769 }
1770
1771 return success();
1772}
1773
1774/// Collect reduction info
1775template <typename T>
1776static void collectReductionInfo(
1777 T loop, llvm::IRBuilderBase &builder,
1778 LLVM::ModuleTranslation &moduleTranslation,
1781 SmallVectorImpl<OwningAtomicReductionGen> &owningAtomicReductionGens,
1783 const ArrayRef<llvm::Value *> privateReductionVariables,
1785 ArrayRef<bool> isByRef) {
1786 unsigned numReductions = loop.getNumReductionVars();
1787
1788 for (unsigned i = 0; i < numReductions; ++i) {
1789 owningReductionGens.push_back(
1790 makeReductionGen(reductionDecls[i], builder, moduleTranslation));
1791 owningAtomicReductionGens.push_back(
1792 makeAtomicReductionGen(reductionDecls[i], builder, moduleTranslation));
1794 reductionDecls[i], builder, moduleTranslation, isByRef[i]));
1795 }
1796
1797 // Collect the reduction information.
1798 reductionInfos.reserve(numReductions);
1799 for (unsigned i = 0; i < numReductions; ++i) {
1800 llvm::OpenMPIRBuilder::ReductionGenAtomicCBTy atomicGen = nullptr;
1801 if (owningAtomicReductionGens[i])
1802 atomicGen = owningAtomicReductionGens[i];
1803 llvm::Value *variable =
1804 moduleTranslation.lookupValue(loop.getReductionVars()[i]);
1805 mlir::Type allocatedType;
1806 reductionDecls[i].getAllocRegion().walk([&](mlir::Operation *op) {
1807 if (auto alloca = mlir::dyn_cast<LLVM::AllocaOp>(op)) {
1808 allocatedType = alloca.getElemType();
1810 }
1811
1813 });
1814
1815 reductionInfos.push_back(
1816 {moduleTranslation.convertType(reductionDecls[i].getType()), variable,
1817 privateReductionVariables[i],
1818 /*EvaluationKind=*/llvm::OpenMPIRBuilder::EvalKind::Scalar,
1820 /*ReductionGenClang=*/nullptr, atomicGen,
1822 allocatedType ? moduleTranslation.convertType(allocatedType) : nullptr,
1823 reductionDecls[i].getByrefElementType()
1824 ? moduleTranslation.convertType(
1825 *reductionDecls[i].getByrefElementType())
1826 : nullptr});
1827 }
1828}
1829
1830/// handling of DeclareReductionOp's cleanup region
1831static LogicalResult
1833 llvm::ArrayRef<llvm::Value *> privateVariables,
1834 LLVM::ModuleTranslation &moduleTranslation,
1835 llvm::IRBuilderBase &builder, StringRef regionName,
1836 bool shouldLoadCleanupRegionArg = true) {
1837 for (auto [i, cleanupRegion] : llvm::enumerate(cleanupRegions)) {
1838 if (cleanupRegion->empty())
1839 continue;
1840
1841 // map the argument to the cleanup region
1842 Block &entry = cleanupRegion->front();
1843
1844 llvm::Instruction *potentialTerminator =
1845 builder.GetInsertBlock()->empty() ? nullptr
1846 : &builder.GetInsertBlock()->back();
1847 if (potentialTerminator && potentialTerminator->isTerminator())
1848 builder.SetInsertPoint(potentialTerminator);
1849 llvm::Value *privateVarValue =
1850 shouldLoadCleanupRegionArg
1851 ? builder.CreateLoad(
1852 moduleTranslation.convertType(entry.getArgument(0).getType()),
1853 privateVariables[i])
1854 : privateVariables[i];
1855
1856 moduleTranslation.mapValue(entry.getArgument(0), privateVarValue);
1857
1858 if (failed(inlineConvertOmpRegions(*cleanupRegion, regionName, builder,
1859 moduleTranslation)))
1860 return failure();
1861
1862 // clear block argument mapping in case it needs to be re-created with a
1863 // different source for another use of the same reduction decl
1864 moduleTranslation.forgetMapping(*cleanupRegion);
1865 }
1866 return success();
1867}
1868
1869// TODO: not used by ParallelOp
1870template <class OP>
1871static LogicalResult createReductionsAndCleanup(
1872 OP op, llvm::IRBuilderBase &builder,
1873 LLVM::ModuleTranslation &moduleTranslation,
1874 llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
1876 ArrayRef<llvm::Value *> privateReductionVariables, ArrayRef<bool> isByRef,
1877 bool isNowait = false, bool isTeamsReduction = false) {
1878 // Process the reductions if required.
1879 if (op.getNumReductionVars() == 0)
1880 return success();
1881
1883 SmallVector<OwningAtomicReductionGen> owningAtomicReductionGens;
1884 SmallVector<OwningDataPtrPtrReductionGen> owningReductionGenRefDataPtrGens;
1886
1887 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
1888
1889 // Create the reduction generators. We need to own them here because
1890 // ReductionInfo only accepts references to the generators.
1891 collectReductionInfo(op, builder, moduleTranslation, reductionDecls,
1892 owningReductionGens, owningAtomicReductionGens,
1893 owningReductionGenRefDataPtrGens,
1894 privateReductionVariables, reductionInfos, isByRef);
1895
1896 // The call to createReductions below expects the block to have a
1897 // terminator. Create an unreachable instruction to serve as terminator
1898 // and remove it later.
1899 llvm::UnreachableInst *tempTerminator = builder.CreateUnreachable();
1900 builder.SetInsertPoint(tempTerminator);
1901 llvm::DebugLoc reductionLoc = builder.getCurrentDebugLocation();
1902 llvm::OpenMPIRBuilder::InsertPointOrErrorTy contInsertPoint =
1903 ompBuilder->createReductions(builder, allocaIP, reductionInfos, isByRef,
1904 isNowait, isTeamsReduction);
1905
1906 if (failed(handleError(contInsertPoint, *op)))
1907 return failure();
1908
1909 if (!contInsertPoint->isValid())
1910 return op->emitOpError() << "failed to convert reductions";
1911
1912 llvm::OpenMPIRBuilder::InsertPointTy afterIP = *contInsertPoint;
1913 if (!isTeamsReduction && !isNowait) {
1914 llvm::OpenMPIRBuilder::InsertPointOrErrorTy barrierIP =
1915 ompBuilder->createBarrier({*contInsertPoint, reductionLoc},
1916 llvm::omp::OMPD_for);
1917
1918 if (failed(handleError(barrierIP, *op)))
1919 return failure();
1920 afterIP = *barrierIP;
1921 }
1922
1923 tempTerminator->eraseFromParent();
1924 builder.restoreIP(afterIP);
1925
1926 // after the construct, deallocate private reduction variables
1927 SmallVector<Region *> reductionRegions;
1928 llvm::transform(reductionDecls, std::back_inserter(reductionRegions),
1929 [](omp::DeclareReductionOp reductionDecl) {
1930 return &reductionDecl.getCleanupRegion();
1931 });
1932 LogicalResult result = inlineOmpRegionCleanup(
1933 reductionRegions, privateReductionVariables, moduleTranslation, builder,
1934 "omp.reduction.cleanup");
1935
1936 bool useDeviceSharedMem = omp::opInSharedDeviceContext(*op);
1937 if (useDeviceSharedMem) {
1938 for (auto [var, reductionDecl] :
1939 llvm::zip_equal(privateReductionVariables, reductionDecls))
1940 ompBuilder->createOMPFreeShared(
1941 builder, var, moduleTranslation.convertType(reductionDecl.getType()));
1942 }
1943
1944 return result;
1945}
1946
1947static ArrayRef<bool> getIsByRef(std::optional<ArrayRef<bool>> attr) {
1948 if (!attr)
1949 return {};
1950 return *attr;
1951}
1952
1953// TODO: not used by omp.parallel
1954template <typename OP>
1956 OP op, ArrayRef<BlockArgument> reductionArgs, llvm::IRBuilderBase &builder,
1957 LLVM::ModuleTranslation &moduleTranslation,
1958 llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
1960 SmallVectorImpl<llvm::Value *> &privateReductionVariables,
1961 DenseMap<Value, llvm::Value *> &reductionVariableMap,
1962 llvm::ArrayRef<bool> isByRef) {
1963 if (op.getNumReductionVars() == 0)
1964 return success();
1965
1966 SmallVector<DeferredStore> deferredStores;
1967
1968 if (failed(allocReductionVars(op, reductionArgs, builder, moduleTranslation,
1969 allocaIP, reductionDecls,
1970 privateReductionVariables, reductionVariableMap,
1971 deferredStores, isByRef)))
1972 return failure();
1973
1974 return initReductionVars(op, reductionArgs, builder, moduleTranslation,
1975 allocaIP.getNodeParent(), reductionDecls,
1976 privateReductionVariables, reductionVariableMap,
1977 isByRef, deferredStores);
1978}
1979
1980/// Return the llvm::Value * corresponding to the `privateVar` that
1981/// is being privatized. It isn't always as simple as looking up
1982/// moduleTranslation with privateVar. For instance, in case of
1983/// an allocatable, the descriptor for the allocatable is privatized.
1984/// This descriptor is mapped using an MapInfoOp. So, this function
1985/// will return a pointer to the llvm::Value corresponding to the
1986/// block argument for the mapped descriptor.
1987static llvm::Value *
1988findAssociatedValue(Value privateVar, llvm::IRBuilderBase &builder,
1989 LLVM::ModuleTranslation &moduleTranslation,
1990 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
1991 if (mappedPrivateVars == nullptr || !mappedPrivateVars->contains(privateVar))
1992 return moduleTranslation.lookupValue(privateVar);
1993
1994 Value blockArg = (*mappedPrivateVars)[privateVar];
1995 Type privVarType = privateVar.getType();
1996 Type blockArgType = blockArg.getType();
1997 assert(isa<LLVM::LLVMPointerType>(blockArgType) &&
1998 "A block argument corresponding to a mapped var should have "
1999 "!llvm.ptr type");
2000
2001 if (privVarType == blockArgType)
2002 return moduleTranslation.lookupValue(blockArg);
2003
2004 // This typically happens when the privatized type is lowered from
2005 // boxchar<KIND> and gets lowered to !llvm.struct<(ptr, i64)>. That is the
2006 // struct/pair is passed by value. But, mapped values are passed only as
2007 // pointers, so before we privatize, we must load the pointer.
2008 if (!isa<LLVM::LLVMPointerType>(privVarType))
2009 return builder.CreateLoad(moduleTranslation.convertType(privVarType),
2010 moduleTranslation.lookupValue(blockArg));
2011
2012 return moduleTranslation.lookupValue(privateVar);
2013}
2014
2015// Privatizer region arguments may be by-value even when the available LLVM
2016// value is storage for that value, e.g. lowered Fortran boxchar descriptors in
2017// task context structs. Materialize the value expected by the region argument
2018// while preserving the existing pointer mapping for pointer arguments.
2019static llvm::Value *
2020materializeRegionArgValue(llvm::IRBuilderBase &builder,
2021 LLVM::ModuleTranslation &moduleTranslation,
2022 BlockArgument regionArg, llvm::Value *value) {
2023 if (!regionArg)
2024 return value;
2025
2026 llvm::Type *regionArgType =
2027 moduleTranslation.convertType(regionArg.getType());
2028 if (regionArgType->isPointerTy() || !value->getType()->isPointerTy())
2029 return value;
2030
2031 return builder.CreateLoad(regionArgType, value);
2032}
2033
2034/// Returns the address of the data in `var`, via the `data_address` region.
2036getPrivateDataAddress(llvm::IRBuilderBase &builder,
2037 LLVM::ModuleTranslation &moduleTranslation,
2038 omp::PrivateClauseOp privatizer, llvm::Value *var) {
2039 Region &region = privatizer.getDataAddressRegion();
2040 if (region.empty())
2041 return var;
2042
2043 BlockArgument arg = privatizer.getDataAddressArg();
2044 moduleTranslation.mapValue(
2045 arg, materializeRegionArgValue(builder, moduleTranslation, arg, var));
2047 if (failed(inlineConvertOmpRegions(region, "omp.private.data_address",
2048 builder, moduleTranslation, &address)))
2049 return llvm::make_error<PreviouslyReportedError>();
2051 return address.front();
2052}
2053
2054/// Initialize a single (first)private variable. You probably want to use
2055/// allocateAndInitPrivateVars instead of this.
2056/// This returns the private variable which has been initialized. This
2057/// variable should be mapped before constructing the body of the Op.
2059initPrivateVar(llvm::IRBuilderBase &builder,
2060 LLVM::ModuleTranslation &moduleTranslation,
2061 omp::PrivateClauseOp &privDecl, llvm::Value *nonPrivateVar,
2062 BlockArgument &blockArg, llvm::Value *llvmPrivateVar,
2063 llvm::BasicBlock *privInitBlock,
2064 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
2065 Region &initRegion = privDecl.getInitRegion();
2066 if (initRegion.empty())
2067 return llvmPrivateVar;
2068
2069 assert(nonPrivateVar);
2070 moduleTranslation.mapValue(privDecl.getInitMoldArg(), nonPrivateVar);
2071 moduleTranslation.mapValue(privDecl.getInitPrivateArg(), llvmPrivateVar);
2072
2073 // in-place convert the private initialization region
2075 if (failed(inlineConvertOmpRegions(initRegion, "omp.private.init", builder,
2076 moduleTranslation, &phis)))
2077 return llvm::createStringError(
2078 "failed to inline `init` region of `omp.private`");
2079
2080 assert(phis.size() == 1 && "expected one allocation to be yielded");
2081
2082 // clear init region block argument mapping in case it needs to be
2083 // re-created with a different source for another use of the same
2084 // reduction decl
2085 moduleTranslation.forgetMapping(initRegion);
2086
2087 // Prefer the value yielded from the init region to the allocated private
2088 // variable in case the region is operating on arguments by-value (e.g.
2089 // Fortran character boxes).
2090 return phis[0];
2091}
2092
2093/// Version of initPrivateVar which looks up the nonPrivateVar from mlirPrivVar.
2095 llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation,
2096 omp::PrivateClauseOp &privDecl, Value mlirPrivVar, BlockArgument &blockArg,
2097 llvm::Value *llvmPrivateVar, llvm::BasicBlock *privInitBlock,
2098 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
2099 return initPrivateVar(
2100 builder, moduleTranslation, privDecl,
2101 findAssociatedValue(mlirPrivVar, builder, moduleTranslation,
2102 mappedPrivateVars),
2103 blockArg, llvmPrivateVar, privInitBlock, mappedPrivateVars);
2104}
2105
2106static llvm::Error
2107initPrivateVars(llvm::IRBuilderBase &builder,
2108 LLVM::ModuleTranslation &moduleTranslation,
2109 PrivateVarsInfo &privateVarsInfo,
2110 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
2111 if (privateVarsInfo.blockArgs.empty())
2112 return llvm::Error::success();
2113
2114 llvm::BasicBlock *privInitBlock = splitBB(builder, true, "omp.private.init");
2115 setInsertPointForPossiblyEmptyBlock(builder, privInitBlock);
2116
2117 for (auto [idx, zip] : llvm::enumerate(llvm::zip_equal(
2118 privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
2119 privateVarsInfo.blockArgs, privateVarsInfo.llvmVars))) {
2120 auto [privDecl, mlirPrivVar, blockArg, llvmPrivateVar] = zip;
2122 builder, moduleTranslation, privDecl, mlirPrivVar, blockArg,
2123 llvmPrivateVar, privInitBlock, mappedPrivateVars);
2124
2125 if (!privVarOrErr)
2126 return privVarOrErr.takeError();
2127
2128 llvmPrivateVar = privVarOrErr.get();
2129 moduleTranslation.mapValue(blockArg, llvmPrivateVar);
2130
2132 }
2133
2134 return llvm::Error::success();
2135}
2136
2137static LogicalResult
2139 llvm::IRBuilderBase &builder,
2140 LLVM::ModuleTranslation &moduleTranslation,
2141 PrivateVarsInfo &privateVarsInfo) {
2142 for (Value allocatorVar : allocatorVars) {
2143 if (privateVarsInfo.convertedAllocators.contains(allocatorVar))
2144 continue;
2145
2146 llvm::Value *allocator = moduleTranslation.lookupValue(allocatorVar);
2147 if (!allocator)
2148 return op.emitError("failed to translate OpenMP allocator operand");
2149 if (allocator->getType()->isIntegerTy())
2150 allocator = builder.CreateIntToPtr(allocator, builder.getPtrTy());
2151 else if (allocator->getType()->isPointerTy())
2152 allocator = builder.CreatePointerBitCastOrAddrSpaceCast(
2153 allocator, builder.getPtrTy());
2154 else
2155 return op.emitError(
2156 "OpenMP allocator operand must have integer or pointer type");
2157
2158 privateVarsInfo.convertedAllocators.try_emplace(allocatorVar, allocator);
2159 }
2160 return success();
2161}
2162
2163/// Allocate and initialize delayed private variables. Returns the basic block
2164/// which comes after all of these allocations. llvm::Value * for each of these
2165/// private variables are populated in llvmPrivateVars.
2166template <typename T>
2168 T op, llvm::IRBuilderBase &builder,
2169 LLVM::ModuleTranslation &moduleTranslation,
2170 PrivateVarsInfo &privateVarsInfo,
2171 llvm::OpenMPIRBuilder::InsertPointTy &allocaIP,
2172 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr,
2173 std::optional<llvm::OpenMPIRBuilder::InsertPointTy> allocatorIP =
2174 std::nullopt) {
2175 // Allocate private vars
2176 // Save blocks before splits since allocaIP/allocatorIP iterators may follow
2177 // spliced instructions to the new block.
2178 llvm::BasicBlock *allocaBB = allocaIP.getNodeParent();
2179 llvm::Instruction *allocaTerminator = allocaBB->getTerminator();
2180 splitBB(allocaTerminator->getIterator(), true,
2181 allocaTerminator->getDebugLoc(), "omp.region.after_alloca");
2182 // Update the allocaTerminator since the alloca block was split above.
2183 allocaTerminator = allocaBB->getTerminator();
2184 // The new terminator is an uncondition branch created by the splitBB above.
2185 assert(allocaTerminator->getNumSuccessors() == 1 &&
2186 "This is an unconditional branch created by splitBB");
2187 allocaIP = allocaTerminator->getIterator();
2188
2189 llvm::Instruction *allocatorTerminator = nullptr;
2190 llvm::BasicBlock *afterAllocatorAllocations = nullptr;
2191 if (allocatorIP) {
2192 llvm::BasicBlock *allocatorBB = allocatorIP->getNodeParent();
2193 allocatorTerminator = allocatorBB->getTerminator();
2194 afterAllocatorAllocations = splitBB(
2195 allocatorTerminator->getIterator(), true,
2196 allocatorTerminator->getDebugLoc(), "omp.region.after_allocate");
2197 allocatorTerminator = allocatorBB->getTerminator();
2198 assert(allocatorTerminator->getNumSuccessors() == 1 &&
2199 "This is an unconditional branch created by splitBB");
2200 }
2201
2202 std::optional<llvm::IRBuilderBase::InsertPointGuard> guard;
2203 if (!allocatorIP)
2204 guard.emplace(builder);
2205 builder.SetInsertPoint(allocaTerminator);
2206
2207 llvm::DataLayout dataLayout = builder.getDataLayout();
2208 llvm::BasicBlock *afterAllocas = allocaTerminator->getSuccessor(0);
2209
2210 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
2211 bool mightUseDeviceSharedMem = omp::opInSharedDeviceContext(*op);
2212 unsigned int allocaAS =
2213 moduleTranslation.getLLVMModule()->getDataLayout().getAllocaAddrSpace();
2214 unsigned int defaultAS = moduleTranslation.getLLVMModule()
2215 ->getDataLayout()
2216 .getProgramAddressSpace();
2217
2218 SmallVector<int64_t> allocateItemForPrivate(privateVarsInfo.blockArgs.size(),
2219 -1);
2220 ValueRange allocatorVars;
2221 DenseI64ArrayAttr allocateAlignments;
2222 if constexpr (std::is_same_v<T, omp::ParallelOp> ||
2223 std::is_same_v<T, omp::ScopeOp>) {
2224 allocatorVars = op.getAllocatorVars();
2225 allocateAlignments = op.getAllocateAlignmentsAttr();
2226 if (auto privateIndices = op.getAllocatePrivateIndicesAttr())
2227 for (auto [allocateIndex, privateIndex] :
2228 llvm::enumerate(privateIndices.asArrayRef()))
2229 allocateItemForPrivate[privateIndex] = allocateIndex;
2230 }
2231
2232 for (auto [privateIndex, tuple] : llvm::enumerate(llvm::zip_equal(
2233 privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
2234 privateVarsInfo.blockArgs))) {
2235 auto [privDecl, mlirPrivVar, blockArg] = tuple;
2236 llvm::Type *llvmAllocType =
2237 moduleTranslation.convertType(privDecl.getType());
2238 llvm::Value *llvmPrivateVar = nullptr;
2239 int64_t allocateIndex = allocateItemForPrivate[privateIndex];
2240 builder.SetInsertPoint(allocateIndex >= 0 && allocatorTerminator
2241 ? allocatorTerminator
2242 : allocaTerminator);
2243 if (allocateIndex >= 0) {
2244 if (ompBuilder->Config.isTargetDevice() ||
2245 op->template getParentOfType<omp::TargetOp>())
2246 return llvm::createStringError(
2247 "allocate clause in an OpenMP device context is not supported");
2248 if (!llvmAllocType->isSized())
2249 return llvm::createStringError(
2250 "allocate clause private type must have a fixed size");
2251 llvm::TypeSize size = dataLayout.getTypeAllocSize(llvmAllocType);
2252 if (size.isScalable())
2253 return llvm::createStringError(
2254 "allocate clause private type must have a fixed size");
2255 llvm::IntegerType *sizeTy =
2256 moduleTranslation.getLLVMModule()->getDataLayout().getIntPtrType(
2257 moduleTranslation.getLLVMModule()->getContext());
2258 if (!llvm::isUIntN(sizeTy->getBitWidth(), size.getFixedValue()))
2259 return llvm::createStringError(
2260 "OpenMP allocation size cannot be represented by the target size "
2261 "type");
2262 llvm::Value *sizeValue =
2263 llvm::ConstantInt::get(sizeTy, size.getFixedValue());
2264
2265 Value allocatorVar = allocatorVars[allocateIndex];
2266 auto allocator = privateVarsInfo.convertedAllocators.find(allocatorVar);
2267 if (allocator == privateVarsInfo.convertedAllocators.end())
2268 return llvm::createStringError(
2269 "failed to find converted OpenMP allocator operand");
2270 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2271 int64_t alignment =
2272 allocateAlignments ? allocateAlignments[allocateIndex] : 0;
2273 if (alignment != 0) {
2274 // The allocation must be aligned to at least the maximum of the
2275 // requested alignment and the alignment the base language requires
2276 // for the type being allocated.
2277 uint64_t alignmentValue = std::max<uint64_t>(
2278 static_cast<uint64_t>(alignment),
2279 dataLayout.getABITypeAlign(llvmAllocType).value());
2280 if (!llvm::isUIntN(sizeTy->getBitWidth(), alignmentValue))
2281 return llvm::createStringError(
2282 "OpenMP allocation alignment cannot be represented by the "
2283 "target size type");
2284 llvmPrivateVar = ompBuilder->createOMPAlignedAlloc(
2285 ompLoc, llvm::ConstantInt::get(sizeTy, alignmentValue), sizeValue,
2286 allocator->second, "omp.private.alloc");
2287 } else {
2288 llvmPrivateVar = ompBuilder->createOMPAlloc(
2289 ompLoc, sizeValue, allocator->second, "omp.private.alloc");
2290 }
2291 if (!llvmPrivateVar)
2292 return llvm::createStringError(
2293 "failed to create OpenMP private allocation");
2294 privateVarsInfo.allocatorPrivates.push_back(
2295 {llvmPrivateVar, allocator->second});
2296 } else if (mightUseDeviceSharedMem &&
2298 llvmPrivateVar = ompBuilder->createOMPAllocShared(builder, llvmAllocType);
2299 } else {
2300 llvmPrivateVar = builder.CreateAlloca(
2301 llvmAllocType, /*ArraySize=*/nullptr, "omp.private.alloc");
2302 if (allocaAS != defaultAS)
2303 llvmPrivateVar = builder.CreateAddrSpaceCast(
2304 llvmPrivateVar, builder.getPtrTy(defaultAS));
2305 }
2306
2307 privateVarsInfo.llvmVars.push_back(llvmPrivateVar);
2308 }
2309
2310 return afterAllocatorAllocations ? afterAllocatorAllocations : afterAllocas;
2311}
2312
2313/// A compiler-generated barrier is unsafe when only part of the current team
2314/// can reach it. This cannot always be determined statically (e.g. across a
2315/// function call), but the enclosing OpenMP constructs can rule it out.
2317 for (mlir::Operation *parent = op; parent != nullptr;
2318 parent = parent->getParentOp()) {
2319 // An inner parallel construct creates a new team, even when it is nested
2320 // inside a construct that only some threads of the outer team encounter.
2321 if (mlir::isa<omp::ParallelOp>(parent))
2322 return true;
2323
2324 if (mlir::isa<omp::SingleOp, omp::CriticalOp, omp::MaskedOp, omp::MasterOp,
2325 omp::SectionOp>(parent))
2326 return false;
2327 }
2328 return true;
2329}
2330
2331static LogicalResult
2332emitPrivatizationBarrier(mlir::Operation *op, llvm::IRBuilderBase &builder,
2333 LLVM::ModuleTranslation &moduleTranslation) {
2334 if (!opMightBeSafeForBarriers(op))
2335 return success();
2336
2337 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
2338 llvm::OpenMPIRBuilder::InsertPointOrErrorTy res =
2339 ompBuilder->createBarrier(builder, llvm::omp::OMPD_barrier);
2340 return handleError(res, *op);
2341}
2342
2343static LogicalResult
2344completePrivateVars(mlir::Operation *op, llvm::IRBuilderBase &builder,
2345 LLVM::ModuleTranslation &moduleTranslation,
2347 ArrayRef<llvm::Value *> llvmPrivateVars,
2349 bool insertBarrier,
2350 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
2351 // Apply copy region for firstprivate.
2352 bool needsFirstprivate =
2353 llvm::any_of(privateDecls, [](omp::PrivateClauseOp &privOp) {
2354 return privOp.getDataSharingType() ==
2355 omp::DataSharingClauseType::FirstPrivate;
2356 });
2357
2358 if (!needsFirstprivate)
2359 return insertBarrier
2360 ? emitPrivatizationBarrier(op, builder, moduleTranslation)
2361 : success();
2362
2363 llvm::BasicBlock *copyBlock =
2364 splitBB(builder, /*CreateBranch=*/true, "omp.private.copy");
2365 setInsertPointForPossiblyEmptyBlock(builder, copyBlock);
2366
2367 for (auto [decl, moldVar, llvmVar] :
2368 llvm::zip_equal(privateDecls, moldVars, llvmPrivateVars)) {
2369 if (decl.getDataSharingType() != omp::DataSharingClauseType::FirstPrivate)
2370 continue;
2371
2372 // copyRegion implements `lhs = rhs`
2373 Region &copyRegion = decl.getCopyRegion();
2374
2375 llvm::Value *copyMoldVar = materializeRegionArgValue(
2376 builder, moduleTranslation, decl.getCopyMoldArg(), moldVar);
2377 llvm::Value *copyPrivateVar = materializeRegionArgValue(
2378 builder, moduleTranslation, decl.getCopyPrivateArg(), llvmVar);
2379
2380 moduleTranslation.mapValue(decl.getCopyMoldArg(), copyMoldVar);
2381
2382 // map copyRegion lhs arg
2383 moduleTranslation.mapValue(decl.getCopyPrivateArg(), copyPrivateVar);
2384
2385 // in-place convert copy region
2386 if (failed(inlineConvertOmpRegions(copyRegion, "omp.private.copy", builder,
2387 moduleTranslation)))
2388 return decl.emitError("failed to inline `copy` region of `omp.private`");
2389
2391
2392 // ignore unused value yielded from copy region
2393
2394 // clear copy region block argument mapping in case it needs to be
2395 // re-created with different sources for reuse of the same reduction
2396 // decl
2397 moduleTranslation.forgetMapping(copyRegion);
2398 }
2399
2400 return insertBarrier
2401 ? emitPrivatizationBarrier(op, builder, moduleTranslation)
2402 : success();
2403}
2404
2405static LogicalResult
2406completePrivateVars(mlir::Operation *op, llvm::IRBuilderBase &builder,
2407 LLVM::ModuleTranslation &moduleTranslation,
2408 SmallVectorImpl<mlir::Value> &mlirPrivateVars,
2409 ArrayRef<llvm::Value *> llvmPrivateVars,
2411 bool insertBarrier,
2412 llvm::DenseMap<Value, Value> *mappedPrivateVars = nullptr) {
2413 llvm::SmallVector<llvm::Value *> moldVars(mlirPrivateVars.size());
2414 llvm::transform(mlirPrivateVars, moldVars.begin(), [&](mlir::Value mlirVar) {
2415 // map copyRegion rhs arg
2416 llvm::Value *moldVar = findAssociatedValue(
2417 mlirVar, builder, moduleTranslation, mappedPrivateVars);
2418 assert(moldVar);
2419 return moldVar;
2420 });
2421 return completePrivateVars(op, builder, moduleTranslation, moldVars,
2422 llvmPrivateVars, privateDecls, insertBarrier,
2423 mappedPrivateVars);
2424}
2425
2426template <typename T>
2427static LogicalResult
2428cleanupPrivateVars(T op, llvm::IRBuilderBase &builder,
2429 LLVM::ModuleTranslation &moduleTranslation, Location loc,
2430 PrivateVarsInfo &privateVarsInfo) {
2431 // private variable deallocation
2432 SmallVector<Region *> privateCleanupRegions;
2433 llvm::transform(privateVarsInfo.privatizers,
2434 std::back_inserter(privateCleanupRegions),
2435 [](omp::PrivateClauseOp privatizer) {
2436 return &privatizer.getDeallocRegion();
2437 });
2438
2439 if (failed(inlineOmpRegionCleanup(privateCleanupRegions,
2440 privateVarsInfo.llvmVars, moduleTranslation,
2441 builder, "omp.private.dealloc",
2442 /*shouldLoadCleanupRegionArg=*/false)))
2443 return mlir::emitError(loc, "failed to inline `dealloc` region of an "
2444 "`omp.private` op in");
2446
2447 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
2448 bool mightUseDeviceSharedMem = omp::opInSharedDeviceContext(*op);
2449 for (auto [privDecl, llvmPrivVar, blockArg] :
2450 llvm::zip_equal(privateVarsInfo.privatizers, privateVarsInfo.llvmVars,
2451 privateVarsInfo.blockArgs)) {
2452 if (mightUseDeviceSharedMem && omp::allocaUsesRequireSharedMem(blockArg)) {
2453 ompBuilder->createOMPFreeShared(
2454 builder, llvmPrivVar,
2455 moduleTranslation.convertType(privDecl.getType()));
2456 }
2457 }
2458
2459 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2460 for (const PrivateVarsInfo::AllocatorPrivateInfo &allocation :
2461 llvm::reverse(privateVarsInfo.allocatorPrivates))
2462 ompBuilder->createOMPFree(ompLoc, allocation.allocatedPtr,
2463 allocation.allocator);
2464
2465 return success();
2466}
2467
2468/// Returns true if the construct contains omp.cancel or omp.cancellation_point
2470 // omp.cancel and omp.cancellation_point must be "closely nested" so they will
2471 // be visible and not inside of function calls. This is enforced by the
2472 // verifier.
2473 return op
2474 ->walk([](Operation *child) {
2475 if (mlir::isa<omp::CancelOp, omp::CancellationPointOp>(child))
2476 return WalkResult::interrupt();
2477 return WalkResult::advance();
2478 })
2479 .wasInterrupted();
2480}
2481
2482// Forward declarations for the task-reduction helpers defined alongside the
2483// omp.taskgroup lowering further down in this file. These are shared by the
2484// `reduction(task, ...)` modifier lowering on the parallel/worksharing
2485// constructs and by the omp.taskgroup / omp.taskloop.context task_reduction
2486// lowering. When \p isModifier is set, `__kmpc_taskred_modifier_init` is
2487// emitted (opening a task-reduction scope) instead of `__kmpc_taskred_init`,
2488// with \p isWorksharing selecting the runtime `is_ws` argument.
2489static llvm::Value *emitTaskReductionInitCall(
2491 ArrayRef<llvm::Value *> origPtrs, StringRef helperNamePrefix,
2492 llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder::InsertPointTy allocaIP,
2493 LLVM::ModuleTranslation &moduleTranslation, bool isModifier = false,
2494 bool isWorksharing = false);
2495static void
2496emitTaskReductionModifierFini(bool isWorksharing, llvm::IRBuilderBase &builder,
2497 LLVM::ModuleTranslation &moduleTranslation);
2498
2499/// Register only the task-modified items. All items still participate in the
2500/// enclosing construct's ordinary reduction combination and cleanup.
2501template <typename OpTy>
2503 OpTy op, ArrayRef<omp::DeclareReductionOp> reductionDecls,
2504 ArrayRef<llvm::Value *> privateReductionVariables,
2505 llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder::InsertPointTy allocaIP,
2506 LLVM::ModuleTranslation &moduleTranslation, bool isWorksharing) {
2508 SmallVector<llvm::Value *> taskVariables;
2509 for (unsigned i = 0, e = op.getNumReductionVars(); i != e; ++i) {
2510 if (op.getReductionModifier(i) != omp::ReductionModifier::task)
2511 continue;
2512 taskDecls.push_back(reductionDecls[i]);
2513 taskVariables.push_back(privateReductionVariables[i]);
2514 }
2516 taskDecls, taskVariables, "__omp_taskred_mod_", builder, allocaIP,
2517 moduleTranslation, /*isModifier=*/true, isWorksharing);
2518}
2519
2520static LogicalResult
2521convertOmpSections(Operation &opInst, llvm::IRBuilderBase &builder,
2522 LLVM::ModuleTranslation &moduleTranslation) {
2523 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2524 using StorableBodyGenCallbackTy =
2525 llvm::OpenMPIRBuilder::StorableBodyGenCallbackTy;
2526
2527 auto sectionsOp = cast<omp::SectionsOp>(opInst);
2528
2529 if (failed(checkImplementationStatus(opInst)))
2530 return failure();
2531
2532 llvm::ArrayRef<bool> isByRef = getIsByRef(sectionsOp.getReductionByref());
2533 assert(isByRef.size() == sectionsOp.getNumReductionVars());
2534
2536 collectReductionDecls(sectionsOp, reductionDecls);
2537 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
2538 findAllocInsertPoints(builder, moduleTranslation);
2539
2540 SmallVector<llvm::Value *> privateReductionVariables(
2541 sectionsOp.getNumReductionVars());
2542 DenseMap<Value, llvm::Value *> reductionVariableMap;
2543
2544 MutableArrayRef<BlockArgument> reductionArgs =
2545 cast<omp::BlockArgOpenMPOpInterface>(opInst).getReductionBlockArgs();
2546
2548 sectionsOp, reductionArgs, builder, moduleTranslation, allocaIP,
2549 reductionDecls, privateReductionVariables, reductionVariableMap,
2550 isByRef)))
2551 return failure();
2552
2553 bool isTaskReductionMod =
2554 sectionsOp.hasReductionModifier(omp::ReductionModifier::task);
2555
2557
2558 for (Operation &op : *sectionsOp.getRegion().begin()) {
2559 auto sectionOp = dyn_cast<omp::SectionOp>(op);
2560 if (!sectionOp) // omp.terminator
2561 continue;
2562
2563 Region &region = sectionOp.getRegion();
2564 auto sectionCB = [&sectionsOp, &region, &builder, &moduleTranslation](
2565 InsertPointTy allocaIP, InsertPointTy codeGenIP,
2566 ArrayRef<llvm::BasicBlock *> deallocBlocks) {
2567 builder.restoreIP(codeGenIP);
2568
2569 // map the omp.section reduction block argument to the omp.sections block
2570 // arguments
2571 // TODO: this assumes that the only block arguments are reduction
2572 // variables
2573 assert(region.getNumArguments() ==
2574 sectionsOp.getRegion().getNumArguments());
2575 for (auto [sectionsArg, sectionArg] : llvm::zip_equal(
2576 sectionsOp.getRegion().getArguments(), region.getArguments())) {
2577 llvm::Value *llvmVal = moduleTranslation.lookupValue(sectionsArg);
2578 assert(llvmVal);
2579 moduleTranslation.mapValue(sectionArg, llvmVal);
2580 }
2581
2582 return convertOmpOpRegions(region, "omp.section.region", builder,
2583 moduleTranslation)
2584 .takeError();
2585 };
2586 sectionCBs.push_back(sectionCB);
2587 }
2588
2589 // No sections within omp.sections operation - skip generation. This situation
2590 // is only possible if there is only a terminator operation inside the
2591 // sections operation
2592 if (sectionCBs.empty())
2593 return success();
2594
2595 // For `reduction(task, ...)` open a task-reduction scope for the worksharing
2596 // region. Participating explicit tasks accumulate into the per-thread private
2597 // copies, which the worksharing reduction then combines across threads. This
2598 // is emitted only after the empty-sections early return above, so it stays
2599 // balanced with the matching fini emitted after the sections region.
2600 if (isTaskReductionMod &&
2602 sectionsOp, reductionDecls, privateReductionVariables, builder,
2603 allocaIP, moduleTranslation, /*isWorksharing=*/true))
2604 return sectionsOp.emitError(
2605 "failed to emit task reduction modifier initialization");
2606
2607 assert(isa<omp::SectionOp>(*sectionsOp.getRegion().op_begin()));
2608
2609 // TODO: Perform appropriate actions according to the data-sharing
2610 // attribute (shared, private, firstprivate, ...) of variables.
2611 // Currently defaults to shared.
2612 auto privCB = [&](InsertPointTy, InsertPointTy codeGenIP, llvm::Value &,
2613 llvm::Value &vPtr, llvm::Value *&replacementValue)
2614 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
2615 replacementValue = &vPtr;
2616 return codeGenIP;
2617 };
2618
2619 // TODO: Perform finalization actions for variables. This has to be
2620 // called for variables which have destructors/finalizers.
2621 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
2622
2623 allocaIP = findAllocInsertPoints(builder, moduleTranslation);
2624 bool isCancellable = constructIsCancellable(sectionsOp);
2625 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2626 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2627 getOpenMPBuilder(moduleTranslation)
2628 ->createSections(ompLoc, allocaIP, sectionCBs, privCB, finiCB,
2629 isCancellable, sectionsOp.getNowait());
2630
2631 if (failed(handleError(afterIP, opInst)))
2632 return failure();
2633
2634 builder.restoreIP(*afterIP);
2635
2636 // Close the task-reduction scope before combining the worksharing copies.
2637 if (isTaskReductionMod)
2638 emitTaskReductionModifierFini(/*isWorksharing=*/true, builder,
2639 moduleTranslation);
2640
2641 // Process the reductions if required.
2643 sectionsOp, builder, moduleTranslation, allocaIP, reductionDecls,
2644 privateReductionVariables, isByRef, sectionsOp.getNowait());
2645}
2646
2647/// Converts an OpenMP scope construct into LLVM IR.
2648static LogicalResult
2649convertOmpScope(omp::ScopeOp &scopeOp, llvm::IRBuilderBase &builder,
2650 LLVM::ModuleTranslation &moduleTranslation) {
2651 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2652 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
2653
2654 if (failed(checkImplementationStatus(*scopeOp)))
2655 return failure();
2656
2657 llvm::ArrayRef<bool> isByRef = getIsByRef(scopeOp.getReductionByref());
2658 assert(isByRef.size() == scopeOp.getNumReductionVars());
2659
2660 PrivateVarsInfo privateVarsInfo(scopeOp);
2661 if (failed(convertAllocatorVars(*scopeOp, scopeOp.getAllocatorVars(), builder,
2662 moduleTranslation, privateVarsInfo)))
2663 return failure();
2664
2666 collectReductionDecls(scopeOp, reductionDecls);
2667 InsertPointTy privateAllocaIP =
2668 findAllocInsertPoints(builder, moduleTranslation);
2669
2670 SmallVector<llvm::Value *> privateReductionVariables(
2671 scopeOp.getNumReductionVars());
2672 DenseMap<Value, llvm::Value *> reductionVariableMap;
2673
2674 MutableArrayRef<BlockArgument> reductionArgs =
2675 cast<omp::BlockArgOpenMPOpInterface>(*scopeOp).getReductionBlockArgs();
2676
2677 if (scopeOp.getAllocateVars().empty()) {
2679 scopeOp, builder, moduleTranslation, privateVarsInfo, privateAllocaIP);
2680 if (failed(handleError(afterAllocas, *scopeOp)))
2681 return failure();
2682 }
2683
2685 scopeOp, reductionArgs, builder, moduleTranslation, privateAllocaIP,
2686 reductionDecls, privateReductionVariables, reductionVariableMap,
2687 isByRef)))
2688 return failure();
2689
2690 auto bodyCB =
2691 [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
2692 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
2693 if (!scopeOp.getAllocateVars().empty()) {
2694 // Runtime storage must be allocated on each dynamic Scope entry. Ordinary
2695 // private allocas still use the enclosing alloca insertion point.
2697 scopeOp, builder, moduleTranslation, privateVarsInfo, privateAllocaIP,
2698 /*mappedPrivateVars=*/nullptr, codeGenIP);
2699 if (handleError(afterAllocas, *scopeOp).failed())
2700 return llvm::make_error<PreviouslyReportedError>();
2701 builder.SetInsertPoint(afterAllocas.get()->getTerminator());
2702 } else {
2703 builder.restoreIP(codeGenIP);
2704 }
2705
2706 if (handleError(
2707 initPrivateVars(builder, moduleTranslation, privateVarsInfo),
2708 *scopeOp)
2709 .failed())
2710 return llvm::make_error<PreviouslyReportedError>();
2711
2712 if (failed(completePrivateVars(
2713 scopeOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
2714 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
2715 scopeOp.getPrivateNeedsBarrier())))
2716 return llvm::make_error<PreviouslyReportedError>();
2717
2718 return convertOmpOpRegions(scopeOp.getRegion(), "omp.scope.region", builder,
2719 moduleTranslation)
2720 .takeError();
2721 };
2722
2723 auto finiCB = [&](InsertPointTy codeGenIP) -> llvm::Error {
2724 InsertPointTy oldIP = builder.saveIP();
2725 builder.restoreIP(codeGenIP);
2726 if (failed(cleanupPrivateVars(scopeOp, builder, moduleTranslation,
2727 scopeOp.getLoc(), privateVarsInfo)))
2728 return llvm::make_error<PreviouslyReportedError>();
2729 builder.restoreIP(oldIP);
2730 return llvm::Error::success();
2731 };
2732
2733 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2734 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2735 ompBuilder->createScope(ompLoc, bodyCB, finiCB, scopeOp.getNowait());
2736
2737 if (failed(handleError(afterIP, *scopeOp)))
2738 return failure();
2739
2740 builder.restoreIP(*afterIP);
2741
2742 // Process the reductions if required.
2744 scopeOp, builder, moduleTranslation, privateAllocaIP, reductionDecls,
2745 privateReductionVariables, isByRef, scopeOp.getNowait(),
2746 /*isTeamsReduction=*/false);
2747}
2748
2749/// Converts an OpenMP single construct into LLVM IR using OpenMPIRBuilder.
2750static LogicalResult
2751convertOmpSingle(omp::SingleOp &singleOp, llvm::IRBuilderBase &builder,
2752 LLVM::ModuleTranslation &moduleTranslation) {
2753 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2754 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2755
2756 if (failed(checkImplementationStatus(*singleOp)))
2757 return failure();
2758
2759 auto bodyCB = [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
2760 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) {
2761 builder.restoreIP(codegenIP);
2762 return convertOmpOpRegions(singleOp.getRegion(), "omp.single.region",
2763 builder, moduleTranslation)
2764 .takeError();
2765 };
2766 auto finiCB = [&](InsertPointTy codeGenIP) { return llvm::Error::success(); };
2767
2768 // Handle copyprivate
2769 Operation::operand_range cpVars = singleOp.getCopyprivateVars();
2770 std::optional<ArrayAttr> cpFuncs = singleOp.getCopyprivateSyms();
2773 for (size_t i = 0, e = cpVars.size(); i < e; ++i) {
2774 llvmCPVars.push_back(moduleTranslation.lookupValue(cpVars[i]));
2776 singleOp, cast<SymbolRefAttr>((*cpFuncs)[i]));
2777 llvmCPFuncs.push_back(
2778 moduleTranslation.lookupFunction(llvmFuncOp.getName()));
2779 }
2780
2781 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2782 getOpenMPBuilder(moduleTranslation)
2783 ->createSingle(ompLoc, bodyCB, finiCB, singleOp.getNowait(),
2784 llvmCPVars, llvmCPFuncs);
2785
2786 if (failed(handleError(afterIP, *singleOp)))
2787 return failure();
2788
2789 builder.restoreIP(*afterIP);
2790 return success();
2791}
2792
2793static omp::DistributeOp
2795 // Early return if we found more than one distribute op or if we can't find
2796 // any distribute op in the teams region.
2797 omp::DistributeOp distOp;
2798 WalkResult walk = teamsOp.getRegion().walk([&](omp::DistributeOp op) {
2799 if (distOp)
2800 return WalkResult::interrupt();
2801 distOp = op;
2802 return WalkResult::skip();
2803 });
2804 if (walk.wasInterrupted() || !distOp)
2805 return {};
2806
2807 auto iface =
2808 llvm::cast<mlir::omp::BlockArgOpenMPOpInterface>(teamsOp.getOperation());
2809 // Check that all uses of the reduction block arg has the same distribute op
2810 // parent.
2812 for (auto ra : iface.getReductionBlockArgs())
2813 for (auto &use : ra.getUses()) {
2814 auto *useOp = use.getOwner();
2815 // Ignore debug uses.
2816 if (mlir::isa<LLVM::DbgDeclareOp, LLVM::DbgValueOp>(useOp)) {
2817 debugUses.push_back(useOp);
2818 continue;
2819 }
2820 if (!distOp->isProperAncestor(useOp))
2821 return {};
2822 }
2823
2824 // If we are going to use distribute reduction then remove any debug uses of
2825 // the reduction parameters in teamsOp. Otherwise they will be left without
2826 // any mapped value in moduleTranslation and will eventually error out.
2827 for (auto *use : debugUses)
2828 use->erase();
2829 return distOp;
2830}
2831
2832// Convert an OpenMP Teams construct to LLVM IR using OpenMPIRBuilder
2833static LogicalResult
2834convertOmpTeams(omp::TeamsOp op, llvm::IRBuilderBase &builder,
2835 LLVM::ModuleTranslation &moduleTranslation) {
2836 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
2837 if (failed(checkImplementationStatus(*op)))
2838 return failure();
2839
2840 DenseMap<Value, llvm::Value *> reductionVariableMap;
2841 unsigned numReductionVars = op.getNumReductionVars();
2843 SmallVector<llvm::Value *> privateReductionVariables(numReductionVars);
2844 llvm::ArrayRef<bool> isByRef;
2845 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
2846 findAllocInsertPoints(builder, moduleTranslation);
2847
2848 // Only do teams reduction if there is no distribute op that captures the
2849 // reduction instead.
2850 bool doTeamsReduction = !getDistributeCapturingTeamsReduction(op);
2851 if (doTeamsReduction) {
2852 isByRef = getIsByRef(op.getReductionByref());
2853
2854 assert(isByRef.size() == op.getNumReductionVars());
2855
2856 MutableArrayRef<BlockArgument> reductionArgs =
2857 llvm::cast<omp::BlockArgOpenMPOpInterface>(*op).getReductionBlockArgs();
2858
2859 collectReductionDecls(op, reductionDecls);
2860
2862 op, reductionArgs, builder, moduleTranslation, allocaIP,
2863 reductionDecls, privateReductionVariables, reductionVariableMap,
2864 isByRef)))
2865 return failure();
2866 }
2867
2868 auto bodyCB = [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
2869 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) {
2871 moduleTranslation, allocaIP.getNodeParent(), deallocBlocks);
2872 builder.restoreIP(codegenIP);
2873 return convertOmpOpRegions(op.getRegion(), "omp.teams.region", builder,
2874 moduleTranslation)
2875 .takeError();
2876 };
2877
2878 llvm::Value *numTeamsLower = nullptr;
2879 if (Value numTeamsLowerVar = op.getNumTeamsLower())
2880 numTeamsLower = moduleTranslation.lookupValue(numTeamsLowerVar);
2881
2882 llvm::Value *numTeamsUpper = nullptr;
2883 if (!op.getNumTeamsUpperVars().empty())
2884 numTeamsUpper = moduleTranslation.lookupValue(op.getNumTeams(0));
2885
2886 llvm::Value *threadLimit = nullptr;
2887 if (!op.getThreadLimitVars().empty())
2888 threadLimit = moduleTranslation.lookupValue(op.getThreadLimit(0));
2889
2890 llvm::Value *ifExpr = nullptr;
2891 if (Value ifVar = op.getIfExpr())
2892 ifExpr = moduleTranslation.lookupValue(ifVar);
2893
2894 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
2895 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
2896 getOpenMPBuilder(moduleTranslation)
2897 ->createTeams(ompLoc, bodyCB, numTeamsLower, numTeamsUpper,
2898 threadLimit, ifExpr);
2899
2900 if (failed(handleError(afterIP, *op)))
2901 return failure();
2902
2903 builder.restoreIP(*afterIP);
2904 if (doTeamsReduction) {
2905 // Process the reductions if required.
2907 op, builder, moduleTranslation, allocaIP, reductionDecls,
2908 privateReductionVariables, isByRef,
2909 /*isNoWait*/ false, /*isTeamsReduction*/ true);
2910 }
2911 return success();
2912}
2913
2914static llvm::omp::RTLDependenceKindTy
2915convertDependKind(mlir::omp::ClauseTaskDepend kind) {
2916 switch (kind) {
2917 case mlir::omp::ClauseTaskDepend::taskdependin:
2918 return llvm::omp::RTLDependenceKindTy::DepIn;
2919 // The OpenMP runtime requires that the codegen for 'depend' clause for
2920 // 'out' dependency kind must be the same as codegen for 'depend' clause
2921 // with 'inout' dependency.
2922 case mlir::omp::ClauseTaskDepend::taskdependout:
2923 case mlir::omp::ClauseTaskDepend::taskdependinout:
2924 return llvm::omp::RTLDependenceKindTy::DepInOut;
2925 case mlir::omp::ClauseTaskDepend::taskdependmutexinoutset:
2926 return llvm::omp::RTLDependenceKindTy::DepMutexInOutSet;
2927 case mlir::omp::ClauseTaskDepend::taskdependinoutset:
2928 return llvm::omp::RTLDependenceKindTy::DepInOutSet;
2929 }
2930 llvm_unreachable("unhandled depend kind");
2931}
2932
2934 std::optional<ArrayAttr> dependKinds, OperandRange dependVars,
2935 LLVM::ModuleTranslation &moduleTranslation,
2937 if (dependVars.empty())
2938 return;
2939 for (auto dep : llvm::zip(dependVars, dependKinds->getValue())) {
2940 auto kind =
2941 cast<mlir::omp::ClauseTaskDependAttr>(std::get<1>(dep)).getValue();
2942 llvm::omp::RTLDependenceKindTy type = convertDependKind(kind);
2943 llvm::Value *depVal = moduleTranslation.lookupValue(std::get<0>(dep));
2944 llvm::OpenMPIRBuilder::DependData dd(type, depVal->getType(), depVal);
2945 dds.emplace_back(dd);
2946 }
2947}
2948
2949/// Shared implementation of a callback which adds a termiator for the new block
2950/// created for the branch taken when an openmp construct is cancelled. The
2951/// terminator is saved in \p cancelTerminators. This callback is invoked only
2952/// if there is cancellation inside of the taskgroup body.
2953/// The terminator will need to be fixed to branch to the correct block to
2954/// cleanup the construct.
2956 SmallVectorImpl<llvm::UncondBrInst *> &cancelTerminators,
2957 llvm::IRBuilderBase &llvmBuilder, llvm::OpenMPIRBuilder &ompBuilder,
2958 mlir::Operation *op, llvm::omp::Directive cancelDirective) {
2959 auto finiCB = [&](llvm::OpenMPIRBuilder::InsertPointTy ip) -> llvm::Error {
2960 llvm::IRBuilderBase::InsertPointGuard guard(llvmBuilder);
2961
2962 // ip is currently in the block branched to if cancellation occurred.
2963 // We need to create a branch to terminate that block.
2964 llvmBuilder.restoreIP(ip);
2965
2966 // We must still clean up the construct after cancelling it, so we need to
2967 // branch to the block that finalizes the taskgroup.
2968 // That block has not been created yet so use this block as a dummy for now
2969 // and fix this after creating the operation.
2970 cancelTerminators.push_back(llvmBuilder.CreateBr(ip.getNodeParent()));
2971 return llvm::Error::success();
2972 };
2973 // We have to add the cleanup to the OpenMPIRBuilder before the body gets
2974 // created in case the body contains omp.cancel (which will then expect to be
2975 // able to find this cleanup callback).
2976 ompBuilder.pushFinalizationCB(
2977 {finiCB, cancelDirective, constructIsCancellable(op)});
2978}
2979
2980/// If we cancelled the construct, we should branch to the finalization block of
2981/// that construct. OMPIRBuilder structures the CFG such that the cleanup block
2982/// is immediately before the continuation block. Now this finalization has
2983/// been created we can fix the branch.
2984static void
2986 llvm::OpenMPIRBuilder &ompBuilder,
2987 llvm::BasicBlock *afterBB) {
2988 ompBuilder.popFinalizationCB();
2989 llvm::BasicBlock *constructFini = afterBB->getSinglePredecessor();
2990 for (llvm::UncondBrInst *cancelBranch : cancelTerminators)
2991 cancelBranch->setSuccessor(constructFini);
2992}
2993
2994namespace {
2995/// TaskContextStructManager takes care of creating and freeing a structure
2996/// containing information needed by the task body to execute.
2997class TaskContextStructManager {
2998public:
2999 TaskContextStructManager(llvm::IRBuilderBase &builder,
3000 LLVM::ModuleTranslation &moduleTranslation,
3001 MutableArrayRef<omp::PrivateClauseOp> privateDecls)
3002 : builder{builder}, moduleTranslation{moduleTranslation},
3003 privateDecls{privateDecls} {}
3004
3005 /// Creates a heap allocated struct containing space for each private
3006 /// variable. Invariant: privateVarTypes, privateDecls, and the elements of
3007 /// the structure should all have the same order (although privateDecls which
3008 /// do not read from the mold argument are skipped).
3009 void generateTaskContextStruct();
3010
3011 /// Create GEPs to access each member of the structure representing a private
3012 /// variable, adding them to llvmPrivateVars. Null values are added where
3013 /// private decls were skipped so that the ordering continues to match the
3014 /// private decls.
3015 void createGEPsToPrivateVars();
3016
3017 /// Given the address of the structure, return a GEP for each private variable
3018 /// in the structure. Null values are added where private decls were skipped
3019 /// so that the ordering continues to match the private decls.
3020 /// Must be called after generateTaskContextStruct().
3021 SmallVector<llvm::Value *>
3022 createGEPsToPrivateVars(llvm::Value *altStructPtr) const;
3023
3024 /// De-allocate the task context structure.
3025 void freeStructPtr();
3026
3027 MutableArrayRef<llvm::Value *> getLLVMPrivateVarGEPs() {
3028 return llvmPrivateVarGEPs;
3029 }
3030
3031 llvm::Value *getStructPtr() { return structPtr; }
3032
3033private:
3034 llvm::IRBuilderBase &builder;
3035 LLVM::ModuleTranslation &moduleTranslation;
3036 MutableArrayRef<omp::PrivateClauseOp> privateDecls;
3037
3038 /// The type of each member of the structure, in order.
3039 SmallVector<llvm::Type *> privateVarTypes;
3040
3041 /// LLVM values for each private variable, or null if that private variable is
3042 /// not included in the task context structure
3043 SmallVector<llvm::Value *> llvmPrivateVarGEPs;
3044
3045 /// A pointer to the structure containing context for this task.
3046 llvm::Value *structPtr = nullptr;
3047 /// The type of the structure
3048 llvm::Type *structTy = nullptr;
3049};
3050
3051/// IteratorInfo extracts and prepares loop bounds information from an
3052/// mlir::omp::IteratorOp for lowering to LLVM IR.
3053///
3054/// It computes the per-dimension trip counts and the total linearized trip
3055/// count as i64. Bounds and steps keep their declared type so that the
3056/// physical induction variables can be reconstructed in that type inside the
3057/// loop body.
3058class IteratorInfo {
3059private:
3060 llvm::SmallVector<llvm::Value *> lowerBounds;
3061 llvm::SmallVector<llvm::Value *> upperBounds;
3062 llvm::SmallVector<llvm::Value *> steps;
3063 llvm::SmallVector<llvm::Value *> trips;
3064 unsigned dims;
3065 llvm::Value *totalTrips;
3066
3067public:
3068 IteratorInfo(mlir::omp::IteratorOp itersOp,
3069 mlir::LLVM::ModuleTranslation &moduleTranslation,
3070 llvm::IRBuilderBase &builder) {
3071 dims = itersOp.getLoopLowerBounds().size();
3072 lowerBounds.resize(dims);
3073 upperBounds.resize(dims);
3074 steps.resize(dims);
3075 trips.resize(dims);
3076
3077 for (unsigned d = 0; d < dims; ++d) {
3078 llvm::Value *lb =
3079 moduleTranslation.lookupValue(itersOp.getLoopLowerBounds()[d]);
3080 llvm::Value *ub =
3081 moduleTranslation.lookupValue(itersOp.getLoopUpperBounds()[d]);
3082 llvm::Value *st =
3083 moduleTranslation.lookupValue(itersOp.getLoopSteps()[d]);
3084 assert(lb && ub && st &&
3085 "Expect lowerBounds, upperBounds, and steps in IteratorOp");
3086 assert(lb->getType() == ub->getType() && lb->getType() == st->getType() &&
3087 "Expect matching iterator range types");
3088 assert((!llvm::isa<llvm::ConstantInt>(st) ||
3089 !llvm::cast<llvm::ConstantInt>(st)->isZero()) &&
3090 "Expect non-zero step in IteratorOp");
3091
3092 lowerBounds[d] = lb;
3093 upperBounds[d] = ub;
3094 steps[d] = st;
3095
3096 // Use a direction-aware count so an empty range contributes no entries.
3097 // Widen by one bit so the span between the bounds cannot overflow.
3098 llvm::Type *countTy =
3099 builder.getIntNTy(lb->getType()->getIntegerBitWidth() + 1);
3100 llvm::Value *start = builder.CreateSExt(lb, countTy);
3101 llvm::Value *stop = builder.CreateSExt(ub, countTy);
3102 llvm::Value *step = builder.CreateSExt(st, countTy);
3103 llvm::Value *count =
3104 getOpenMPBuilder(moduleTranslation)
3105 ->calculateCanonicalLoopTripCount(
3106 builder, start, stop, step,
3107 /*IsSigned=*/true, itersOp.getLoopInclusive(), "iterator");
3108 trips[d] = builder.CreateZExtOrTrunc(count, builder.getInt64Ty());
3109 }
3110
3111 totalTrips = llvm::ConstantInt::get(builder.getInt64Ty(), 1);
3112 for (unsigned d = 0; d < dims; ++d)
3113 totalTrips = builder.CreateMul(totalTrips, trips[d]);
3114 }
3115
3116 unsigned getDims() const { return dims; }
3117 llvm::ArrayRef<llvm::Value *> getLowerBounds() const { return lowerBounds; }
3118 llvm::ArrayRef<llvm::Value *> getUpperBounds() const { return upperBounds; }
3119 llvm::ArrayRef<llvm::Value *> getSteps() const { return steps; }
3120 llvm::ArrayRef<llvm::Value *> getTrips() const { return trips; }
3121 llvm::Value *getTotalTrips() const { return totalTrips; }
3122};
3123} // namespace
3124
3125void TaskContextStructManager::generateTaskContextStruct() {
3126 if (privateDecls.empty())
3127 return;
3128 privateVarTypes.reserve(privateDecls.size());
3129
3130 for (omp::PrivateClauseOp &privOp : privateDecls) {
3131 // Skip private variables which can safely be allocated and initialised
3132 // inside of the task
3133 if (!privOp.readsFromMold())
3134 continue;
3135 Type mlirType = privOp.getType();
3136 privateVarTypes.push_back(moduleTranslation.convertType(mlirType));
3137 }
3138
3139 if (privateVarTypes.empty())
3140 return;
3141
3142 structTy = llvm::StructType::get(moduleTranslation.getLLVMContext(),
3143 privateVarTypes);
3144
3145 llvm::DataLayout dataLayout = builder.getDataLayout();
3146 llvm::Type *intPtrTy = builder.getIntPtrTy(dataLayout);
3147 llvm::Value *allocSize =
3148 builder.CreateTypeSize(intPtrTy, dataLayout.getTypeAllocSize(structTy));
3149
3150 // Heap allocate the structure
3151 structPtr = builder.CreateMalloc(intPtrTy, allocSize,
3152 /*ArraySize=*/nullptr, /*MallocF=*/nullptr,
3153 "omp.task.context_ptr");
3154}
3155
3156SmallVector<llvm::Value *> TaskContextStructManager::createGEPsToPrivateVars(
3157 llvm::Value *altStructPtr) const {
3158 SmallVector<llvm::Value *> ret;
3159
3160 // Create GEPs for each struct member
3161 ret.reserve(privateDecls.size());
3162 llvm::Value *zero = builder.getInt32(0);
3163 unsigned i = 0;
3164 for (auto privDecl : privateDecls) {
3165 if (!privDecl.readsFromMold()) {
3166 // Handle this inside of the task so we don't pass unnessecary vars in
3167 ret.push_back(nullptr);
3168 continue;
3169 }
3170 llvm::Value *iVal = builder.getInt32(i);
3171 llvm::Value *gep = builder.CreateGEP(structTy, altStructPtr, {zero, iVal});
3172 ret.push_back(gep);
3173 i += 1;
3174 }
3175 return ret;
3176}
3177
3178void TaskContextStructManager::createGEPsToPrivateVars() {
3179 if (!structPtr)
3180 assert(privateVarTypes.empty());
3181 // Still need to run createGEPsToPrivateVars to populate llvmPrivateVarGEPs
3182 // with null values for skipped private decls
3183
3184 llvmPrivateVarGEPs = createGEPsToPrivateVars(structPtr);
3185}
3186
3187void TaskContextStructManager::freeStructPtr() {
3188 if (!structPtr)
3189 return;
3190
3191 llvm::IRBuilderBase::InsertPointGuard guard{builder};
3192 // Ensure we don't put the call to free() after the terminator
3193 builder.SetInsertPoint(builder.GetInsertBlock()->getTerminator());
3194 builder.CreateFree(structPtr);
3195}
3196
3197static void storeAffinityEntry(llvm::IRBuilderBase &builder,
3198 llvm::OpenMPIRBuilder &ompBuilder,
3199 llvm::Value *affinityList, llvm::Value *index,
3200 llvm::Value *addr, llvm::Value *len) {
3201 llvm::StructType *kmpTaskAffinityInfoTy =
3202 ompBuilder.getKmpTaskAffinityInfoTy();
3203 llvm::Value *entry = builder.CreateInBoundsGEP(
3204 kmpTaskAffinityInfoTy, affinityList, index, "omp.affinity.entry");
3205
3206 addr = builder.CreatePtrToInt(addr, kmpTaskAffinityInfoTy->getElementType(0));
3207 len = builder.CreateIntCast(len, kmpTaskAffinityInfoTy->getElementType(1),
3208 /*isSigned=*/false);
3209 llvm::Value *flags = builder.getInt32(0);
3210
3211 builder.CreateStore(addr,
3212 builder.CreateStructGEP(kmpTaskAffinityInfoTy, entry, 0));
3213 builder.CreateStore(len,
3214 builder.CreateStructGEP(kmpTaskAffinityInfoTy, entry, 1));
3215 builder.CreateStore(flags,
3216 builder.CreateStructGEP(kmpTaskAffinityInfoTy, entry, 2));
3217}
3218
3220 llvm::IRBuilderBase &builder,
3221 LLVM::ModuleTranslation &moduleTranslation,
3222 llvm::Value *affinityList) {
3223 for (auto [i, affinityVar] : llvm::enumerate(affinityVars)) {
3224 auto entryOp = affinityVar.getDefiningOp<mlir::omp::AffinityEntryOp>();
3225 assert(entryOp && "affinity item must be omp.affinity_entry");
3226
3227 llvm::Value *addr = moduleTranslation.lookupValue(entryOp.getAddr());
3228 llvm::Value *len = moduleTranslation.lookupValue(entryOp.getLen());
3229 assert(addr && len && "expect affinity addr and len to be non-null");
3230 storeAffinityEntry(builder, *getOpenMPBuilder(moduleTranslation),
3231 affinityList, builder.getInt64(i), addr, len);
3232 }
3233}
3234
3235static mlir::LogicalResult convertIteratorRegion(
3236 llvm::Value *linearIV, IteratorInfo &iterInfo, mlir::Region &iteratorRegion,
3237 llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation) {
3238 mlir::Block &entryBlock = iteratorRegion.front();
3239 llvm::Value *tmp = linearIV;
3240 for (int d = (int)iterInfo.getDims() - 1; d >= 0; --d) {
3241 llvm::Value *trip = iterInfo.getTrips()[d];
3242 llvm::Value *zero = llvm::ConstantInt::get(trip->getType(), 0);
3243 llvm::Value *one = llvm::ConstantInt::get(trip->getType(), 1);
3244 // A zero trip count makes the linearized loop empty. Use one in its
3245 // unreachable IV decoder so the generated IR contains no division by zero.
3246 llvm::Value *divisor =
3247 builder.CreateSelect(builder.CreateICmpEQ(trip, zero), one, trip);
3248 // idx_d = tmp % divisor_d
3249 llvm::Value *idx = builder.CreateURem(tmp, divisor);
3250 // tmp = tmp / divisor_d
3251 tmp = builder.CreateUDiv(tmp, divisor);
3252
3253 // physIV_d = lb_d + idx_d * step_d
3254 idx =
3255 builder.CreateZExtOrTrunc(idx, iterInfo.getLowerBounds()[d]->getType());
3256 llvm::Value *physIV = builder.CreateAdd(
3257 iterInfo.getLowerBounds()[d],
3258 builder.CreateMul(idx, iterInfo.getSteps()[d]), "omp.it.phys_iv");
3259
3260 moduleTranslation.mapValue(entryBlock.getArgument(d), physIV);
3261 }
3262
3263 // Translate the iterator region into the loop body.
3264 if (iteratorRegion.hasOneBlock()) {
3265 moduleTranslation.mapBlock(&entryBlock, builder.GetInsertBlock());
3266 return moduleTranslation.convertBlock(entryBlock, /*ignoreArguments=*/true,
3267 builder);
3268 }
3269
3270 // Unlike convertOmpOpRegions, do not forward the yielded value through a PHI:
3271 // it may have no LLVM type (e.g. `!omp.affinity_entry_ty`). The unique
3272 // `omp.yield` block is the continuation's only predecessor, so callers can
3273 // look up the yielded value directly.
3274 llvm::BasicBlock *continuation =
3275 splitBB(builder, true, "omp.iterator.region.cont");
3276 llvm::Instruction *sourceBranch = builder.GetInsertBlock()->getTerminator();
3277 for (mlir::Block &block : iteratorRegion)
3278 moduleTranslation.mapBlock(
3279 &block,
3280 llvm::BasicBlock::Create(builder.getContext(), "omp.iterator.region",
3281 continuation->getParent(), continuation));
3282 sourceBranch->setSuccessor(0, moduleTranslation.lookupBlock(&entryBlock));
3283
3284 for (mlir::Block *block : getBlocksSortedByDominance(iteratorRegion)) {
3285 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3286 if (failed(moduleTranslation.convertBlock(*block, block->isEntryBlock(),
3287 builder)))
3288 return mlir::failure();
3289 if (isa<omp::YieldOp>(block->getTerminator()))
3290 builder.CreateBr(continuation);
3291 }
3292 LLVM::detail::connectPHINodes(iteratorRegion, moduleTranslation);
3293
3294 builder.SetInsertPoint(continuation);
3295 return mlir::success();
3296}
3297
3299 llvm::function_ref<void(llvm::Value *linearIV, mlir::omp::YieldOp yield)>;
3300
3301static mlir::LogicalResult
3302fillIteratorLoop(mlir::omp::IteratorOp itersOp, llvm::IRBuilderBase &builder,
3303 mlir::LLVM::ModuleTranslation &moduleTranslation,
3304 IteratorInfo &iterInfo, llvm::StringRef loopName,
3305 IteratorStoreEntryTy genStoreEntry) {
3306 mlir::Region &itersRegion = itersOp.getRegion();
3307
3308 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
3309
3310 auto bodyGen = [&](llvm::OpenMPIRBuilder::InsertPointTy bodyIP,
3311 llvm::Value *linearIV) -> llvm::Error {
3312 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3313 builder.restoreIP(bodyIP);
3314
3315 if (failed(convertIteratorRegion(linearIV, iterInfo, itersRegion, builder,
3316 moduleTranslation))) {
3317 return llvm::make_error<llvm::StringError>(
3318 "failed to convert iterator region", llvm::inconvertibleErrorCode());
3319 }
3320
3321 mlir::omp::YieldOp yield = itersOp.getYieldOp();
3322 assert(yield && yield.getResults().size() == 1 &&
3323 "expect omp.yield in iterator region to have one result");
3324
3325 genStoreEntry(linearIV, yield);
3326
3327 // Iterator-region block/value mappings are temporary for this conversion,
3328 // clear them to avoid stale entries in ModuleTranslation.
3329 moduleTranslation.forgetMapping(itersRegion);
3330
3331 return llvm::Error::success();
3332 };
3333
3334 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
3335 getOpenMPBuilder(moduleTranslation)
3336 ->createIteratorLoop(loc, iterInfo.getTotalTrips(), bodyGen,
3337 loopName);
3338 if (failed(handleError(afterIP, *itersOp)))
3339 return failure();
3340
3341 builder.restoreIP(*afterIP);
3342
3343 return mlir::success();
3344}
3345
3346static mlir::LogicalResult
3347buildAffinityData(mlir::omp::TaskOp &taskOp, llvm::IRBuilderBase &builder,
3348 mlir::LLVM::ModuleTranslation &moduleTranslation,
3349 llvm::OpenMPIRBuilder::AffinityData &ad) {
3350
3351 if (taskOp.getAffinityVars().empty() && taskOp.getIterated().empty()) {
3352 ad.Count = nullptr;
3353 ad.Info = nullptr;
3354 return mlir::success();
3355 }
3356
3358 llvm::StructType *kmpTaskAffinityInfoTy =
3359 getOpenMPBuilder(moduleTranslation)->getKmpTaskAffinityInfoTy();
3360
3361 auto allocateAffinityList = [&](llvm::Value *count) -> llvm::Value * {
3362 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3363 if (llvm::isa<llvm::Constant>(count) || llvm::isa<llvm::Argument>(count))
3364 builder.restoreIP(findAllocInsertPoints(builder, moduleTranslation));
3365 return builder.CreateAlloca(kmpTaskAffinityInfoTy, count,
3366 "omp.affinity_list");
3367 };
3368
3369 auto createAffinity =
3370 [&](llvm::Value *count,
3371 llvm::Value *info) -> llvm::OpenMPIRBuilder::AffinityData {
3372 llvm::OpenMPIRBuilder::AffinityData ad{};
3373 ad.Count = builder.CreateTrunc(count, builder.getInt32Ty());
3374 ad.Info =
3375 builder.CreatePointerBitCastOrAddrSpaceCast(info, builder.getPtrTy(0));
3376 return ad;
3377 };
3378
3379 if (!taskOp.getAffinityVars().empty()) {
3380 llvm::Value *count = llvm::ConstantInt::get(
3381 builder.getInt64Ty(), taskOp.getAffinityVars().size());
3382 llvm::Value *list = allocateAffinityList(count);
3383 fillAffinityLocators(taskOp.getAffinityVars(), builder, moduleTranslation,
3384 list);
3385 ads.emplace_back(createAffinity(count, list));
3386 }
3387
3388 if (!taskOp.getIterated().empty()) {
3389 for (auto [i, iter] : llvm::enumerate(taskOp.getIterated())) {
3390 auto itersOp = iter.getDefiningOp<omp::IteratorOp>();
3391 assert(itersOp && "iterated value must be defined by omp.iterator");
3392 IteratorInfo iterInfo(itersOp, moduleTranslation, builder);
3393 llvm::Value *affList = allocateAffinityList(iterInfo.getTotalTrips());
3394 if (failed(fillIteratorLoop(
3395 itersOp, builder, moduleTranslation, iterInfo, "iterator",
3396 [&](llvm::Value *linearIV, mlir::omp::YieldOp yield) {
3397 auto entryOp = yield.getResults()[0]
3398 .getDefiningOp<mlir::omp::AffinityEntryOp>();
3399 assert(entryOp && "expect yield produce an affinity entry");
3400 llvm::Value *addr =
3401 moduleTranslation.lookupValue(entryOp.getAddr());
3402 llvm::Value *len =
3403 moduleTranslation.lookupValue(entryOp.getLen());
3404 storeAffinityEntry(builder,
3405 *getOpenMPBuilder(moduleTranslation),
3406 affList, linearIV, addr, len);
3407 })))
3408 return llvm::failure();
3409 ads.emplace_back(createAffinity(iterInfo.getTotalTrips(), affList));
3410 }
3411 }
3412
3413 llvm::Value *totalAffinityCount = builder.getInt32(0);
3414 for (const auto &affinity : ads)
3415 totalAffinityCount = builder.CreateAdd(
3416 totalAffinityCount,
3417 builder.CreateIntCast(affinity.Count, builder.getInt32Ty(),
3418 /*isSigned=*/false));
3419
3420 llvm::Value *affinityInfo = ads.front().Info;
3421 if (ads.size() > 1) {
3422 llvm::StructType *kmpTaskAffinityInfoTy =
3423 getOpenMPBuilder(moduleTranslation)->getKmpTaskAffinityInfoTy();
3424 llvm::Value *affinityInfoElemSize = builder.getInt64(
3425 moduleTranslation.getLLVMModule()->getDataLayout().getTypeAllocSize(
3426 kmpTaskAffinityInfoTy));
3427
3428 llvm::Value *packedAffinityInfo = allocateAffinityList(totalAffinityCount);
3429 llvm::Value *packedAffinityInfoOffset = builder.getInt32(0);
3430 for (const auto &affinity : ads) {
3431 llvm::Value *affinityCount = builder.CreateIntCast(
3432 affinity.Count, builder.getInt32Ty(), /*isSigned=*/false);
3433 llvm::Value *affinityCountInt64 = builder.CreateIntCast(
3434 affinityCount, builder.getInt64Ty(), /*isSigned=*/false);
3435 llvm::Value *affinityInfoSize =
3436 builder.CreateMul(affinityCountInt64, affinityInfoElemSize);
3437
3438 llvm::Value *packedAffinityInfoIndex = builder.CreateIntCast(
3439 packedAffinityInfoOffset, kmpTaskAffinityInfoTy->getElementType(0),
3440 /*isSigned=*/false);
3441 packedAffinityInfoIndex = builder.CreateInBoundsGEP(
3442 kmpTaskAffinityInfoTy, packedAffinityInfo, packedAffinityInfoIndex);
3443
3444 builder.CreateMemCpy(
3445 packedAffinityInfoIndex, llvm::Align(1),
3446 builder.CreatePointerBitCastOrAddrSpaceCast(
3447 affinity.Info, builder.getPtrTy(packedAffinityInfoIndex->getType()
3448 ->getPointerAddressSpace())),
3449 llvm::Align(1), affinityInfoSize);
3450
3451 packedAffinityInfoOffset =
3452 builder.CreateAdd(packedAffinityInfoOffset, affinityCount);
3453 }
3454
3455 affinityInfo = packedAffinityInfo;
3456 }
3457
3458 ad.Count = totalAffinityCount;
3459 ad.Info = affinityInfo;
3460
3461 return mlir::success();
3462}
3463
3464// Allocates a single kmp_dep_info array sized to hold both locator
3465// (non-iterated) and iterated entries, fills the locator entries first, then
3466// runs an iterator loop for each iterator modifier object.
3467static mlir::LogicalResult
3468buildDependData(OperandRange dependVars, std::optional<ArrayAttr> dependKinds,
3469 OperandRange dependIterated,
3470 std::optional<ArrayAttr> dependIteratedKinds,
3471 llvm::IRBuilderBase &builder,
3472 mlir::LLVM::ModuleTranslation &moduleTranslation,
3473 llvm::OpenMPIRBuilder::DependenciesInfo &taskDeps) {
3474 if (dependIterated.empty()) {
3475 buildDependDataLocator(dependKinds, dependVars, moduleTranslation,
3476 taskDeps.Deps);
3477 return mlir::success();
3478 }
3479
3480 llvm::OpenMPIRBuilder &ompBuilder = *getOpenMPBuilder(moduleTranslation);
3481 llvm::Type *dependInfoTy = ompBuilder.DependInfo;
3482 unsigned numLocator = dependVars.size();
3483
3484 // Compute total count: locator deps + sum of iterator trip counts.
3485 llvm::Value *totalCount =
3486 llvm::ConstantInt::get(builder.getInt64Ty(), numLocator);
3487
3489 for (auto iter : dependIterated) {
3490 auto itersOp = iter.getDefiningOp<mlir::omp::IteratorOp>();
3491 assert(itersOp && "depend_iterated value must be defined by omp.iterator");
3492 iterInfos.emplace_back(itersOp, moduleTranslation, builder);
3493 totalCount =
3494 builder.CreateAdd(totalCount, iterInfos.back().getTotalTrips());
3495 }
3496
3497 // Heap-allocate the kmp_depend_info array so we don't risk
3498 // dynamic-sized alloca outside the entry block (e.g. inside loops).
3499 llvm::DataLayout dataLayout = builder.getDataLayout();
3500 llvm::Value *allocSize = builder.CreateTypeSize(
3501 ompBuilder.SizeTy, dataLayout.getTypeAllocSize(dependInfoTy));
3502 llvm::Value *depArray =
3503 builder.CreateMalloc(ompBuilder.SizeTy, allocSize, totalCount,
3504 /*MallocF=*/nullptr, ".dep.arr.addr");
3505
3506 // Fill non-iterated entries at indices [0, numLocator).
3507 if (numLocator > 0) {
3509 buildDependDataLocator(dependKinds, dependVars, moduleTranslation, dds);
3510 for (auto [i, dd] : llvm::enumerate(dds)) {
3511 llvm::Value *idx = llvm::ConstantInt::get(builder.getInt64Ty(), i);
3512 llvm::Value *entry =
3513 builder.CreateInBoundsGEP(dependInfoTy, depArray, idx);
3514 ompBuilder.emitTaskDependency(builder, entry, dd);
3515 }
3516 }
3517
3518 // Fill iterated entries starting at index numLocator.
3519 llvm::Value *offset =
3520 llvm::ConstantInt::get(builder.getInt64Ty(), numLocator);
3521 for (auto [i, iterInfo] : llvm::enumerate(iterInfos)) {
3522 auto kindAttr = cast<mlir::omp::ClauseTaskDependAttr>(
3523 dependIteratedKinds->getValue()[i]);
3524 llvm::omp::RTLDependenceKindTy rtlKind =
3525 convertDependKind(kindAttr.getValue());
3526
3527 auto itersOp = dependIterated[i].getDefiningOp<mlir::omp::IteratorOp>();
3528 if (failed(fillIteratorLoop(
3529 itersOp, builder, moduleTranslation, iterInfo, "dep_iterator",
3530 [&](llvm::Value *linearIV, mlir::omp::YieldOp yield) {
3531 llvm::Value *addr =
3532 moduleTranslation.lookupValue(yield.getResults()[0]);
3533 llvm::Value *idx = builder.CreateAdd(offset, linearIV);
3534 llvm::Value *entry =
3535 builder.CreateInBoundsGEP(dependInfoTy, depArray, idx);
3536 ompBuilder.emitTaskDependency(
3537 builder, entry,
3538 llvm::OpenMPIRBuilder::DependData{rtlKind, addr->getType(),
3539 addr});
3540 })))
3541 return mlir::failure();
3542
3543 // Advance offset by the trip count of this iterator.
3544 offset = builder.CreateAdd(offset, iterInfo.getTotalTrips());
3545 }
3546
3547 taskDeps.DepArray = depArray;
3548 taskDeps.NumDeps = builder.CreateTrunc(totalCount, builder.getInt32Ty());
3549 return mlir::success();
3550}
3551
3552/// Converts an OpenMP task construct into LLVM IR using OpenMPIRBuilder.
3553static LogicalResult
3554convertOmpTaskOp(omp::TaskOp taskOp, llvm::IRBuilderBase &builder,
3555 LLVM::ModuleTranslation &moduleTranslation) {
3556 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
3557 if (failed(checkImplementationStatus(*taskOp)))
3558 return failure();
3559
3560 PrivateVarsInfo privateVarsInfo(taskOp);
3561 TaskContextStructManager taskStructMgr{builder, moduleTranslation,
3562 privateVarsInfo.privatizers};
3563
3564 // Allocate and copy private variables before creating the task. This avoids
3565 // accessing invalid memory if (after this scope ends) the private variables
3566 // are initialized from host variables or if the variables are copied into
3567 // from host variables (firstprivate). The insertion point is just before
3568 // where the code for creating and scheduling the task will go. That puts this
3569 // code outside of the outlined task region, which is what we want because
3570 // this way the initialization and copy regions are executed immediately while
3571 // the host variable data are still live.
3573 InsertPointTy allocaIP =
3574 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
3575
3576 // Not using splitBB() because that requires the current block to have a
3577 // terminator.
3578 assert(builder.GetInsertPoint() == builder.GetInsertBlock()->end());
3579 llvm::BasicBlock *taskStartBlock = llvm::BasicBlock::Create(
3580 builder.getContext(), "omp.task.start",
3581 /*Parent=*/builder.GetInsertBlock()->getParent());
3582 llvm::Instruction *branchToTaskStartBlock = builder.CreateBr(taskStartBlock);
3583 builder.SetInsertPoint(branchToTaskStartBlock);
3584
3585 // Now do this again to make the initialization and copy blocks
3586 llvm::BasicBlock *copyBlock =
3587 splitBB(builder, /*CreateBranch=*/true, "omp.private.copy");
3588 llvm::BasicBlock *initBlock =
3589 splitBB(builder, /*CreateBranch=*/true, "omp.private.init");
3590
3591 // Now the control flow graph should look like
3592 // starter_block:
3593 // <---- where we started when convertOmpTaskOp was called
3594 // br %omp.private.init
3595 // omp.private.init:
3596 // br %omp.private.copy
3597 // omp.private.copy:
3598 // br %omp.task.start
3599 // omp.task.start:
3600 // <---- where we want the insertion point to be when we call createTask()
3601
3602 // Save the alloca insertion point on ModuleTranslation stack for use in
3603 // nested regions.
3605 moduleTranslation, allocaIP.getNodeParent(), deallocBlocks);
3606
3607 // Allocate and initialize private variables
3608 builder.SetInsertPoint(initBlock->getTerminator());
3609
3610 // Create task variable structure
3611 taskStructMgr.generateTaskContextStruct();
3612 // GEPs so that we can initialize the variables. Don't use these GEPs inside
3613 // of the body otherwise it will be the GEP not the struct which is fowarded
3614 // to the outlined function. GEPs forwarded in this way are passed in a
3615 // stack-allocated (by OpenMPIRBuilder) structure which is not safe for tasks
3616 // which may not be executed until after the current stack frame goes out of
3617 // scope.
3618 taskStructMgr.createGEPsToPrivateVars();
3619
3620 for (auto [privDecl, mlirPrivVar, blockArg, llvmPrivateVarAlloc] :
3621 llvm::zip_equal(privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
3622 privateVarsInfo.blockArgs,
3623 taskStructMgr.getLLVMPrivateVarGEPs())) {
3624 // To be handled inside the task.
3625 if (!privDecl.readsFromMold())
3626 continue;
3627 assert(llvmPrivateVarAlloc &&
3628 "reads from mold so shouldn't have been skipped");
3629
3630 llvm::Expected<llvm::Value *> privateVarOrErr =
3631 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
3632 blockArg, llvmPrivateVarAlloc, initBlock);
3633 if (!privateVarOrErr)
3634 return handleError(privateVarOrErr, *taskOp.getOperation());
3635
3637
3638 // TODO: this is a bit of a hack for Fortran character boxes.
3639 // Character boxes are passed by value into the init region and then the
3640 // initialized character box is yielded by value. Here we need to store the
3641 // yielded value into the private allocation, and load the private
3642 // allocation to match the type expected by region block arguments.
3643 [[maybe_unused]] llvm::Value *llvmPrivateVar = llvmPrivateVarAlloc;
3644 if ((privateVarOrErr.get() != llvmPrivateVarAlloc) &&
3645 !mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3646 builder.CreateStore(privateVarOrErr.get(), llvmPrivateVarAlloc);
3647 // Load it so we have the value pointed to by the GEP
3648 llvmPrivateVar = builder.CreateLoad(privateVarOrErr.get()->getType(),
3649 llvmPrivateVarAlloc);
3650 }
3651 assert(llvmPrivateVar->getType() ==
3652 moduleTranslation.convertType(blockArg.getType()));
3653
3654 // Mapping blockArg -> llvmPrivateVarAlloc is done inside the body callback
3655 // so that OpenMPIRBuilder doesn't try to pass each GEP address through a
3656 // stack allocated structure.
3657 }
3658
3659 // firstprivate copy region
3660 setInsertPointForPossiblyEmptyBlock(builder, copyBlock);
3661 if (failed(completePrivateVars(
3662 taskOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
3663 taskStructMgr.getLLVMPrivateVarGEPs(), privateVarsInfo.privatizers,
3664 taskOp.getPrivateNeedsBarrier())))
3665 return llvm::failure();
3666
3667 llvm::OpenMPIRBuilder::AffinityData ad;
3668 if (failed(buildAffinityData(taskOp, builder, moduleTranslation, ad)))
3669 return llvm::failure();
3670
3671 // Resolve and validate in_reduction declarations. Byref in_reduction has
3672 // already been rejected by checkImplementationStatus; the helper rejects the
3673 // remaining richer declare_reduction shapes (two-argument initializer,
3674 // cleanup region, missing combiner). This is pure MLIR symbol-table work and
3675 // emits no IR. The matching task_reduction descriptor is registered by an
3676 // enclosing taskgroup; here we only look the per-task storage up at runtime.
3679 taskOp.getOperation(), taskOp.getInReductionSyms(), "omp.task",
3680 "in_reduction", inRedDecls)))
3681 return failure();
3682 SmallVector<llvm::Value *> inRedOrigPtrs;
3683 inRedOrigPtrs.reserve(inRedDecls.size());
3684 for (Value v : taskOp.getInReductionVars())
3685 inRedOrigPtrs.push_back(moduleTranslation.lookupValue(v));
3686
3687 // Set up for call to createTask()
3688 builder.SetInsertPoint(taskStartBlock);
3689
3690 auto bodyCB =
3691 [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
3692 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
3693 // Save the alloca insertion point on ModuleTranslation stack for use in
3694 // nested regions.
3696 moduleTranslation, allocaIP.getNodeParent(), deallocBlocks);
3697
3698 // translate the body of the task:
3699 builder.restoreIP(codegenIP);
3700
3701 llvm::BasicBlock *privInitBlock = nullptr;
3702 privateVarsInfo.llvmVars.resize(privateVarsInfo.blockArgs.size());
3703 for (auto [i, zip] : llvm::enumerate(llvm::zip_equal(
3704 privateVarsInfo.blockArgs, privateVarsInfo.privatizers,
3705 privateVarsInfo.mlirVars))) {
3706 auto [blockArg, privDecl, mlirPrivVar] = zip;
3707 // This is handled before the task executes
3708 if (privDecl.readsFromMold())
3709 continue;
3710
3711 llvm::IRBuilderBase::InsertPointGuard guard(builder);
3712 llvm::Type *llvmAllocType =
3713 moduleTranslation.convertType(privDecl.getType());
3714 builder.SetInsertPoint(allocaIP.getNodeParent()->getTerminator());
3715 llvm::Value *llvmPrivateVar = builder.CreateAlloca(
3716 llvmAllocType, /*ArraySize=*/nullptr, "omp.private.alloc");
3717
3718 llvm::Expected<llvm::Value *> privateVarOrError =
3719 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
3720 blockArg, llvmPrivateVar, privInitBlock);
3721 if (!privateVarOrError)
3722 return privateVarOrError.takeError();
3723 moduleTranslation.mapValue(blockArg, privateVarOrError.get());
3724 privateVarsInfo.llvmVars[i] = privateVarOrError.get();
3725 }
3726
3727 taskStructMgr.createGEPsToPrivateVars();
3728 for (auto [i, llvmPrivVar] :
3729 llvm::enumerate(taskStructMgr.getLLVMPrivateVarGEPs())) {
3730 if (!llvmPrivVar) {
3731 assert(privateVarsInfo.llvmVars[i] &&
3732 "This is added in the loop above");
3733 continue;
3734 }
3735 privateVarsInfo.llvmVars[i] = llvmPrivVar;
3736 }
3737
3738 // Find and map the addresses of each variable within the task context
3739 // structure
3740 for (auto [blockArg, llvmPrivateVar, privateDecl] :
3741 llvm::zip_equal(privateVarsInfo.blockArgs, privateVarsInfo.llvmVars,
3742 privateVarsInfo.privatizers)) {
3743 // This was handled above.
3744 if (!privateDecl.readsFromMold())
3745 continue;
3746 // Fix broken pass-by-value case for Fortran character boxes
3747 if (!mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
3748 llvmPrivateVar = builder.CreateLoad(
3749 moduleTranslation.convertType(blockArg.getType()), llvmPrivateVar);
3750 }
3751 assert(llvmPrivateVar->getType() ==
3752 moduleTranslation.convertType(blockArg.getType()));
3753 moduleTranslation.mapValue(blockArg, llvmPrivateVar);
3754 }
3755
3756 // Map in_reduction block arguments to the per-task private storage returned
3757 // by __kmpc_task_reduction_get_th_data. This call must be emitted inside
3758 // the to-be-outlined task body so that it returns the *executing* thread's
3759 // gtid (not the encountering thread's). The descriptor is NULL: the runtime
3760 // walks up enclosing taskgroups to find the matching task_reduction
3761 // registration for `origPtr`. The original pointers are auto-captured into
3762 // the task shareds aggregate by CodeExtractor during
3763 // OpenMPIRBuilder::finalize.
3764 if (!inRedDecls.empty()) {
3765 auto iface = cast<omp::BlockArgOpenMPOpInterface>(taskOp.getOperation());
3766 llvm::OpenMPIRBuilder &ompB = *getOpenMPBuilder(moduleTranslation);
3767 llvm::Module *m = moduleTranslation.getLLVMModule();
3768 llvm::LLVMContext &llvmCtx = m->getContext();
3769 llvm::OpenMPIRBuilder::LocationDescription bodyLoc(builder);
3770 uint32_t srcLocSize;
3771 llvm::Constant *srcLocStr =
3772 ompB.getOrCreateSrcLocStr(bodyLoc, srcLocSize);
3773 llvm::Value *bodyIdent = ompB.getOrCreateIdent(srcLocStr, srcLocSize);
3774 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the gtid
3775 // call lands inside the to-be-outlined task body.
3776 ompB.updateToLocation(bodyLoc);
3777 llvm::Value *bodyGtid = ompB.getOrCreateThreadID(bodyIdent);
3778 llvm::FunctionCallee getThData = ompB.getOrCreateRuntimeFunction(
3779 *m, llvm::omp::OMPRTL___kmpc_task_reduction_get_th_data);
3780 llvm::Type *ptrTy = llvm::PointerType::getUnqual(llvmCtx);
3781 llvm::Value *nullDesc = llvm::ConstantPointerNull::get(ptrTy);
3782 ArrayRef<BlockArgument> inRedBlockArgs = iface.getInReductionBlockArgs();
3783 for (auto [blockArg, origPtr] :
3784 llvm::zip_equal(inRedBlockArgs, inRedOrigPtrs)) {
3785 // __kmpc_task_reduction_get_th_data takes and returns a generic,
3786 // default-address-space `ptr`. Normalize a non-default-address-space
3787 // original pointer to the generic address space before the call, and
3788 // cast the returned private pointer back to the block argument's
3789 // address space when it differs (mirrors the taskloop reduction
3790 // remapping in convertOmpTaskloopContextOp).
3791 llvm::Value *lookupPtr = origPtr;
3792 if (auto *origPtrTy =
3793 llvm::dyn_cast<llvm::PointerType>(lookupPtr->getType());
3794 origPtrTy && origPtrTy->getAddressSpace() != 0)
3795 lookupPtr = builder.CreateAddrSpaceCast(lookupPtr, ptrTy);
3796 llvm::Value *priv = builder.CreateCall(
3797 getThData, {bodyGtid, nullDesc, lookupPtr}, "omp.inred.priv");
3798 if (auto *argPtrTy = llvm::dyn_cast<llvm::PointerType>(
3799 moduleTranslation.convertType(blockArg.getType()));
3800 argPtrTy && argPtrTy->getAddressSpace() != 0)
3801 priv = builder.CreateAddrSpaceCast(priv, argPtrTy);
3802 moduleTranslation.mapValue(blockArg, priv);
3803 }
3804 }
3805
3806 auto continuationBlockOrError = convertOmpOpRegions(
3807 taskOp.getRegion(), "omp.task.region", builder, moduleTranslation);
3808 if (failed(handleError(continuationBlockOrError, *taskOp)))
3809 return llvm::make_error<PreviouslyReportedError>();
3810
3811 builder.SetInsertPoint(continuationBlockOrError.get()->getTerminator());
3812
3813 if (failed(cleanupPrivateVars(taskOp, builder, moduleTranslation,
3814 taskOp.getLoc(), privateVarsInfo)))
3815 return llvm::make_error<PreviouslyReportedError>();
3816
3817 // Free heap allocated task context structure at the end of the task.
3818 taskStructMgr.freeStructPtr();
3819
3820 return llvm::Error::success();
3821 };
3822
3823 llvm::OpenMPIRBuilder &ompBuilder = *getOpenMPBuilder(moduleTranslation);
3824 SmallVector<llvm::UncondBrInst *> cancelTerminators;
3825 // The directive to match here is OMPD_taskgroup because it is the taskgroup
3826 // which is canceled. This is handled here because it is the task's cleanup
3827 // block which should be branched to.
3828 pushCancelFinalizationCB(cancelTerminators, builder, ompBuilder, taskOp,
3829 llvm::omp::Directive::OMPD_taskgroup);
3830
3831 llvm::OpenMPIRBuilder::DependenciesInfo dependencies;
3832 if (failed(buildDependData(taskOp.getDependVars(), taskOp.getDependKinds(),
3833 taskOp.getDependIterated(),
3834 taskOp.getDependIteratedKinds(), builder,
3835 moduleTranslation, dependencies)))
3836 return failure();
3837
3838 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
3839 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
3840 getOpenMPBuilder(moduleTranslation)
3841 ->createTask(ompLoc, allocaIP, deallocBlocks, bodyCB,
3842 !taskOp.getUntied(),
3843 moduleTranslation.lookupValue(taskOp.getFinal()),
3844 moduleTranslation.lookupValue(taskOp.getIfExpr()),
3845 dependencies, ad, taskOp.getMergeable(),
3846 moduleTranslation.lookupValue(taskOp.getEventHandle()),
3847 moduleTranslation.lookupValue(taskOp.getPriority()),
3848 taskOp.getThreadset() == omp::ThreadsetPolicy::omp_pool);
3849
3850 if (failed(handleError(afterIP, *taskOp)))
3851 return failure();
3852
3853 // Set the correct branch target for task cancellation
3854 popCancelFinalizationCB(cancelTerminators, ompBuilder,
3855 afterIP->getNodeParent());
3856
3857 builder.restoreIP(*afterIP);
3858
3859 if (dependencies.DepArray)
3860 builder.CreateFree(dependencies.DepArray);
3861
3862 return success();
3863}
3864
3865/// The correct entry point is convertOmpTaskloopContextOp. This gets called
3866/// whilst lowering the body of the taskloop context (i.e. the task function).
3867static LogicalResult
3868convertOmpTaskloopWrapperOp(omp::TaskloopWrapperOp loopWrapperOp,
3869 llvm::IRBuilderBase &builder,
3870 LLVM::ModuleTranslation &moduleTranslation) {
3871 mlir::Operation &opInst = *loopWrapperOp.getOperation();
3872 if (failed(checkImplementationStatus(opInst)))
3873 return failure();
3874
3875 // Recurse into the loop body.
3876 auto continuationBlockOrError = convertOmpOpRegions(
3877 loopWrapperOp.getRegion(), "omp.taskloop.wrapper.region", builder,
3878 moduleTranslation);
3879
3880 if (failed(handleError(continuationBlockOrError, opInst)))
3881 return failure();
3882
3883 builder.SetInsertPoint(continuationBlockOrError.get());
3884 return success();
3885}
3886
3887/// Look up the given value in the mapping, and if it's not there, translate its
3888/// defining operation at the current builder insertion point. Only pure,
3889/// regionless operations are supported because the same operation will later be
3890/// translated again when the taskloop body itself is lowered.
3891static llvm::Expected<llvm::Value *>
3893 LLVM::ModuleTranslation &moduleTranslation,
3894 llvm::IRBuilderBase &builder) {
3895 if (llvm::Value *mapped = moduleTranslation.lookupValue(value))
3896 return mapped;
3897
3898 Operation *defOp = value.getDefiningOp();
3899 if (!defOp)
3900 return llvm::make_error<llvm::StringError>(
3901 "value is a block argument and is not mapped",
3902 llvm::inconvertibleErrorCode());
3903 if (defOp->getNumRegions() != 0 || !isPure(defOp))
3904 return llvm::make_error<llvm::StringError>(
3905 "unsupported op defining taskloop loop bound",
3906 llvm::inconvertibleErrorCode());
3907
3908 SmallVector<Value> mappingsToRemove;
3909 mappingsToRemove.reserve(defOp->getNumOperands() + defOp->getNumResults());
3910 for (Value operand : defOp->getOperands()) {
3911 if (moduleTranslation.lookupValue(operand))
3912 continue;
3913
3914 llvm::Expected<llvm::Value *> operandOrError =
3915 lookupOrTranslatePureValue(operand, moduleTranslation, builder);
3916 if (!operandOrError)
3917 return operandOrError.takeError();
3918 moduleTranslation.mapValue(operand, *operandOrError);
3919 mappingsToRemove.push_back(operand);
3920 }
3921
3922 if (failed(moduleTranslation.convertOperation(*defOp, builder)))
3923 return llvm::make_error<llvm::StringError>(
3924 "failed to convert op defining taskloop loop bound",
3925 llvm::inconvertibleErrorCode());
3926
3927 llvm::Value *result = moduleTranslation.lookupValue(value);
3928 assert(result && "expected conversion of loop bound op to produce a value");
3929
3930 for (Value resultValue : defOp->getResults()) {
3931 if (moduleTranslation.lookupValue(resultValue))
3932 mappingsToRemove.push_back(resultValue);
3933 }
3934 for (Value mappedValue : mappingsToRemove)
3935 moduleTranslation.forgetMapping(mappedValue);
3936
3937 return result;
3938}
3939
3940static llvm::Error
3941computeTaskloopBounds(omp::LoopNestOp loopOp, llvm::IRBuilderBase &builder,
3942 LLVM::ModuleTranslation &moduleTranslation,
3943 llvm::Value *&lbVal, llvm::Value *&ubVal,
3944 llvm::Value *&stepVal) {
3945 Operation::operand_range lowerBounds = loopOp.getLoopLowerBounds();
3946 Operation::operand_range upperBounds = loopOp.getLoopUpperBounds();
3947 Operation::operand_range steps = loopOp.getLoopSteps();
3948
3949 llvm::Expected<llvm::Value *> firstLbOrErr =
3950 lookupOrTranslatePureValue(lowerBounds[0], moduleTranslation, builder);
3951 if (!firstLbOrErr)
3952 return firstLbOrErr.takeError();
3953
3954 llvm::Type *boundType = (*firstLbOrErr)->getType();
3955 ubVal = builder.getIntN(boundType->getIntegerBitWidth(), 1);
3956 if (loopOp.getCollapseNumLoops() > 1) {
3957 // In cases where Collapse is used with Taskloop, the upper bound of the
3958 // iteration space needs to be recalculated to cater for the collapsed loop.
3959 // The Collapsed Loop UpperBound is the product of all collapsed
3960 // loop's tripcount.
3961 // The LowerBound for collapsed loops is always 1. When the loops are
3962 // collapsed, it will reset the bounds and introduce processing to ensure
3963 // the index's are presented as expected. As this happens after creating
3964 // Taskloop, these bounds need predicting. Example:
3965 // !$omp taskloop collapse(2)
3966 // do i = 1, 10
3967 // do j = 1, 5
3968 // ..
3969 // end do
3970 // end do
3971 // This loop above has a total of 50 iterations, so the lb will be 1, and
3972 // the ub will be 50. collapseLoops in OMPIRBuilder then handles ensuring
3973 // that i and j are properly presented when used in the loop.
3974 for (uint64_t i = 0; i < loopOp.getCollapseNumLoops(); i++) {
3976 i == 0 ? std::move(firstLbOrErr)
3977 : lookupOrTranslatePureValue(lowerBounds[i], moduleTranslation,
3978 builder);
3979 if (!lbOrErr)
3980 return lbOrErr.takeError();
3982 upperBounds[i], moduleTranslation, builder);
3983 if (!ubOrErr)
3984 return ubOrErr.takeError();
3986 lookupOrTranslatePureValue(steps[i], moduleTranslation, builder);
3987 if (!stepOrErr)
3988 return stepOrErr.takeError();
3989
3990 llvm::Value *loopLb = *lbOrErr;
3991 llvm::Value *loopUb = *ubOrErr;
3992 llvm::Value *loopStep = *stepOrErr;
3993 // In some cases, such as where the ub is less than the lb so the loop
3994 // steps down, the calculation for the loopTripCount is swapped. To ensure
3995 // the correct value is found, calculate both UB - LB and LB - UB then
3996 // select which value to use depending on how the loop has been
3997 // configured.
3998 llvm::Value *loopLbMinusOne = builder.CreateSub(
3999 loopLb, builder.getIntN(boundType->getIntegerBitWidth(), 1));
4000 llvm::Value *loopUbMinusOne = builder.CreateSub(
4001 loopUb, builder.getIntN(boundType->getIntegerBitWidth(), 1));
4002 llvm::Value *boundsCmp = builder.CreateICmpSLT(loopLb, loopUb);
4003 llvm::Value *ubMinusLb = builder.CreateSub(loopUb, loopLbMinusOne);
4004 llvm::Value *lbMinusUb = builder.CreateSub(loopLb, loopUbMinusOne);
4005 llvm::Value *loopTripCount =
4006 builder.CreateSelect(boundsCmp, ubMinusLb, lbMinusUb);
4007 loopTripCount = builder.CreateBinaryIntrinsic(
4008 llvm::Intrinsic::abs, loopTripCount, builder.getFalse());
4009 // For loops that have a step value not equal to 1, we need to adjust the
4010 // trip count to ensure the correct number of iterations for the loop is
4011 // captured.
4012 llvm::Value *loopTripCountDivStep =
4013 builder.CreateSDiv(loopTripCount, loopStep);
4014 loopTripCountDivStep = builder.CreateBinaryIntrinsic(
4015 llvm::Intrinsic::abs, loopTripCountDivStep, builder.getFalse());
4016 llvm::Value *loopTripCountRem =
4017 builder.CreateSRem(loopTripCount, loopStep);
4018 loopTripCountRem = builder.CreateBinaryIntrinsic(
4019 llvm::Intrinsic::abs, loopTripCountRem, builder.getFalse());
4020 llvm::Value *needsRoundUp = builder.CreateICmpNE(
4021 loopTripCountRem,
4022 builder.getIntN(loopTripCountRem->getType()->getIntegerBitWidth(),
4023 0));
4024 loopTripCount =
4025 builder.CreateAdd(loopTripCountDivStep,
4026 builder.CreateZExtOrTrunc(
4027 needsRoundUp, loopTripCountDivStep->getType()));
4028 ubVal = builder.CreateMul(ubVal, loopTripCount);
4029 }
4030 lbVal = builder.getIntN(boundType->getIntegerBitWidth(), 1);
4031 stepVal = builder.getIntN(boundType->getIntegerBitWidth(), 1);
4032 } else {
4034 lookupOrTranslatePureValue(upperBounds[0], moduleTranslation, builder);
4035 if (!ubOrErr)
4036 return ubOrErr.takeError();
4038 lookupOrTranslatePureValue(steps[0], moduleTranslation, builder);
4039 if (!stepOrErr)
4040 return stepOrErr.takeError();
4041 lbVal = *firstLbOrErr;
4042 ubVal = *ubOrErr;
4043 stepVal = *stepOrErr;
4044 }
4045
4046 assert(lbVal != nullptr && "Expected value for lbVal");
4047 assert(ubVal != nullptr && "Expected value for ubVal");
4048 assert(stepVal != nullptr && "Expected value for stepVal");
4049 return llvm::Error::success();
4050}
4051
4052// Converts an OpenMP taskloop construct into LLVM IR using OpenMPIRBuilder.
4053static LogicalResult
4054convertOmpTaskloopContextOp(omp::TaskloopContextOp contextOp,
4055 llvm::IRBuilderBase &builder,
4056 LLVM::ModuleTranslation &moduleTranslation) {
4057 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
4058 mlir::Operation &opInst = *contextOp.getOperation();
4059 omp::TaskloopWrapperOp loopWrapperOp = contextOp.getLoopOp();
4060 if (failed(checkImplementationStatus(opInst)))
4061 return failure();
4062
4063 // It stores the pointer of allocated firstprivate copies,
4064 // which can be used later for freeing the allocated space.
4065 SmallVector<llvm::Value *> llvmFirstPrivateVars;
4066 PrivateVarsInfo privateVarsInfo(contextOp);
4067 TaskContextStructManager taskStructMgr{builder, moduleTranslation,
4068 privateVarsInfo.privatizers};
4069
4071 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
4072 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
4073
4074 assert(builder.GetInsertPoint() == builder.GetInsertBlock()->end());
4075 llvm::BasicBlock *taskloopStartBlock = llvm::BasicBlock::Create(
4076 builder.getContext(), "omp.taskloop.wrapper.start",
4077 /*Parent=*/builder.GetInsertBlock()->getParent());
4078 llvm::Instruction *branchToTaskloopStartBlock =
4079 builder.CreateBr(taskloopStartBlock);
4080 builder.SetInsertPoint(branchToTaskloopStartBlock);
4081
4082 llvm::BasicBlock *copyBlock =
4083 splitBB(builder, /*CreateBranch=*/true, "omp.private.copy");
4084 llvm::BasicBlock *initBlock =
4085 splitBB(builder, /*CreateBranch=*/true, "omp.private.init");
4086
4088 moduleTranslation, allocaIP.getNodeParent(), deallocBlocks);
4089
4090 // Allocate and initialize private variables
4091 builder.SetInsertPoint(initBlock->getTerminator());
4092
4093 // TODO: don't allocate if the loop has zero iterations.
4094 taskStructMgr.generateTaskContextStruct();
4095 taskStructMgr.createGEPsToPrivateVars();
4096
4097 llvmFirstPrivateVars.resize(privateVarsInfo.blockArgs.size());
4098
4099 for (auto [i, zip] : llvm::enumerate(llvm::zip_equal(
4100 privateVarsInfo.privatizers, privateVarsInfo.mlirVars,
4101 privateVarsInfo.blockArgs, taskStructMgr.getLLVMPrivateVarGEPs()))) {
4102 auto [privDecl, mlirPrivVar, blockArg, llvmPrivateVarAlloc] = zip;
4103 // To be handled inside the taskloop.
4104 if (!privDecl.readsFromMold())
4105 continue;
4106 assert(llvmPrivateVarAlloc &&
4107 "reads from mold so shouldn't have been skipped");
4108
4109 llvm::Expected<llvm::Value *> privateVarOrErr =
4110 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
4111 blockArg, llvmPrivateVarAlloc, initBlock);
4112 if (!privateVarOrErr)
4113 return handleError(privateVarOrErr, *contextOp.getOperation());
4114
4115 llvmFirstPrivateVars[i] = privateVarOrErr.get();
4116
4117 llvm::IRBuilderBase::InsertPointGuard guard(builder);
4118 builder.SetInsertPoint(builder.GetInsertBlock()->getTerminator());
4119
4120 [[maybe_unused]] llvm::Value *llvmPrivateVar = llvmPrivateVarAlloc;
4121 if ((privateVarOrErr.get() != llvmPrivateVarAlloc) &&
4122 !mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
4123 builder.CreateStore(privateVarOrErr.get(), llvmPrivateVarAlloc);
4124 // Load it so we have the value pointed to by the GEP
4125 llvmPrivateVar = builder.CreateLoad(privateVarOrErr.get()->getType(),
4126 llvmPrivateVarAlloc);
4127 }
4128 assert(llvmPrivateVar->getType() ==
4129 moduleTranslation.convertType(blockArg.getType()));
4130 }
4131
4132 // firstprivate copy region
4133 setInsertPointForPossiblyEmptyBlock(builder, copyBlock);
4134 if (failed(completePrivateVars(
4135 contextOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
4136 taskStructMgr.getLLVMPrivateVarGEPs(), privateVarsInfo.privatizers,
4137 contextOp.getPrivateNeedsBarrier())))
4138 return llvm::failure();
4139
4140 // Resolve and validate reduction / in_reduction declarations up front.
4141 // This is pure MLIR symbol-table work and does not emit IR, so do it
4142 // before moving the builder to the taskloop start block. Richer
4143 // declare_reduction shapes (byref) have been rejected already by
4144 // checkImplementationStatus; the rest (two-argument initializer, cleanup
4145 // region, missing combiner) are rejected by the helper.
4148 contextOp.getOperation(), contextOp.getReductionSyms(),
4149 "omp.taskloop.context", "reduction", redDecls)))
4150 return failure();
4153 contextOp.getOperation(), contextOp.getInReductionSyms(),
4154 "omp.taskloop.context", "in_reduction", inRedDecls)))
4155 return failure();
4156
4157 // The op verifier rejects nogroup + reduction, so no check is needed here.
4158
4159 SmallVector<llvm::Value *> redOrigPtrs;
4160 redOrigPtrs.reserve(redDecls.size());
4161 for (Value v : contextOp.getReductionVars())
4162 redOrigPtrs.push_back(moduleTranslation.lookupValue(v));
4163 SmallVector<llvm::Value *> inRedOrigPtrs;
4164 inRedOrigPtrs.reserve(inRedDecls.size());
4165 for (Value v : contextOp.getInReductionVars())
4166 inRedOrigPtrs.push_back(moduleTranslation.lookupValue(v));
4167
4168 // Set up insertion point for emitting the implicit-taskgroup reduction
4169 // setup (if any) and for the subsequent call to createTaskloop().
4170 builder.SetInsertPoint(taskloopStartBlock);
4171
4172 llvm::OpenMPIRBuilder &ompBuilderRef = *getOpenMPBuilder(moduleTranslation);
4173 llvm::Module *module = moduleTranslation.getLLVMModule();
4174
4175 // If we have task_reduction items, we must emit our own implicit
4176 // __kmpc_taskgroup so that the descriptor returned by __kmpc_taskred_init
4177 // is associated with that taskgroup. We then force NoGroup=true so that
4178 // OpenMPIRBuilder::createTaskloop does not emit a second taskgroup.
4179 bool implicitTaskgroup = !redDecls.empty();
4180 llvm::Value *redDesc = nullptr;
4181 if (implicitTaskgroup) {
4182 llvm::OpenMPIRBuilder::LocationDescription redLoc(builder);
4183 uint32_t srcLocSize;
4184 llvm::Constant *srcLocStr =
4185 ompBuilderRef.getOrCreateSrcLocStr(redLoc, srcLocSize);
4186 llvm::Value *ident = ompBuilderRef.getOrCreateIdent(srcLocStr, srcLocSize);
4187 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the
4188 // gtid call lands at our insertion point.
4189 ompBuilderRef.updateToLocation(redLoc);
4190 llvm::Value *outerGtid = ompBuilderRef.getOrCreateThreadID(ident);
4191 llvm::FunctionCallee taskgroupFn = ompBuilderRef.getOrCreateRuntimeFunction(
4192 *module, llvm::omp::OMPRTL___kmpc_taskgroup);
4193 builder.CreateCall(taskgroupFn, {ident, outerGtid});
4194
4195 redDesc = emitTaskReductionInitCall(redDecls, redOrigPtrs,
4196 "__omp_taskloop_taskred_", builder,
4197 allocaIP, moduleTranslation);
4198 if (!redDesc)
4199 return failure();
4200 }
4201
4202 auto loopOp = cast<omp::LoopNestOp>(loopWrapperOp.getWrappedLoop());
4203 llvm::Value *lbVal = nullptr;
4204 llvm::Value *ubVal = nullptr;
4205 llvm::Value *stepVal = nullptr;
4206 if (llvm::Error err = computeTaskloopBounds(
4207 loopOp, builder, moduleTranslation, lbVal, ubVal, stepVal))
4208 return handleError(std::move(err), opInst);
4209
4210 auto bodyCB =
4211 [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
4212 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
4213 // Save the alloca insertion point on ModuleTranslation stack for use in
4214 // nested regions.
4216 moduleTranslation, allocaIP.getNodeParent(), deallocBlocks);
4217
4218 // translate the body of the taskloop:
4219 builder.restoreIP(codegenIP);
4220
4221 llvm::BasicBlock *privInitBlock = nullptr;
4222 privateVarsInfo.llvmVars.resize(privateVarsInfo.blockArgs.size());
4223 for (auto [i, zip] : llvm::enumerate(llvm::zip_equal(
4224 privateVarsInfo.blockArgs, privateVarsInfo.privatizers,
4225 privateVarsInfo.mlirVars))) {
4226 auto [blockArg, privDecl, mlirPrivVar] = zip;
4227 // This is handled before the task executes
4228 if (privDecl.readsFromMold())
4229 continue;
4230
4231 llvm::IRBuilderBase::InsertPointGuard guard(builder);
4232 llvm::Type *llvmAllocType =
4233 moduleTranslation.convertType(privDecl.getType());
4234 builder.SetInsertPoint(allocaIP.getNodeParent()->getTerminator());
4235 llvm::Value *llvmPrivateVar = builder.CreateAlloca(
4236 llvmAllocType, /*ArraySize=*/nullptr, "omp.private.alloc");
4237
4238 llvm::Expected<llvm::Value *> privateVarOrError =
4239 initPrivateVar(builder, moduleTranslation, privDecl, mlirPrivVar,
4240 blockArg, llvmPrivateVar, privInitBlock);
4241 if (!privateVarOrError)
4242 return privateVarOrError.takeError();
4243 moduleTranslation.mapValue(blockArg, privateVarOrError.get());
4244 privateVarsInfo.llvmVars[i] = privateVarOrError.get();
4245 }
4246
4247 taskStructMgr.createGEPsToPrivateVars();
4248 for (auto [i, llvmPrivVar] :
4249 llvm::enumerate(taskStructMgr.getLLVMPrivateVarGEPs())) {
4250 if (!llvmPrivVar) {
4251 assert(privateVarsInfo.llvmVars[i] &&
4252 "This is added in the loop above");
4253 continue;
4254 }
4255 privateVarsInfo.llvmVars[i] = llvmPrivVar;
4256 }
4257
4258 // Find and map the addresses of each variable within the taskloop context
4259 // structure
4260 for (auto [blockArg, llvmPrivateVar, privateDecl] :
4261 llvm::zip_equal(privateVarsInfo.blockArgs, privateVarsInfo.llvmVars,
4262 privateVarsInfo.privatizers)) {
4263 // This was handled above.
4264 if (!privateDecl.readsFromMold())
4265 continue;
4266 // Fix broken pass-by-value case for Fortran character boxes
4267 if (!mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
4268 llvmPrivateVar = builder.CreateLoad(
4269 moduleTranslation.convertType(blockArg.getType()), llvmPrivateVar);
4270 }
4271 assert(llvmPrivateVar->getType() ==
4272 moduleTranslation.convertType(blockArg.getType()));
4273 moduleTranslation.mapValue(blockArg, llvmPrivateVar);
4274 }
4275
4276 // Map reduction and in_reduction block arguments to the per-task private
4277 // storage returned by __kmpc_task_reduction_get_th_data. This call must
4278 // be emitted inside the to-be-outlined task body so that it returns the
4279 // *executing* thread's gtid (not the encountering thread's). The
4280 // taskgroup descriptor `redDesc` is computed in the outer scope and is
4281 // auto-captured into the task shareds aggregate by CodeExtractor during
4282 // OpenMPIRBuilder::finalize. For in_reduction the descriptor is NULL:
4283 // the runtime walks up enclosing taskgroups to find the matching
4284 // task_reduction registration for `origPtr`.
4285 if (!redDecls.empty() || !inRedDecls.empty()) {
4286 auto iface =
4287 cast<omp::BlockArgOpenMPOpInterface>(contextOp.getOperation());
4288 llvm::OpenMPIRBuilder &ompB = *getOpenMPBuilder(moduleTranslation);
4289 llvm::Module *m = moduleTranslation.getLLVMModule();
4290 llvm::LLVMContext &llvmCtx = m->getContext();
4291 llvm::OpenMPIRBuilder::LocationDescription bodyLoc(builder);
4292 uint32_t srcLocSize;
4293 llvm::Constant *srcLocStr =
4294 ompB.getOrCreateSrcLocStr(bodyLoc, srcLocSize);
4295 llvm::Value *bodyIdent = ompB.getOrCreateIdent(srcLocStr, srcLocSize);
4296 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the
4297 // gtid call lands inside the to-be-outlined task body.
4298 ompB.updateToLocation(bodyLoc);
4299 llvm::Value *bodyGtid = ompB.getOrCreateThreadID(bodyIdent);
4300 llvm::FunctionCallee getThData = ompB.getOrCreateRuntimeFunction(
4301 *m, llvm::omp::OMPRTL___kmpc_task_reduction_get_th_data);
4302 llvm::Type *ptrTy = llvm::PointerType::getUnqual(llvmCtx);
4303
4304 // Emit one __kmpc_task_reduction_get_th_data lookup for a reduction /
4305 // in_reduction item and map its block argument to the per-task private
4306 // storage the runtime returns. The runtime entry point takes (and
4307 // returns) a generic, default-address-space `ptr`, so normalize a
4308 // non-default-address-space original pointer to the generic address
4309 // space before the call (mirroring the descriptor setup in
4310 // emitTaskReductionInitCall), and cast the returned private pointer back
4311 // to the block argument's address space when that differs.
4312 auto remapReductionArg = [&](BlockArgument blockArg, llvm::Value *desc,
4313 llvm::Value *origPtr,
4314 const llvm::Twine &name) {
4315 if (auto *origPtrTy =
4316 llvm::dyn_cast<llvm::PointerType>(origPtr->getType());
4317 origPtrTy && origPtrTy->getAddressSpace() != 0)
4318 origPtr = builder.CreateAddrSpaceCast(origPtr, ptrTy);
4319 llvm::Value *priv =
4320 builder.CreateCall(getThData, {bodyGtid, desc, origPtr}, name);
4321 if (auto *argPtrTy = llvm::dyn_cast<llvm::PointerType>(
4322 moduleTranslation.convertType(blockArg.getType()));
4323 argPtrTy && argPtrTy->getAddressSpace() != 0)
4324 priv = builder.CreateAddrSpaceCast(priv, argPtrTy);
4325 moduleTranslation.mapValue(blockArg, priv);
4326 };
4327
4328 ArrayRef<BlockArgument> redBlockArgs = iface.getReductionBlockArgs();
4329 for (auto [blockArg, origPtr] :
4330 llvm::zip_equal(redBlockArgs, redOrigPtrs))
4331 remapReductionArg(blockArg, redDesc, origPtr, "omp.taskred.priv");
4332 ArrayRef<BlockArgument> inRedBlockArgs = iface.getInReductionBlockArgs();
4333 llvm::Value *nullDesc = llvm::ConstantPointerNull::get(ptrTy);
4334 for (auto [blockArg, origPtr] :
4335 llvm::zip_equal(inRedBlockArgs, inRedOrigPtrs))
4336 remapReductionArg(blockArg, nullDesc, origPtr, "omp.inred.priv");
4337 }
4338
4339 // Lower the contents of the taskloop context region: this is the body of
4340 // the generated task, not the loop.
4341 auto continuationBlockOrError = convertOmpOpRegions(
4342 contextOp.getRegion(), "omp.taskloop.context.region", builder,
4343 moduleTranslation);
4344
4345 if (failed(handleError(continuationBlockOrError, opInst)))
4346 return llvm::make_error<PreviouslyReportedError>();
4347
4348 builder.SetInsertPoint(continuationBlockOrError.get()->getTerminator());
4349
4350 // This is freeing the private variables as mapped inside of the task: these
4351 // will be per-task private copies possibly after task duplication. This is
4352 // handled transparently by how these are passed to the structure passed
4353 // into the outlined function. When the task is duplicated, that structure
4354 // is duplicated too.
4355 if (failed(cleanupPrivateVars(contextOp, builder, moduleTranslation,
4356 contextOp.getLoc(), privateVarsInfo)))
4357 return llvm::make_error<PreviouslyReportedError>();
4358 // Similarly, the task context structure freed inside the task is the
4359 // per-task copy after task duplication.
4360 taskStructMgr.freeStructPtr();
4361
4362 return llvm::Error::success();
4363 };
4364
4365 // Taskloop divides into an appropriate number of tasks by repeatedly
4366 // duplicating the original task. Each time this is done, the task context
4367 // structure must be duplicated too.
4368 auto taskDupCB = [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
4369 llvm::Value *destPtr, llvm::Value *srcPtr)
4371 llvm::IRBuilderBase::InsertPointGuard guard(builder);
4372 builder.restoreIP(codegenIP);
4373
4374 llvm::Type *ptrTy =
4375 builder.getPtrTy(srcPtr->getType()->getPointerAddressSpace());
4376 llvm::Value *src =
4377 builder.CreateLoad(ptrTy, srcPtr, "omp.taskloop.context.src");
4378
4379 TaskContextStructManager &srcStructMgr = taskStructMgr;
4380 TaskContextStructManager destStructMgr(builder, moduleTranslation,
4381 privateVarsInfo.privatizers);
4382 destStructMgr.generateTaskContextStruct();
4383 llvm::Value *dest = destStructMgr.getStructPtr();
4384 dest->setName("omp.taskloop.context.dest");
4385 builder.CreateStore(dest, destPtr);
4386
4388 srcStructMgr.createGEPsToPrivateVars(src);
4390 destStructMgr.createGEPsToPrivateVars(dest);
4391
4392 // Inline init regions.
4393 for (auto [privDecl, mold, blockArg, llvmPrivateVarAlloc] :
4394 llvm::zip_equal(privateVarsInfo.privatizers, srcGEPs,
4395 privateVarsInfo.blockArgs, destGEPs)) {
4396 // To be handled inside task body.
4397 if (!privDecl.readsFromMold())
4398 continue;
4399 assert(llvmPrivateVarAlloc &&
4400 "reads from mold so shouldn't have been skipped");
4401
4402 llvm::Value *moldArg = materializeRegionArgValue(
4403 builder, moduleTranslation, privDecl.getInitMoldArg(), mold);
4405 builder, moduleTranslation, privDecl, moldArg, blockArg,
4406 llvmPrivateVarAlloc, builder.GetInsertBlock());
4407 if (!privateVarOrErr)
4408 return privateVarOrErr.takeError();
4409
4411
4412 // TODO: this is a bit of a hack for Fortran character boxes.
4413 // Character boxes are passed by value into the init region and then the
4414 // initialized character box is yielded by value. Here we need to store
4415 // the yielded value into the private allocation, and load the private
4416 // allocation to match the type expected by region block arguments.
4417 [[maybe_unused]] llvm::Value *llvmPrivateVar = llvmPrivateVarAlloc;
4418 if ((privateVarOrErr.get() != llvmPrivateVarAlloc) &&
4419 !mlir::isa<LLVM::LLVMPointerType>(blockArg.getType())) {
4420 builder.CreateStore(privateVarOrErr.get(), llvmPrivateVarAlloc);
4421 // Load it so we have the value pointed to by the GEP
4422 llvmPrivateVar = builder.CreateLoad(privateVarOrErr.get()->getType(),
4423 llvmPrivateVarAlloc);
4424 }
4425 assert(llvmPrivateVar->getType() ==
4426 moduleTranslation.convertType(blockArg.getType()));
4427
4428 // Mapping blockArg -> llvmPrivateVarAlloc is done inside the body
4429 // callback so that OpenMPIRBuilder doesn't try to pass each GEP address
4430 // through a stack allocated structure.
4431 }
4432
4433 if (failed(completePrivateVars(contextOp.getOperation(), builder,
4434 moduleTranslation, srcGEPs, destGEPs,
4435 privateVarsInfo.privatizers,
4436 contextOp.getPrivateNeedsBarrier())))
4437 return llvm::make_error<PreviouslyReportedError>();
4438
4439 return builder.saveIP();
4440 };
4441
4442 auto loopInfo = [&]() -> llvm::Expected<llvm::CanonicalLoopInfo *> {
4443 llvm::CanonicalLoopInfo *loopInfo = findCurrentLoopInfo(moduleTranslation);
4444 return loopInfo;
4445 };
4446
4447 llvm::Value *ifCond = nullptr;
4448 llvm::Value *grainsize = nullptr;
4449 int sched = 0; // default
4450 mlir::Value grainsizeVal = contextOp.getGrainsize();
4451 mlir::Value numTasksVal = contextOp.getNumTasks();
4452 if (Value ifVar = contextOp.getIfExpr())
4453 ifCond = moduleTranslation.lookupValue(ifVar);
4454 if (grainsizeVal) {
4455 grainsize = moduleTranslation.lookupValue(grainsizeVal);
4456 sched = 1; // grainsize
4457 } else if (numTasksVal) {
4458 grainsize = moduleTranslation.lookupValue(numTasksVal);
4459 sched = 2; // num_tasks
4460 }
4461
4462 llvm::OpenMPIRBuilder::TaskDupCallbackTy taskDupOrNull = nullptr;
4463 if (taskStructMgr.getStructPtr())
4464 taskDupOrNull = taskDupCB;
4465
4466 llvm::OpenMPIRBuilder &ompBuilder = *getOpenMPBuilder(moduleTranslation);
4467 SmallVector<llvm::UncondBrInst *> cancelTerminators;
4468 // The directive to match here is OMPD_taskgroup because it is the
4469 // taskgroup which is canceled. This is handled here because it is the
4470 // task's cleanup block which should be branched to. It doesn't depend upon
4471 // nogroup because even in that case the taskloop might still be inside an
4472 // explicit taskgroup.
4473 pushCancelFinalizationCB(cancelTerminators, builder, ompBuilder, contextOp,
4474 llvm::omp::Directive::OMPD_taskgroup);
4475
4476 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4477 bool effectiveNoGroup = contextOp.getNogroup() || implicitTaskgroup;
4478 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
4479 getOpenMPBuilder(moduleTranslation)
4480 ->createTaskloop(
4481 ompLoc, allocaIP, deallocBlocks, bodyCB, loopInfo, lbVal, ubVal,
4482 stepVal, contextOp.getUntied(), ifCond, grainsize,
4483 effectiveNoGroup, sched,
4484 moduleTranslation.lookupValue(contextOp.getFinal()),
4485 contextOp.getMergeable(),
4486 moduleTranslation.lookupValue(contextOp.getPriority()),
4487 loopOp.getCollapseNumLoops(), taskDupOrNull,
4488 taskStructMgr.getStructPtr(),
4489 contextOp.getThreadset() == omp::ThreadsetPolicy::omp_pool);
4490
4491 if (failed(handleError(afterIP, opInst)))
4492 return failure();
4493
4494 popCancelFinalizationCB(cancelTerminators, ompBuilder,
4495 afterIP->getNodeParent());
4496
4497 builder.restoreIP(*afterIP);
4498
4499 // Close the implicit taskgroup we opened for task_reduction. The end call
4500 // must execute on the encountering thread, so use the outer-scope gtid.
4501 if (implicitTaskgroup) {
4502 llvm::OpenMPIRBuilder::LocationDescription endLoc(builder);
4503 uint32_t srcLocSize;
4504 llvm::Constant *srcLocStr =
4505 ompBuilder.getOrCreateSrcLocStr(endLoc, srcLocSize);
4506 llvm::Value *ident = ompBuilder.getOrCreateIdent(srcLocStr, srcLocSize);
4507 // Align OpenMPIRBuilder's internal IRBuilder with `builder` so the
4508 // gtid call lands at our insertion point.
4509 ompBuilder.updateToLocation(endLoc);
4510 llvm::Value *outerGtid = ompBuilder.getOrCreateThreadID(ident);
4511 llvm::FunctionCallee endTgFn = ompBuilder.getOrCreateRuntimeFunction(
4512 *moduleTranslation.getLLVMModule(),
4513 llvm::omp::OMPRTL___kmpc_end_taskgroup);
4514 builder.CreateCall(endTgFn, {ident, outerGtid});
4515 }
4516 return success();
4517}
4518
4519/// Build an outlined init helper for a task_reduction declare_reduction op.
4520/// Signature: void(ptr %priv, ptr %orig). For non-byref reductions, the init
4521/// region's mold argument is mapped following the same rule as the regular
4522/// reduction path (`mapInitializationArgs`): a non-pointer mold loads the
4523/// value from %orig, while a pointer-typed mold receives %orig directly. The
4524/// yielded value is stored into %priv.
4525static llvm::Function *
4526emitTaskReductionInitFn(omp::DeclareReductionOp decl, StringRef baseName,
4527 LLVM::ModuleTranslation &moduleTranslation) {
4528 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4529 llvm::LLVMContext &ctx = llvmModule->getContext();
4530 llvm::Type *voidTy = llvm::Type::getVoidTy(ctx);
4531 llvm::Type *ptrTy = llvm::PointerType::getUnqual(ctx);
4532 llvm::FunctionType *fty =
4533 llvm::FunctionType::get(voidTy, {ptrTy, ptrTy}, false);
4534 llvm::Function *fn =
4535 llvm::Function::Create(fty, llvm::GlobalValue::InternalLinkage,
4536 baseName + ".red.init", llvmModule);
4537 fn->setDoesNotRecurse();
4538 fn->getArg(0)->setName("priv");
4539 fn->getArg(1)->setName("orig");
4540
4541 llvm::BasicBlock *entry = llvm::BasicBlock::Create(ctx, "entry", fn);
4542 llvm::IRBuilder<> b(entry);
4543
4544 // Map the initializer's mold argument the same way the regular reduction
4545 // path does in `mapInitializationArgs`: only load the original value when a
4546 // non-pointer mold is expected. For a pointer-typed mold the storage pointer
4547 // (%orig) is passed through directly, so a mold-yielding initializer lowers
4548 // to `store ptr %orig, ptr %priv` rather than emitting a spurious load.
4549 Value moldArg = decl.getInitializerMoldArg();
4550 llvm::Value *origVal = fn->getArg(1);
4551 if (!isa<LLVM::LLVMPointerType>(moldArg.getType()))
4552 origVal = b.CreateLoad(moduleTranslation.convertType(moldArg.getType()),
4553 fn->getArg(1), "omp.orig");
4554 moduleTranslation.mapValue(moldArg, origVal);
4556 if (failed(inlineConvertOmpRegions(decl.getInitializerRegion(),
4557 "omp.taskred.init", b, moduleTranslation,
4558 &phis))) {
4559 fn->eraseFromParent();
4560 return nullptr;
4561 }
4562 assert(phis.size() == 1 &&
4563 "expected one value yielded from reduction initializer");
4564 b.CreateStore(phis[0], fn->getArg(0));
4565 b.CreateRetVoid();
4566
4567 moduleTranslation.forgetMapping(decl.getInitializerRegion());
4568 return fn;
4569}
4570
4571/// Build an outlined combiner helper for a task_reduction declare_reduction op.
4572/// Signature: void(ptr %lhs, ptr %rhs). For non-byref reductions, the values
4573/// at *%lhs and *%rhs are loaded, fed into the combiner region, and the
4574/// yielded scalar is stored back into *%lhs.
4575static llvm::Function *
4576emitTaskReductionCombFn(omp::DeclareReductionOp decl, StringRef baseName,
4577 LLVM::ModuleTranslation &moduleTranslation) {
4578 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4579 llvm::LLVMContext &ctx = llvmModule->getContext();
4580 llvm::Type *voidTy = llvm::Type::getVoidTy(ctx);
4581 llvm::Type *ptrTy = llvm::PointerType::getUnqual(ctx);
4582 llvm::FunctionType *fty =
4583 llvm::FunctionType::get(voidTy, {ptrTy, ptrTy}, false);
4584 llvm::Function *fn =
4585 llvm::Function::Create(fty, llvm::GlobalValue::InternalLinkage,
4586 baseName + ".red.comb", llvmModule);
4587 fn->setDoesNotRecurse();
4588 fn->getArg(0)->setName("lhs");
4589 fn->getArg(1)->setName("rhs");
4590
4591 llvm::BasicBlock *entry = llvm::BasicBlock::Create(ctx, "entry", fn);
4592 llvm::IRBuilder<> b(entry);
4593
4594 llvm::Type *elemTy = moduleTranslation.convertType(decl.getType());
4595 Block &combBlock = decl.getReductionRegion().front();
4596 assert(combBlock.getNumArguments() == 2 &&
4597 "expected two arguments in declare_reduction combiner");
4598 llvm::Value *lhsVal = b.CreateLoad(elemTy, fn->getArg(0), "omp.lhs");
4599 llvm::Value *rhsVal = b.CreateLoad(elemTy, fn->getArg(1), "omp.rhs");
4600 moduleTranslation.mapValue(combBlock.getArgument(0), lhsVal);
4601 moduleTranslation.mapValue(combBlock.getArgument(1), rhsVal);
4602
4604 if (failed(inlineConvertOmpRegions(decl.getReductionRegion(),
4605 "omp.taskred.comb", b, moduleTranslation,
4606 &phis))) {
4607 fn->eraseFromParent();
4608 return nullptr;
4609 }
4610 assert(phis.size() == 1 &&
4611 "expected one value yielded from reduction combiner");
4612 b.CreateStore(phis[0], fn->getArg(0));
4613 b.CreateRetVoid();
4614
4615 moduleTranslation.forgetMapping(decl.getReductionRegion());
4616 return fn;
4617}
4618
4619/// Emit the per-taskgroup task_reduction descriptor array and the
4620/// `__kmpc_taskred_init` runtime call. \p origPtrs holds the LLVM values for
4621/// the original (shared) variables, one per declaration in \p redDecls.
4622/// `builder` must be set to the point at which the descriptor stores and the
4623/// init call should be emitted; the descriptor array itself is allocated at
4624/// \p allocaIP. \p helperNamePrefix is used to disambiguate the generated
4625/// init/combiner helper symbol names between taskgroup and taskloop callers.
4626///
4627/// When \p isModifier is false, emits `__kmpc_taskred_init` and returns the
4628/// `ptr` value it produces (the taskgroup reduction handle). When \p isModifier
4629/// is true, emits `__kmpc_taskred_modifier_init` instead to open a
4630/// task-reduction scope for a parallel or worksharing construct, passing
4631/// \p isWorksharing as the runtime `is_ws` argument. Returns null on failure.
4632///
4633/// Only the non-byref form is handled here. Byref reductions have already
4634/// been rejected by `checkImplementationStatus`.
4635static llvm::Value *emitTaskReductionInitCall(
4637 ArrayRef<llvm::Value *> origPtrs, StringRef helperNamePrefix,
4638 llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder::InsertPointTy allocaIP,
4639 LLVM::ModuleTranslation &moduleTranslation, bool isModifier,
4640 bool isWorksharing) {
4641 assert(redDecls.size() == origPtrs.size() &&
4642 "expected one orig pointer per reduction decl");
4643 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
4644 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4645 llvm::LLVMContext &ctx = llvmModule->getContext();
4646 const llvm::DataLayout &dl = llvmModule->getDataLayout();
4647
4648 llvm::Type *ptrTy = llvm::PointerType::getUnqual(ctx);
4649 llvm::Type *i32Ty = llvm::Type::getInt32Ty(ctx);
4650 llvm::Type *sizeTy =
4651 llvm::Type::getIntNTy(ctx, dl.getPointerSizeInBits(/*AddrSpace=*/0));
4652
4653 // Identified `kmp_taskred_input_t` struct, matching the layout used by
4654 // Clang's CGOpenMPRuntime::emitTaskReductionInit.
4655 llvm::StructType *redInputTy =
4656 llvm::StructType::getTypeByName(ctx, "kmp_taskred_input_t");
4657 if (!redInputTy)
4658 redInputTy = llvm::StructType::create(
4659 ctx, {ptrTy, ptrTy, sizeTy, ptrTy, ptrTy, ptrTy, i32Ty},
4660 "kmp_taskred_input_t");
4661
4662 unsigned n = redDecls.size();
4663 llvm::ArrayType *arrTy = llvm::ArrayType::get(redInputTy, n);
4664
4665 // Allocate the descriptor array in the enclosing function's alloca block.
4666 llvm::AllocaInst *arrAlloca;
4667 {
4668 llvm::IRBuilderBase::InsertPointGuard guard(builder);
4669 builder.restoreIP(allocaIP);
4670 arrAlloca =
4671 builder.CreateAlloca(arrTy, /*ArraySize=*/nullptr, ".taskred.input");
4672 }
4673
4674 // Fill each descriptor entry at the current builder insertion point.
4675 llvm::Value *zero = builder.getInt32(0);
4676 for (unsigned i = 0; i < n; ++i) {
4677 omp::DeclareReductionOp decl = redDecls[i];
4678 llvm::Value *orig = origPtrs[i];
4679 if (auto *origPtrTy = llvm::dyn_cast<llvm::PointerType>(orig->getType());
4680 origPtrTy && origPtrTy->getAddressSpace() != 0)
4681 orig = builder.CreateAddrSpaceCast(orig, ptrTy);
4682 llvm::Type *elemTy = moduleTranslation.convertType(decl.getType());
4683 uint64_t size = dl.getTypeAllocSize(elemTy).getFixedValue();
4684
4685 std::string baseName =
4686 (llvm::Twine(helperNamePrefix) + decl.getSymName()).str();
4687 llvm::Function *initFn =
4688 emitTaskReductionInitFn(decl, baseName, moduleTranslation);
4689 llvm::Function *combFn =
4690 emitTaskReductionCombFn(decl, baseName, moduleTranslation);
4691 if (!initFn || !combFn)
4692 return nullptr;
4693 llvm::Value *elemPtr = builder.CreateInBoundsGEP(
4694 arrTy, arrAlloca, {zero, builder.getInt32(i)}, ".taskred.elem");
4695 auto storeField = [&](unsigned fieldIdx, llvm::Value *val) {
4696 llvm::Value *fieldPtr =
4697 builder.CreateStructGEP(redInputTy, elemPtr, fieldIdx);
4698 builder.CreateStore(val, fieldPtr);
4699 };
4700 storeField(0, orig); // reduce_shar
4701 storeField(1, orig); // reduce_orig
4702 storeField(2, llvm::ConstantInt::get(sizeTy, size)); // reduce_size
4703 storeField(3, initFn); // reduce_init
4704 storeField(4, llvm::ConstantPointerNull::get(ptrTy)); // reduce_fini
4705 storeField(5, combFn); // reduce_comb
4706 storeField(6, llvm::ConstantInt::get(i32Ty, 0)); // flags
4707 }
4708
4709 // Emit the runtime call that registers the task reduction data.
4710 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4711 uint32_t srcLocSize;
4712 llvm::Constant *srcLocStr =
4713 ompBuilder->getOrCreateSrcLocStr(ompLoc, srcLocSize);
4714 llvm::Value *ident = ompBuilder->getOrCreateIdent(srcLocStr, srcLocSize);
4715 ompBuilder->updateToLocation(ompLoc);
4716 llvm::Value *gtid = ompBuilder->getOrCreateThreadID(ident);
4717 if (isModifier) {
4718 // __kmpc_taskred_modifier_init(loc, gtid, is_ws, num, &arr) opens a
4719 // task-reduction scope for the enclosing parallel/worksharing region.
4720 llvm::FunctionCallee modInit = ompBuilder->getOrCreateRuntimeFunction(
4721 *llvmModule, llvm::omp::OMPRTL___kmpc_taskred_modifier_init);
4722 return builder.CreateCall(modInit,
4723 {ident, gtid,
4724 builder.getInt32(isWorksharing ? 1 : 0),
4725 builder.getInt32(n), arrAlloca},
4726 ".taskred.desc");
4727 }
4728 // __kmpc_taskred_init(gtid, num, &arr).
4729 llvm::FunctionCallee taskredInit = ompBuilder->getOrCreateRuntimeFunction(
4730 *llvmModule, llvm::omp::OMPRTL___kmpc_taskred_init);
4731 return builder.CreateCall(taskredInit, {gtid, builder.getInt32(n), arrAlloca},
4732 ".taskred.desc");
4733}
4734
4735/// Emits `__kmpc_task_reduction_modifier_fini(loc, gtid, is_ws)` at the current
4736/// builder insertion point, closing the task-reduction scope opened by the
4737/// `task` reduction modifier on a parallel or worksharing construct.
4738static void
4739emitTaskReductionModifierFini(bool isWorksharing, llvm::IRBuilderBase &builder,
4740 LLVM::ModuleTranslation &moduleTranslation) {
4741 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
4742 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
4743 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4744 uint32_t srcLocSize;
4745 llvm::Constant *srcLocStr =
4746 ompBuilder->getOrCreateSrcLocStr(ompLoc, srcLocSize);
4747 llvm::Value *ident = ompBuilder->getOrCreateIdent(srcLocStr, srcLocSize);
4748 ompBuilder->updateToLocation(ompLoc);
4749 llvm::Value *gtid = ompBuilder->getOrCreateThreadID(ident);
4750 llvm::FunctionCallee fini = ompBuilder->getOrCreateRuntimeFunction(
4751 *llvmModule, llvm::omp::OMPRTL___kmpc_task_reduction_modifier_fini);
4752 builder.CreateCall(fini,
4753 {ident, gtid, builder.getInt32(isWorksharing ? 1 : 0)});
4754}
4755
4756/// Converts an OpenMP taskgroup construct into LLVM IR using OpenMPIRBuilder.
4757static LogicalResult
4758convertOmpTaskgroupOp(omp::TaskgroupOp tgOp, llvm::IRBuilderBase &builder,
4759 LLVM::ModuleTranslation &moduleTranslation) {
4760 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
4761 if (failed(checkImplementationStatus(*tgOp)))
4762 return failure();
4763
4764 // Resolve and validate task_reduction declarations up front. We only handle
4765 // declare_reduction ops shaped like a non-byref scalar reduction in this
4766 // first cut; richer shapes (two-argument initializer, cleanup region,
4767 // missing combiner) require additional infrastructure.
4769 if (auto syms = tgOp.getTaskReductionSyms()) {
4770 redDecls.reserve(syms->size());
4771 for (auto sym : syms->getAsRange<SymbolRefAttr>()) {
4773 tgOp, sym);
4774 if (!decl)
4775 return tgOp.emitError()
4776 << "failed to resolve task_reduction declare_reduction symbol "
4777 << sym.getRootReference() << " in omp.taskgroup";
4778 if (decl.getInitializerRegion().front().getNumArguments() != 1)
4779 return tgOp.emitError("not yet implemented: task_reduction with "
4780 "two-argument initializer in omp.taskgroup");
4781 if (!decl.getCleanupRegion().empty())
4782 return tgOp.emitError("not yet implemented: task_reduction with "
4783 "cleanup region in omp.taskgroup");
4784 if (decl.getReductionRegion().empty())
4785 return tgOp.emitError("task_reduction declare_reduction is missing a "
4786 "combiner region");
4787 redDecls.push_back(decl);
4788 }
4789 }
4790
4791 auto bodyCB =
4792 [&](InsertPointTy allocaIP, InsertPointTy codegenIP,
4793 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
4794 builder.restoreIP(codegenIP);
4795
4796 if (!redDecls.empty()) {
4798 origPtrs.reserve(redDecls.size());
4799 for (Value v : tgOp.getTaskReductionVars())
4800 origPtrs.push_back(moduleTranslation.lookupValue(v));
4801 if (!emitTaskReductionInitCall(redDecls, origPtrs, "__omp_taskred_",
4802 builder, allocaIP, moduleTranslation))
4803 return llvm::createStringError(
4804 llvm::inconvertibleErrorCode(),
4805 "failed to emit task_reduction initialization for omp.taskgroup");
4806 }
4807
4808 // Inside the taskgroup body, each task_reduction block argument refers to
4809 // the same shared/original storage that the runtime now knows about via
4810 // the descriptor array. Inner tasks that declare in_reduction look up
4811 // per-task private copies through the runtime; the taskgroup body itself
4812 // uses the original variable.
4813 for (auto [i, blockArg] :
4814 llvm::enumerate(tgOp.getRegion().getArguments())) {
4815 llvm::Value *orig =
4816 moduleTranslation.lookupValue(tgOp.getTaskReductionVars()[i]);
4817 moduleTranslation.mapValue(blockArg, orig);
4818 }
4819
4820 return convertOmpOpRegions(tgOp.getRegion(), "omp.taskgroup.region",
4821 builder, moduleTranslation)
4822 .takeError();
4823 };
4824
4826 InsertPointTy allocaIP =
4827 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
4828 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
4829 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
4830 getOpenMPBuilder(moduleTranslation)
4831 ->createTaskgroup(ompLoc, allocaIP, deallocBlocks, bodyCB);
4832
4833 if (failed(handleError(afterIP, *tgOp)))
4834 return failure();
4835
4836 builder.restoreIP(*afterIP);
4837 return success();
4838}
4839
4840static LogicalResult
4841convertOmpInteropInitOp(omp::InteropInitOp initOp, llvm::IRBuilderBase &builder,
4842 LLVM::ModuleTranslation &moduleTranslation) {
4843 if (!initOp.getDependVars().empty() || initOp.getDependKinds() ||
4844 !initOp.getDependIterated().empty() || initOp.getDependIteratedKinds())
4845 return initOp.emitError()
4846 << "not yet implemented: Unhandled clause depend in "
4847 << omp::InteropInitOp::getOperationName() << " operation";
4848
4849 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
4850 llvm::Value *interopVar =
4851 moduleTranslation.lookupValue(initOp.getInteropVar());
4852 llvm::Value *device = initOp.getDevice()
4853 ? moduleTranslation.lookupValue(initOp.getDevice())
4854 : nullptr;
4855
4856 // TODO: Handle depend clauses when supported.
4857 llvm::Value *numDeps = llvm::ConstantInt::get(builder.getInt32Ty(), 0);
4858 llvm::Value *depArray = llvm::ConstantPointerNull::get(builder.getPtrTy());
4859 bool hasNowait = initOp.getNowait();
4860
4861 // A single `init` clause may list both `target` and `targetsync`, but the
4862 // runtime init call takes a single interop-type. Collapse the set to one
4863 // value, matching Clang: if `target` is present use Target, otherwise
4864 // TargetSync. The offload runtime object model supports only one type per
4865 // object; representing both would require a runtime change.
4866 bool hasTarget = false, hasTargetSync = false;
4867 for (mlir::Attribute typeAttr : initOp.getInteropTypes()) {
4868 switch (cast<omp::InteropTypeAttr>(typeAttr).getValue()) {
4869 case omp::InteropType::target:
4870 hasTarget = true;
4871 break;
4872 case omp::InteropType::targetsync:
4873 hasTargetSync = true;
4874 break;
4875 }
4876 }
4877 llvm::omp::OMPInteropType interopType =
4878 (!hasTarget && hasTargetSync) ? llvm::omp::OMPInteropType::TargetSync
4879 : llvm::omp::OMPInteropType::Target;
4880 ompBuilder->createOMPInteropInit(builder, interopVar, interopType, device,
4881 numDeps, depArray, hasNowait);
4882 return success();
4883}
4884
4885static LogicalResult
4886convertOmpInteropDestroyOp(omp::InteropDestroyOp destroyOp,
4887 llvm::IRBuilderBase &builder,
4888 LLVM::ModuleTranslation &moduleTranslation) {
4889 if (!destroyOp.getDependVars().empty() || destroyOp.getDependKinds() ||
4890 !destroyOp.getDependIterated().empty() ||
4891 destroyOp.getDependIteratedKinds())
4892 return destroyOp.emitError()
4893 << "not yet implemented: Unhandled clause depend in "
4894 << omp::InteropDestroyOp::getOperationName() << " operation";
4895
4896 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
4897 llvm::Value *interopVar =
4898 moduleTranslation.lookupValue(destroyOp.getInteropVar());
4899 llvm::Value *device =
4900 destroyOp.getDevice()
4901 ? moduleTranslation.lookupValue(destroyOp.getDevice())
4902 : nullptr;
4903
4904 llvm::Value *numDeps = llvm::ConstantInt::get(builder.getInt32Ty(), 0);
4905 llvm::Value *depArray = llvm::ConstantPointerNull::get(builder.getPtrTy());
4906 bool hasNowait = destroyOp.getNowait();
4907
4908 ompBuilder->createOMPInteropDestroy(builder, interopVar, device, numDeps,
4909 depArray, hasNowait);
4910 return success();
4911}
4912
4913static LogicalResult
4914convertOmpInteropUseOp(omp::InteropUseOp useOp, llvm::IRBuilderBase &builder,
4915 LLVM::ModuleTranslation &moduleTranslation) {
4916 if (!useOp.getDependVars().empty() || useOp.getDependKinds() ||
4917 !useOp.getDependIterated().empty() || useOp.getDependIteratedKinds())
4918 return useOp.emitError()
4919 << "not yet implemented: Unhandled clause depend in "
4920 << omp::InteropUseOp::getOperationName() << " operation";
4921
4922 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
4923 llvm::Value *interopVar =
4924 moduleTranslation.lookupValue(useOp.getInteropVar());
4925 llvm::Value *device = useOp.getDevice()
4926 ? moduleTranslation.lookupValue(useOp.getDevice())
4927 : nullptr;
4928
4929 llvm::Value *numDeps = llvm::ConstantInt::get(builder.getInt32Ty(), 0);
4930 llvm::Value *depArray = llvm::ConstantPointerNull::get(builder.getPtrTy());
4931 bool hasNowait = useOp.getNowait();
4932
4933 ompBuilder->createOMPInteropUse(builder, interopVar, device, numDeps,
4934 depArray, hasNowait);
4935 return success();
4936}
4937
4938static LogicalResult
4939convertOmpTaskwaitOp(omp::TaskwaitOp twOp, llvm::IRBuilderBase &builder,
4940 LLVM::ModuleTranslation &moduleTranslation) {
4941 if (failed(checkImplementationStatus(*twOp)))
4942 return failure();
4943
4944 llvm::OpenMPIRBuilder::DependenciesInfo dds;
4945 if (failed(buildDependData(
4946 twOp.getDependVars(), twOp.getDependKinds(), twOp.getDependIterated(),
4947 twOp.getDependIteratedKinds(), builder, moduleTranslation, dds))) {
4948 return failure();
4949 }
4950
4951 getOpenMPBuilder(moduleTranslation)
4952 ->createTaskwait(builder, dds, twOp.getNowait());
4953 if (dds.DepArray) {
4954 builder.CreateFree(dds.DepArray);
4955 }
4956
4957 return success();
4958}
4959
4960/// Converts an OpenMP workshare loop into LLVM IR using OpenMPIRBuilder.
4961static LogicalResult
4962convertOmpWsloop(Operation &opInst, llvm::IRBuilderBase &builder,
4963 LLVM::ModuleTranslation &moduleTranslation) {
4964 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
4965 auto wsloopOp = cast<omp::WsloopOp>(opInst);
4966 if (failed(checkImplementationStatus(opInst)))
4967 return failure();
4968
4969 auto loopOp = cast<omp::LoopNestOp>(wsloopOp.getWrappedLoop());
4970 llvm::ArrayRef<bool> isByRef = getIsByRef(wsloopOp.getReductionByref());
4971 assert(isByRef.size() == wsloopOp.getNumReductionVars());
4972
4973 // Static is the default.
4974 auto schedule =
4975 wsloopOp.getScheduleKind().value_or(omp::ClauseScheduleKind::Static);
4976
4977 // Find the loop configuration.
4978 llvm::Value *step = moduleTranslation.lookupValue(loopOp.getLoopSteps()[0]);
4979 llvm::Type *ivType = step->getType();
4980 llvm::Value *chunk = nullptr;
4981 if (wsloopOp.getScheduleChunk()) {
4982 llvm::Value *chunkVar =
4983 moduleTranslation.lookupValue(wsloopOp.getScheduleChunk());
4984 chunk = builder.CreateSExtOrTrunc(chunkVar, ivType);
4985 }
4986
4987 omp::DistributeOp distributeOp = nullptr;
4988 llvm::Value *distScheduleChunk = nullptr;
4989 bool hasDistSchedule = false;
4990 if (llvm::isa_and_present<omp::DistributeOp>(opInst.getParentOp())) {
4991 distributeOp = cast<omp::DistributeOp>(opInst.getParentOp());
4992 hasDistSchedule = distributeOp.getDistScheduleStatic();
4993 if (distributeOp.getDistScheduleChunkSize()) {
4994 llvm::Value *chunkVar = moduleTranslation.lookupValue(
4995 distributeOp.getDistScheduleChunkSize());
4996 distScheduleChunk = builder.CreateSExtOrTrunc(chunkVar, ivType);
4997 }
4998 }
4999
5000 PrivateVarsInfo privateVarsInfo(wsloopOp);
5001 privateVarsInfo.addLinearVars(wsloopOp);
5002
5004 collectReductionDecls(wsloopOp, reductionDecls);
5005
5006 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
5007 findAllocInsertPoints(builder, moduleTranslation);
5008
5009 llvm::BasicBlock *allocaBlock = allocaIP.getNodeParent();
5010 SmallVector<llvm::Value *> privateReductionVariables(
5011 wsloopOp.getNumReductionVars());
5012
5014 wsloopOp, builder, moduleTranslation, privateVarsInfo, allocaIP);
5015 if (handleError(afterAllocas, opInst).failed())
5016 return failure();
5017
5018 DenseMap<Value, llvm::Value *> reductionVariableMap;
5019
5020 MutableArrayRef<BlockArgument> reductionArgs =
5021 cast<omp::BlockArgOpenMPOpInterface>(opInst).getReductionBlockArgs();
5022
5023 SmallVector<DeferredStore> deferredStores;
5024
5025 if (failed(allocReductionVars(wsloopOp, reductionArgs, builder,
5026 moduleTranslation, allocaIP, reductionDecls,
5027 privateReductionVariables, reductionVariableMap,
5028 deferredStores, isByRef)))
5029 return failure();
5030
5031 if (handleError(initPrivateVars(builder, moduleTranslation, privateVarsInfo),
5032 opInst)
5033 .failed())
5034 return failure();
5035
5036 if (failed(completePrivateVars(
5037 wsloopOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
5038 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
5039 wsloopOp.getPrivateNeedsBarrier())))
5040 return failure();
5041
5042 assert(afterAllocas.get()->getSinglePredecessor());
5043 if (failed(initReductionVars(wsloopOp, reductionArgs, builder,
5044 moduleTranslation,
5045 afterAllocas.get()->getSinglePredecessor(),
5046 reductionDecls, privateReductionVariables,
5047 reductionVariableMap, isByRef, deferredStores)))
5048 return failure();
5049
5050 // For `reduction(task, ...)` open a task-reduction scope for the worksharing
5051 // loop. Participating explicit tasks accumulate into the per-thread private
5052 // copies, which the worksharing reduction then combines across threads.
5053 bool isTaskReductionMod =
5054 wsloopOp.hasReductionModifier(omp::ReductionModifier::task);
5055 if (isTaskReductionMod &&
5057 wsloopOp, reductionDecls, privateReductionVariables, builder,
5058 allocaIP, moduleTranslation, /*isWorksharing=*/true))
5059 return wsloopOp.emitError(
5060 "failed to emit task reduction modifier initialization");
5061
5062 // TODO: Handle doacross loops when the ordered clause has a parameter.
5063 bool isOrdered = wsloopOp.getOrdered().has_value();
5064 std::optional<omp::ScheduleModifier> scheduleMod = wsloopOp.getScheduleMod();
5065 bool isSimd = wsloopOp.getScheduleSimd();
5066 bool loopNeedsBarrier = !wsloopOp.getNowait();
5067
5068 // The only legal way for the direct parent to be omp.distribute is that this
5069 // represents 'distribute parallel do'. Otherwise, this is a regular
5070 // worksharing loop.
5071 llvm::omp::WorksharingLoopType workshareLoopType =
5072 llvm::isa_and_present<omp::DistributeOp>(opInst.getParentOp())
5073 ? llvm::omp::WorksharingLoopType::DistributeForStaticLoop
5074 : llvm::omp::WorksharingLoopType::ForStaticLoop;
5075
5076 SmallVector<llvm::UncondBrInst *> cancelTerminators;
5077 pushCancelFinalizationCB(cancelTerminators, builder, *ompBuilder, wsloopOp,
5078 llvm::omp::Directive::OMPD_for);
5079
5080 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5081
5082 // Initialize linear variables and linear step
5083 LinearClauseProcessor linearClauseProcessor;
5084
5085 if (!wsloopOp.getLinearVars().empty()) {
5086 auto linearVarTypes = wsloopOp.getLinearVarTypes().value();
5087 for (mlir::Attribute linearVarType : linearVarTypes)
5088 linearClauseProcessor.registerType(moduleTranslation, linearVarType);
5089
5090 for (auto [idx, linearVar] : llvm::enumerate(wsloopOp.getLinearVars())) {
5091 size_t privIdx = privateVarsInfo.numPrivateVars + idx;
5092 linearClauseProcessor.createLinearVar(
5093 builder, allocaIP, privateVarsInfo.privatizers[privIdx],
5094 moduleTranslation.lookupValue(linearVar),
5095 privateVarsInfo.llvmVars[privIdx], idx);
5096 }
5097 for (mlir::Value linearStep : wsloopOp.getLinearStepVars())
5098 linearClauseProcessor.initLinearStep(moduleTranslation, linearStep);
5099 }
5100
5102 wsloopOp.getRegion(), "omp.wsloop.region", builder, moduleTranslation);
5103
5104 if (failed(handleError(regionBlock, opInst)))
5105 return failure();
5106
5107 llvm::CanonicalLoopInfo *loopInfo = findCurrentLoopInfo(moduleTranslation);
5108
5109 // Emit Initialization and Update IR for linear variables
5110 if (!wsloopOp.getLinearVars().empty()) {
5111 if (failed(handleError(
5112 linearClauseProcessor.initLinearVar(builder, moduleTranslation,
5113 loopInfo->getPreheader()),
5114 opInst)))
5115 return failure();
5116 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterBarrierIP =
5117 getOpenMPBuilder(moduleTranslation)
5118 ->createBarrier(builder, llvm::omp::OMPD_barrier);
5119 if (failed(handleError(afterBarrierIP, *loopOp)))
5120 return failure();
5121 builder.restoreIP(*afterBarrierIP);
5122 if (failed(handleError(linearClauseProcessor.updateLinearVar(
5123 builder, moduleTranslation, loopInfo->getBody(),
5124 loopInfo->getIndVar()),
5125 opInst)))
5126 return failure();
5127 linearClauseProcessor.splitLinearFiniBB(builder, loopInfo->getExit());
5128 }
5129
5130 builder.SetInsertPoint((*regionBlock)->begin());
5131
5132 // Check if we can generate no-loop kernel
5133 bool noLoopMode = false;
5134 omp::TargetOp targetOp = wsloopOp->getParentOfType<mlir::omp::TargetOp>();
5135 if (targetOp &&
5136 targetOp.getKernelType() == omp::TargetExecMode::spmd_no_loop) {
5137 Operation *targetCapturedOp =
5138 cast<omp::ComposableOpInterface>(*targetOp).findCapturedOp();
5139 // We need this check because, without it, noLoopMode would be set to true
5140 // for every omp.wsloop nested inside a no-loop SPMD target region, even if
5141 // that loop is not the top-level SPMD one.
5142 if (loopOp == targetCapturedOp)
5143 noLoopMode = true;
5144 }
5145
5146 // Nested regions can move the instruction referenced by allocaIP.
5147 allocaIP = allocaBlock->getFirstInsertionPt();
5148 llvm::OpenMPIRBuilder::InsertPointOrErrorTy wsloopIP =
5149 ompBuilder->applyWorkshareLoop(
5150 ompLoc.DL, loopInfo, allocaIP, loopNeedsBarrier,
5151 convertToScheduleKind(schedule), chunk, isSimd,
5152 scheduleMod == omp::ScheduleModifier::monotonic,
5153 scheduleMod == omp::ScheduleModifier::nonmonotonic, isOrdered,
5154 workshareLoopType, noLoopMode, hasDistSchedule, distScheduleChunk,
5155 !wsloopOp.getLinearVars().empty());
5156
5157 if (failed(handleError(wsloopIP, opInst)))
5158 return failure();
5159
5160 // Save the continuation block before linear var finalization, which may
5161 // invalidate the wsloopIP iterator.
5162 llvm::BasicBlock *wsloopContinuationBB = wsloopIP->getNodeParent();
5163
5164 // Emit finalization and in-place rewrites for linear vars.
5165 if (!wsloopOp.getLinearVars().empty()) {
5166 llvm::OpenMPIRBuilder::InsertPointTy oldIP = builder.saveIP();
5167 assert(loopInfo->getLastIter() &&
5168 "`lastiter` in CanonicalLoopInfo is nullptr");
5169 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterBarrierIP =
5170 linearClauseProcessor.finalizeLinearVar(builder, moduleTranslation,
5171 loopInfo->getLastIter());
5172 if (failed(handleError(afterBarrierIP, *loopOp)))
5173 return failure();
5174
5175 builder.restoreIP(oldIP);
5176 }
5177
5178 // Set the correct branch target for task cancellation
5179 popCancelFinalizationCB(cancelTerminators, *ompBuilder, wsloopContinuationBB);
5180
5181 // Close the task-reduction scope before the worksharing reduction combine.
5182 if (isTaskReductionMod)
5183 emitTaskReductionModifierFini(/*isWorksharing=*/true, builder,
5184 moduleTranslation);
5185
5186 // Process the reductions if required.
5187 if (failed(createReductionsAndCleanup(
5188 wsloopOp, builder, moduleTranslation, allocaIP, reductionDecls,
5189 privateReductionVariables, isByRef, wsloopOp.getNowait(),
5190 /*isTeamsReduction=*/false)))
5191 return failure();
5192
5193 return cleanupPrivateVars(wsloopOp, builder, moduleTranslation,
5194 wsloopOp.getLoc(), privateVarsInfo);
5195}
5196
5197/// Converts the OpenMP parallel operation to LLVM IR.
5198static LogicalResult
5199convertOmpParallel(omp::ParallelOp opInst, llvm::IRBuilderBase &builder,
5200 LLVM::ModuleTranslation &moduleTranslation) {
5201 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
5202 ArrayRef<bool> isByRef = getIsByRef(opInst.getReductionByref());
5203 assert(isByRef.size() == opInst.getNumReductionVars());
5204 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
5205 bool isCancellable = constructIsCancellable(opInst);
5206
5207 if (failed(checkImplementationStatus(*opInst)))
5208 return failure();
5209
5210 PrivateVarsInfo privateVarsInfo(opInst);
5211 if (failed(convertAllocatorVars(*opInst, opInst.getAllocatorVars(), builder,
5212 moduleTranslation, privateVarsInfo)))
5213 return failure();
5214
5215 // Collect reduction declarations
5217 collectReductionDecls(opInst, reductionDecls);
5218 SmallVector<llvm::Value *> privateReductionVariables(
5219 opInst.getNumReductionVars());
5220 SmallVector<DeferredStore> deferredStores;
5221 // Only open a task-reduction scope when the `task` modifier is present and
5222 // there are reduction variables to combine; otherwise the matching fini in
5223 // the reduction-combine path (guarded by getNumReductionVars() > 0) would be
5224 // skipped, leaving the modifier init unbalanced.
5225 bool isTaskReductionMod =
5226 opInst.hasReductionModifier(omp::ReductionModifier::task);
5227
5228 auto bodyGenCB =
5229 [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
5230 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
5232 opInst, builder, moduleTranslation, privateVarsInfo, allocaIP);
5233 if (handleError(afterAllocas, *opInst).failed())
5234 return llvm::make_error<PreviouslyReportedError>();
5235
5236 // Allocate reduction vars
5237 DenseMap<Value, llvm::Value *> reductionVariableMap;
5238
5239 MutableArrayRef<BlockArgument> reductionArgs =
5240 cast<omp::BlockArgOpenMPOpInterface>(*opInst).getReductionBlockArgs();
5241
5242 allocaIP = allocaIP.getNodeParent()->getTerminator()->getIterator();
5243
5244 if (failed(allocReductionVars(
5245 opInst, reductionArgs, builder, moduleTranslation, allocaIP,
5246 reductionDecls, privateReductionVariables, reductionVariableMap,
5247 deferredStores, isByRef)))
5248 return llvm::make_error<PreviouslyReportedError>();
5249
5250 assert(afterAllocas.get()->getSinglePredecessor());
5251 builder.restoreIP(codeGenIP);
5252
5253 if (handleError(
5254 initPrivateVars(builder, moduleTranslation, privateVarsInfo),
5255 *opInst)
5256 .failed())
5257 return llvm::make_error<PreviouslyReportedError>();
5258
5259 if (failed(completePrivateVars(
5260 opInst, builder, moduleTranslation, privateVarsInfo.mlirVars,
5261 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
5262 opInst.getPrivateNeedsBarrier())))
5263 return llvm::make_error<PreviouslyReportedError>();
5264
5265 if (failed(
5266 initReductionVars(opInst, reductionArgs, builder, moduleTranslation,
5267 afterAllocas.get()->getSinglePredecessor(),
5268 reductionDecls, privateReductionVariables,
5269 reductionVariableMap, isByRef, deferredStores)))
5270 return llvm::make_error<PreviouslyReportedError>();
5271
5272 // For `reduction(task, ...)` open a task-reduction scope so participating
5273 // explicit tasks accumulate into the per-thread private copies; the
5274 // parallel reduction then combines those copies across the team.
5275 if (isTaskReductionMod &&
5277 opInst, reductionDecls, privateReductionVariables, builder,
5278 allocaIP, moduleTranslation, /*isWorksharing=*/false))
5279 return llvm::createStringError(
5280 "failed to emit task reduction modifier initialization");
5281
5282 // Save the alloca insertion point on ModuleTranslation stack for use in
5283 // nested regions.
5285 moduleTranslation, allocaIP.getNodeParent(), deallocBlocks);
5286
5287 // ParallelOp has only one region associated with it.
5289 opInst.getRegion(), "omp.par.region", builder, moduleTranslation);
5290 if (!regionBlock)
5291 return regionBlock.takeError();
5292
5293 // Process the reductions if required.
5294 if (opInst.getNumReductionVars() > 0) {
5295 // Collect reduction info
5297 SmallVector<OwningAtomicReductionGen> owningAtomicReductionGens;
5299 owningReductionGenRefDataPtrGens;
5301 collectReductionInfo(opInst, builder, moduleTranslation, reductionDecls,
5302 owningReductionGens, owningAtomicReductionGens,
5303 owningReductionGenRefDataPtrGens,
5304 privateReductionVariables, reductionInfos, isByRef);
5305
5306 // Move to region cont block
5307 builder.SetInsertPoint((*regionBlock)->getTerminator());
5308
5309 // Close the task-reduction scope before the per-thread reduction
5310 // contributions are combined across the team.
5311 if (isTaskReductionMod)
5312 emitTaskReductionModifierFini(/*isWorksharing=*/false, builder,
5313 moduleTranslation);
5314
5315 // Generate reductions from info
5316 llvm::UnreachableInst *tempTerminator = builder.CreateUnreachable();
5317 builder.SetInsertPoint(tempTerminator);
5318
5319 llvm::OpenMPIRBuilder::InsertPointOrErrorTy contInsertPoint =
5320 ompBuilder->createReductions(builder, allocaIP, reductionInfos,
5321 isByRef,
5322 /*IsNoWait=*/false,
5323 /*IsTeamsReduction=*/false);
5324 if (!contInsertPoint)
5325 return contInsertPoint.takeError();
5326
5327 if (!contInsertPoint->isValid())
5328 return llvm::make_error<PreviouslyReportedError>();
5329
5330 tempTerminator->eraseFromParent();
5331 builder.restoreIP(*contInsertPoint);
5332 }
5333
5334 return llvm::Error::success();
5335 };
5336
5337 auto privCB = [](InsertPointTy allocaIP, InsertPointTy codeGenIP,
5338 llvm::Value &, llvm::Value &val, llvm::Value *&replVal) {
5339 // tell OpenMPIRBuilder not to do anything. We handled Privatisation in
5340 // bodyGenCB.
5341 replVal = &val;
5342 return codeGenIP;
5343 };
5344
5345 // TODO: Perform finalization actions for variables. This has to be
5346 // called for variables which have destructors/finalizers.
5347 auto finiCB = [&](InsertPointTy codeGenIP) -> llvm::Error {
5348 InsertPointTy oldIP = builder.saveIP();
5349 builder.restoreIP(codeGenIP);
5350
5351 // if the reduction has a cleanup region, inline it here to finalize the
5352 // reduction variables
5353 SmallVector<Region *> reductionCleanupRegions;
5354 llvm::transform(reductionDecls, std::back_inserter(reductionCleanupRegions),
5355 [](omp::DeclareReductionOp reductionDecl) {
5356 return &reductionDecl.getCleanupRegion();
5357 });
5358 if (failed(inlineOmpRegionCleanup(
5359 reductionCleanupRegions, privateReductionVariables,
5360 moduleTranslation, builder, "omp.reduction.cleanup")))
5361 return llvm::createStringError(
5362 "failed to inline `cleanup` region of `omp.declare_reduction`");
5363
5364 if (failed(cleanupPrivateVars(opInst, builder, moduleTranslation,
5365 opInst.getLoc(), privateVarsInfo)))
5366 return llvm::make_error<PreviouslyReportedError>();
5367
5368 // If we could be performing cancellation, add the cancellation barrier on
5369 // the way out of the outlined region.
5370 if (isCancellable) {
5371 auto IPOrErr = ompBuilder->createBarrier(
5372 llvm::OpenMPIRBuilder::LocationDescription(builder),
5373 llvm::omp::Directive::OMPD_unknown,
5374 /* ForceSimpleCall */ false,
5375 /* CheckCancelFlag */ false);
5376 if (!IPOrErr)
5377 return IPOrErr.takeError();
5378 }
5379
5380 builder.restoreIP(oldIP);
5381 return llvm::Error::success();
5382 };
5383
5384 llvm::Value *ifCond = nullptr;
5385 if (auto ifVar = opInst.getIfExpr())
5386 ifCond = moduleTranslation.lookupValue(ifVar);
5387 llvm::Value *numThreads = nullptr;
5388 if (!opInst.getNumThreadsVars().empty())
5389 numThreads = moduleTranslation.lookupValue(opInst.getNumThreads(0));
5390 auto pbKind = llvm::omp::OMP_PROC_BIND_default;
5391 if (auto bind = opInst.getProcBindKind())
5392 pbKind = getProcBindKind(*bind);
5393
5395 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
5396 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
5397 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5398
5399 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
5400 ompBuilder->createParallel(ompLoc, allocaIP, deallocBlocks, bodyGenCB,
5401 privCB, finiCB, ifCond, numThreads, pbKind,
5402 isCancellable);
5403
5404 if (failed(handleError(afterIP, *opInst)))
5405 return failure();
5406
5407 builder.restoreIP(*afterIP);
5408 return success();
5409}
5410
5411/// Convert Order attribute to llvm::omp::OrderKind.
5412static llvm::omp::OrderKind
5413convertOrderKind(std::optional<omp::ClauseOrderKind> o) {
5414 if (!o)
5415 return llvm::omp::OrderKind::OMP_ORDER_unknown;
5416 switch (*o) {
5417 case omp::ClauseOrderKind::Concurrent:
5418 return llvm::omp::OrderKind::OMP_ORDER_concurrent;
5419 }
5420 llvm_unreachable("Unknown ClauseOrderKind kind");
5421}
5422
5423/// Converts an OpenMP simd loop into LLVM IR using OpenMPIRBuilder.
5424static LogicalResult
5425convertOmpSimd(Operation &opInst, llvm::IRBuilderBase &builder,
5426 LLVM::ModuleTranslation &moduleTranslation) {
5427 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
5428 auto simdOp = cast<omp::SimdOp>(opInst);
5429
5430 if (failed(checkImplementationStatus(opInst)))
5431 return failure();
5432
5433 PrivateVarsInfo privateVarsInfo(simdOp);
5434 privateVarsInfo.addLinearVars(simdOp);
5435
5436 MutableArrayRef<BlockArgument> reductionArgs =
5437 cast<omp::BlockArgOpenMPOpInterface>(opInst).getReductionBlockArgs();
5438 DenseMap<Value, llvm::Value *> reductionVariableMap;
5439 SmallVector<llvm::Value *> privateReductionVariables(
5440 simdOp.getNumReductionVars());
5441 SmallVector<DeferredStore> deferredStores;
5443 collectReductionDecls(simdOp, reductionDecls);
5444 llvm::ArrayRef<bool> isByRef = getIsByRef(simdOp.getReductionByref());
5445 assert(isByRef.size() == simdOp.getNumReductionVars());
5446
5447 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
5448 findAllocInsertPoints(builder, moduleTranslation);
5449
5451 simdOp, builder, moduleTranslation, privateVarsInfo, allocaIP);
5452 if (handleError(afterAllocas, opInst).failed())
5453 return failure();
5454
5455 if (failed(allocReductionVars(simdOp, reductionArgs, builder,
5456 moduleTranslation, allocaIP, reductionDecls,
5457 privateReductionVariables, reductionVariableMap,
5458 deferredStores, isByRef)))
5459 return failure();
5460
5461 if (handleError(initPrivateVars(builder, moduleTranslation, privateVarsInfo),
5462 opInst)
5463 .failed())
5464 return failure();
5465
5466 // No call to completePrivateVars because SIMD does not allow FIRSTPRIVATE and
5467 // is not a worksharing construct, so a privatization barrier here could
5468 // deadlock.
5469
5470 // Initialize linear variables and linear step
5471 LinearClauseProcessor linearClauseProcessor;
5472 if (linearClauseProcessor.initLinearIV(simdOp).failed())
5473 return failure();
5474
5475 if (!simdOp.getLinearVars().empty()) {
5476 auto linearVarTypes = simdOp.getLinearVarTypes().value();
5477 for (mlir::Attribute linearVarType : linearVarTypes)
5478 linearClauseProcessor.registerType(moduleTranslation, linearVarType);
5479 for (auto [idx, linearVar] : llvm::enumerate(simdOp.getLinearVars())) {
5480 size_t privIdx = privateVarsInfo.numPrivateVars + idx;
5481 linearClauseProcessor.createLinearVar(
5482 builder, allocaIP, privateVarsInfo.privatizers[privIdx],
5483 moduleTranslation.lookupValue(linearVar),
5484 privateVarsInfo.llvmVars[privIdx], idx);
5485 }
5486 for (mlir::Value linearStep : simdOp.getLinearStepVars())
5487 linearClauseProcessor.initLinearStep(moduleTranslation, linearStep);
5488 }
5489
5490 assert(afterAllocas.get()->getSinglePredecessor());
5491 if (failed(initReductionVars(simdOp, reductionArgs, builder,
5492 moduleTranslation,
5493 afterAllocas.get()->getSinglePredecessor(),
5494 reductionDecls, privateReductionVariables,
5495 reductionVariableMap, isByRef, deferredStores)))
5496 return failure();
5497
5498 llvm::ConstantInt *simdlen = nullptr;
5499 if (std::optional<uint64_t> simdlenVar = simdOp.getSimdlen())
5500 simdlen = builder.getInt64(simdlenVar.value());
5501
5502 llvm::ConstantInt *safelen = nullptr;
5503 if (std::optional<uint64_t> safelenVar = simdOp.getSafelen())
5504 safelen = builder.getInt64(safelenVar.value());
5505
5507 llvm::omp::OrderKind order = convertOrderKind(simdOp.getOrder());
5508
5509 llvm::BasicBlock *sourceBlock = builder.GetInsertBlock();
5510 std::optional<ArrayAttr> alignmentValues = simdOp.getAlignments();
5511 mlir::OperandRange operands = simdOp.getAlignedVars();
5512 for (size_t i = 0; i < operands.size(); ++i) {
5513 llvm::Value *alignment = nullptr;
5514 llvm::Value *llvmVal = moduleTranslation.lookupValue(operands[i]);
5515 llvm::Type *ty = llvmVal->getType();
5516
5517 auto intAttr = cast<IntegerAttr>((*alignmentValues)[i]);
5518 alignment = builder.getInt64(intAttr.getInt());
5519 assert(ty->isPointerTy() && "Invalid type for aligned variable");
5520 assert(alignment && "Invalid alignment value");
5521
5522 // Check if the alignment value is not a power of 2. If so, skip emitting
5523 // alignment.
5524 if (!intAttr.getValue().isPowerOf2())
5525 continue;
5526
5527 auto curInsert = builder.saveIP();
5528 builder.SetInsertPoint(sourceBlock);
5529 llvmVal = builder.CreateLoad(ty, llvmVal);
5530 builder.restoreIP(curInsert);
5531 alignedVars[llvmVal] = alignment;
5532 }
5533
5535 simdOp.getRegion(), "omp.simd.region", builder, moduleTranslation);
5536
5537 if (failed(handleError(regionBlock, opInst)))
5538 return failure();
5539
5540 llvm::CanonicalLoopInfo *loopInfo = findCurrentLoopInfo(moduleTranslation);
5541 // Emit Initialization for linear variables
5542 if (simdOp.getLinearVars().size()) {
5543 if (failed(handleError(
5544 linearClauseProcessor.initLinearVar(builder, moduleTranslation,
5545 loopInfo->getPreheader()),
5546 opInst)))
5547 return failure();
5548
5549 if (failed(handleError(linearClauseProcessor.updateLinearVar(
5550 builder, moduleTranslation, loopInfo->getBody(),
5551 loopInfo->getIndVar()),
5552 opInst)))
5553 return failure();
5554 }
5555 builder.SetInsertPoint((*regionBlock)->begin());
5556
5557 ompBuilder->applySimd(loopInfo, alignedVars,
5558 simdOp.getIfExpr()
5559 ? moduleTranslation.lookupValue(simdOp.getIfExpr())
5560 : nullptr,
5561 order, simdlen, safelen);
5562
5563 if (failed(handleError(
5564 linearClauseProcessor.updateLinearIV(builder, moduleTranslation),
5565 opInst)))
5566 return failure();
5567 if (failed(handleError(linearClauseProcessor.emitStoresForLinearVar(
5568 builder, moduleTranslation),
5569 opInst)))
5570 return failure();
5571
5572 // We now need to reduce the per-simd-lane reduction variable into the
5573 // original variable. This works a bit differently to other reductions (e.g.
5574 // wsloop) because we don't need to call into the OpenMP runtime to handle
5575 // threads: everything happened in this one thread.
5576 for (auto [i, tuple] : llvm::enumerate(
5577 llvm::zip(reductionDecls, isByRef, simdOp.getReductionVars(),
5578 privateReductionVariables))) {
5579 auto [decl, byRef, reductionVar, privateReductionVar] = tuple;
5580
5581 OwningReductionGen gen = makeReductionGen(decl, builder, moduleTranslation);
5582 llvm::Value *originalVariable = moduleTranslation.lookupValue(reductionVar);
5583 llvm::Type *reductionType = moduleTranslation.convertType(decl.getType());
5584
5585 // We have one less load for by-ref case because that load is now inside of
5586 // the reduction region.
5587 llvm::Value *redValue = originalVariable;
5588 if (!byRef)
5589 redValue =
5590 builder.CreateLoad(reductionType, redValue, "red.value." + Twine(i));
5591 llvm::Value *privateRedValue = builder.CreateLoad(
5592 reductionType, privateReductionVar, "red.private.value." + Twine(i));
5593 llvm::Value *reduced;
5594
5595 auto res = gen(builder.saveIP(), redValue, privateRedValue, reduced);
5596 if (failed(handleError(res, opInst)))
5597 return failure();
5598 builder.restoreIP(res.get());
5599
5600 // For by-ref case, the store is inside of the reduction region.
5601 if (!byRef)
5602 builder.CreateStore(reduced, originalVariable);
5603 }
5604
5605 // After the construct, deallocate private reduction variables.
5606 SmallVector<Region *> reductionRegions;
5607 llvm::transform(reductionDecls, std::back_inserter(reductionRegions),
5608 [](omp::DeclareReductionOp reductionDecl) {
5609 return &reductionDecl.getCleanupRegion();
5610 });
5611 if (failed(inlineOmpRegionCleanup(reductionRegions, privateReductionVariables,
5612 moduleTranslation, builder,
5613 "omp.reduction.cleanup")))
5614 return failure();
5615
5616 return cleanupPrivateVars(simdOp, builder, moduleTranslation, simdOp.getLoc(),
5617 privateVarsInfo);
5618}
5619
5620/// Select Virtual only for two constant-count host worksharing loops.
5621static llvm::OpenMPIRBuilder::CollapseIVMapping
5622selectCollapseIVMapping(omp::LoopNestOp loopOp, bool isTargetDevice,
5624 using Mapping = llvm::OpenMPIRBuilder::CollapseIVMapping;
5625 if (isTargetDevice || loopOp.getCollapseNumLoops() != 2 ||
5626 disableVirtualCollapse)
5627 return Mapping::DivMod;
5628 auto wsloop = dyn_cast<omp::WsloopOp>(loopOp->getParentOp());
5629 if (!wsloop || isa<omp::LoopWrapperInterface>(wsloop->getParentOp()) ||
5630 wsloop.getScheduleKind().value_or(omp::ClauseScheduleKind::Static) !=
5631 omp::ClauseScheduleKind::Static ||
5632 wsloop.getScheduleChunk() || wsloop.getOrdered().has_value() ||
5633 !wsloop.getLinearVars().empty())
5634 return Mapping::DivMod;
5635 // Use the existing normalized counts; the builder also validates direct
5636 // API callers.
5637 if (llvm::any_of(loops, [](llvm::CanonicalLoopInfo *loop) {
5638 auto *count = llvm::dyn_cast<llvm::ConstantInt>(loop->getTripCount());
5639 return !count;
5640 }))
5641 return Mapping::DivMod;
5642 return Mapping::Virtual;
5643}
5644
5645/// Converts an OpenMP loop nest into LLVM IR using OpenMPIRBuilder.
5646static LogicalResult
5647convertOmpLoopNest(Operation &opInst, llvm::IRBuilderBase &builder,
5648 LLVM::ModuleTranslation &moduleTranslation) {
5649 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
5650 auto loopOp = cast<omp::LoopNestOp>(opInst);
5651
5652 if (failed(checkImplementationStatus(opInst)))
5653 return failure();
5654
5655 // Set up the source location value for OpenMP runtime.
5656 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
5657
5658 // Generator of the canonical loop body.
5661 auto bodyGen = [&](llvm::OpenMPIRBuilder::InsertPointTy ip,
5662 llvm::Value *iv) -> llvm::Error {
5663 // Make sure further conversions know about the induction variable.
5664 moduleTranslation.mapValue(
5665 loopOp.getRegion().front().getArgument(loopInfos.size()), iv);
5666
5667 // Capture the body insertion point for use in nested loops. BodyIP of the
5668 // CanonicalLoopInfo always points to the beginning of the entry block of
5669 // the body.
5670 bodyInsertPoints.push_back(ip);
5671
5672 if (loopInfos.size() != loopOp.getNumLoops() - 1)
5673 return llvm::Error::success();
5674
5675 // Convert the body of the loop.
5676 builder.restoreIP(ip);
5678 loopOp.getRegion(), "omp.loop_nest.region", builder, moduleTranslation);
5679 if (!regionBlock)
5680 return regionBlock.takeError();
5681
5682 builder.SetInsertPoint((*regionBlock)->begin());
5683 return llvm::Error::success();
5684 };
5685
5686 // Delegate actual loop construction to the OpenMP IRBuilder.
5687 // TODO: this currently assumes omp.loop_nest is semantically similar to SCF
5688 // loop, i.e. it has a positive step, uses signed integer semantics.
5689 // Reconsider this code when the nested loop operation clearly supports more
5690 // cases.
5691 for (unsigned i = 0, e = loopOp.getNumLoops(); i < e; ++i) {
5692 llvm::Value *lowerBound =
5693 moduleTranslation.lookupValue(loopOp.getLoopLowerBounds()[i]);
5694 llvm::Value *upperBound =
5695 moduleTranslation.lookupValue(loopOp.getLoopUpperBounds()[i]);
5696 llvm::Value *step = moduleTranslation.lookupValue(loopOp.getLoopSteps()[i]);
5697
5698 // Make sure loop trip count are emitted in the preheader of the outermost
5699 // loop at the latest so that they are all available for the new collapsed
5700 // loop will be created below.
5701 llvm::OpenMPIRBuilder::LocationDescription loc = ompLoc;
5702 llvm::OpenMPIRBuilder::InsertPointTy computeIP = ompLoc.IP;
5703 if (i != 0) {
5704 loc = llvm::OpenMPIRBuilder::LocationDescription(bodyInsertPoints.back(),
5705 ompLoc.DL);
5706 computeIP = loopInfos.front()->getPreheaderIP();
5707 }
5708
5710 ompBuilder->createCanonicalLoop(
5711 loc, bodyGen, lowerBound, upperBound, step,
5712 /*IsSigned=*/true, loopOp.getLoopInclusive(), computeIP);
5713
5714 if (failed(handleError(loopResult, *loopOp)))
5715 return failure();
5716
5717 loopInfos.push_back(*loopResult);
5718 }
5719
5720 llvm::OpenMPIRBuilder::InsertPointTy afterIP =
5721 loopInfos.front()->getAfterIP();
5722
5723 // Do tiling.
5724 if (const auto &tiles = loopOp.getTileSizes()) {
5725 llvm::Type *ivType = loopInfos.front()->getIndVarType();
5727
5728 for (auto tile : tiles.value()) {
5729 llvm::Value *tileVal = llvm::ConstantInt::get(ivType, tile);
5730 tileSizes.push_back(tileVal);
5731 }
5732
5733 std::vector<llvm::CanonicalLoopInfo *> newLoops =
5734 ompBuilder->tileLoops(ompLoc.DL, loopInfos, tileSizes);
5735
5736 // Update afterIP to get the correct insertion point after
5737 // tiling.
5738 llvm::BasicBlock *afterBB = newLoops.front()->getAfter();
5739 llvm::BasicBlock *afterAfterBB = afterBB->getSingleSuccessor();
5740 afterIP = afterAfterBB->begin();
5741
5742 // Update the loop infos.
5743 loopInfos.clear();
5744 for (const auto &newLoop : newLoops)
5745 loopInfos.push_back(newLoop);
5746 } // Tiling done.
5747
5748 // Do collapse.
5749 const auto &numCollapse = loopOp.getCollapseNumLoops();
5751 loopInfos.begin(), loopInfos.begin() + (numCollapse));
5752
5753 llvm::SaveAndRestore<llvm::OpenMPIRBuilder::CollapseIVMapping> mappingGuard(
5754 ompBuilder->Config.CollapseMapping,
5755 selectCollapseIVMapping(loopOp, ompBuilder->Config.isTargetDevice(),
5756 collapseLoopInfos));
5757 auto newTopLoopInfo =
5758 ompBuilder->collapseLoops(ompLoc.DL, collapseLoopInfos, {});
5759
5760 assert(newTopLoopInfo && "New top loop information is missing");
5761 moduleTranslation.stackWalk<OpenMPLoopInfoStackFrame>(
5762 [&](OpenMPLoopInfoStackFrame &frame) {
5763 frame.loopInfo = newTopLoopInfo;
5764 return WalkResult::interrupt();
5765 });
5766
5767 // Continue building IR after the loop. Note that the LoopInfo returned by
5768 // `collapseLoops` points inside the outermost loop and is intended for
5769 // potential further loop transformations. Use the insertion point stored
5770 // before collapsing loops instead.
5771 builder.restoreIP(afterIP);
5772 return success();
5773}
5774
5775/// Convert an omp.canonical_loop to LLVM-IR
5776static LogicalResult
5777convertOmpCanonicalLoopOp(omp::CanonicalLoopOp op, llvm::IRBuilderBase &builder,
5778 LLVM::ModuleTranslation &moduleTranslation) {
5779 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
5780
5781 llvm::OpenMPIRBuilder::LocationDescription loopLoc(builder);
5782 Value loopIV = op.getInductionVar();
5783 Value loopTC = op.getTripCount();
5784
5785 llvm::Value *llvmTC = moduleTranslation.lookupValue(loopTC);
5786
5788 ompBuilder->createCanonicalLoop(
5789 loopLoc,
5790 [&](llvm::OpenMPIRBuilder::InsertPointTy ip, llvm::Value *llvmIV) {
5791 // Register the mapping of MLIR induction variable to LLVM-IR
5792 // induction variable
5793 moduleTranslation.mapValue(loopIV, llvmIV);
5794
5795 builder.restoreIP(ip);
5797 convertOmpOpRegions(op.getRegion(), "omp.loop.region", builder,
5798 moduleTranslation);
5799
5800 return bodyGenStatus.takeError();
5801 },
5802 llvmTC, "omp.loop");
5803 if (!llvmOrError)
5804 return op.emitError(llvm::toString(llvmOrError.takeError()));
5805
5806 llvm::CanonicalLoopInfo *llvmCLI = *llvmOrError;
5807 llvm::IRBuilderBase::InsertPoint afterIP = llvmCLI->getAfterIP();
5808 builder.restoreIP(afterIP);
5809
5810 // Register the mapping of MLIR loop to LLVM-IR OpenMPIRBuilder loop
5811 if (Value cli = op.getCli())
5812 mapOmpLoop(moduleTranslation, cli, llvmCLI);
5813
5814 return success();
5815}
5816
5817/// Apply a `#pragma omp unroll` / "!$omp unroll" transformation using the
5818/// OpenMPIRBuilder.
5819static LogicalResult
5820applyUnrollHeuristic(omp::UnrollHeuristicOp op, llvm::IRBuilderBase &builder,
5821 LLVM::ModuleTranslation &moduleTranslation) {
5822 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
5823
5824 Value applyee = op.getApplyee();
5825 assert(applyee && "Loop to apply unrolling on required");
5826
5827 llvm::CanonicalLoopInfo *consBuilderCLI =
5828 lookupOMPLoop(moduleTranslation, applyee);
5829 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5830 ompBuilder->unrollLoopHeuristic(loc.DL, consBuilderCLI);
5831
5832 invalidateOmpLoop(moduleTranslation, applyee);
5833 return success();
5834}
5835
5836/// Apply a `#pragma omp unroll full` / `!$omp unroll full` transformation
5837/// using the OpenMPIRBuilder.
5838static LogicalResult
5839applyUnrollFull(omp::UnrollFullOp op, llvm::IRBuilderBase &builder,
5840 LLVM::ModuleTranslation &moduleTranslation) {
5841 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
5842
5843 Value applyee = op.getApplyee();
5844 assert(applyee && "Loop to apply unrolling on required");
5845
5846 llvm::CanonicalLoopInfo *consBuilderCLI =
5847 lookupOMPLoop(moduleTranslation, applyee);
5848 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5849 ompBuilder->unrollLoopFull(loc.DL, consBuilderCLI);
5850
5851 invalidateOmpLoop(moduleTranslation, applyee);
5852 return success();
5853}
5854
5855/// Apply a `#pragma omp unroll partial` / `!$omp unroll partial`
5856/// transformation using the OpenMPIRBuilder.
5857static LogicalResult
5858applyUnrollPartial(omp::UnrollPartialOp op, llvm::IRBuilderBase &builder,
5859 LLVM::ModuleTranslation &moduleTranslation) {
5860 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
5861
5862 Value applyee = op.getApplyee();
5863 assert(applyee && "Loop to apply unrolling on required");
5864
5865 llvm::CanonicalLoopInfo *consBuilderCLI =
5866 lookupOMPLoop(moduleTranslation, applyee);
5867 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5868
5869 // No generatee is supported yet, so the unrolled loop's CanonicalLoopInfo is
5870 // not requested and unrolling is deferred to LLVM's LoopUnroll pass.
5871 int32_t factor = static_cast<int32_t>(op.getUnrollFactor());
5872 ompBuilder->unrollLoopPartial(loc.DL, consBuilderCLI, factor,
5873 /*UnrolledCLI=*/nullptr);
5874
5875 invalidateOmpLoop(moduleTranslation, applyee);
5876 return success();
5877}
5878
5879/// Apply a `#pragma omp tile` / `!$omp tile` transformation using the
5880/// OpenMPIRBuilder.
5881static LogicalResult applyTile(omp::TileOp op, llvm::IRBuilderBase &builder,
5882 LLVM::ModuleTranslation &moduleTranslation) {
5883 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
5884 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5885
5887 SmallVector<llvm::Value *> translatedSizes;
5888
5889 for (Value size : op.getSizes()) {
5890 llvm::Value *translatedSize = moduleTranslation.lookupValue(size);
5891 assert(translatedSize &&
5892 "sizes clause arguments must already be translated");
5893 translatedSizes.push_back(translatedSize);
5894 }
5895
5896 for (Value applyee : op.getApplyees()) {
5897 llvm::CanonicalLoopInfo *consBuilderCLI =
5898 lookupOMPLoop(moduleTranslation, applyee);
5899 assert(applyee && "Canonical loop must already been translated");
5900 translatedLoops.push_back(consBuilderCLI);
5901 }
5902
5903 auto generatedLoops =
5904 ompBuilder->tileLoops(loc.DL, translatedLoops, translatedSizes);
5905 if (!op.getGeneratees().empty()) {
5906 for (auto [mlirLoop, genLoop] :
5907 zip_equal(op.getGeneratees(), generatedLoops))
5908 mapOmpLoop(moduleTranslation, mlirLoop, genLoop);
5909 }
5910
5911 // CLIs can only be consumed once
5912 for (Value applyee : op.getApplyees())
5913 invalidateOmpLoop(moduleTranslation, applyee);
5914
5915 return success();
5916}
5917
5918/// Apply a `#pragma omp fuse` / `!$omp fuse` transformation using the
5919/// OpenMPIRBuilder.
5920static LogicalResult applyFuse(omp::FuseOp op, llvm::IRBuilderBase &builder,
5921 LLVM::ModuleTranslation &moduleTranslation) {
5922 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
5923 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
5924
5925 // Select what CLIs are going to be fused
5926 SmallVector<llvm::CanonicalLoopInfo *> beforeFuse, toFuse, afterFuse;
5927 for (size_t i = 0; i < op.getApplyees().size(); i++) {
5928 Value applyee = op.getApplyees()[i];
5929 llvm::CanonicalLoopInfo *consBuilderCLI =
5930 lookupOMPLoop(moduleTranslation, applyee);
5931 assert(applyee && "Canonical loop must already been translated");
5932 if (op.getFirst().has_value() && i < op.getFirst().value() - 1)
5933 beforeFuse.push_back(consBuilderCLI);
5934 else if (op.getCount().has_value() &&
5935 i >= op.getFirst().value() + op.getCount().value() - 1)
5936 afterFuse.push_back(consBuilderCLI);
5937 else
5938 toFuse.push_back(consBuilderCLI);
5939 }
5940 assert(
5941 (op.getGeneratees().empty() ||
5942 beforeFuse.size() + afterFuse.size() + 1 == op.getGeneratees().size()) &&
5943 "Wrong number of generatees");
5944
5945 // do the fuse
5946 auto generatedLoop = ompBuilder->fuseLoops(loc.DL, toFuse);
5947 if (!op.getGeneratees().empty()) {
5948 size_t i = 0;
5949 for (; i < beforeFuse.size(); i++)
5950 mapOmpLoop(moduleTranslation, op.getGeneratees()[i], beforeFuse[i]);
5951 mapOmpLoop(moduleTranslation, op.getGeneratees()[i++], generatedLoop);
5952 for (; i < afterFuse.size(); i++)
5953 mapOmpLoop(moduleTranslation, op.getGeneratees()[i], afterFuse[i]);
5954 }
5955
5956 // CLIs can only be consumed once
5957 for (Value applyee : op.getApplyees())
5958 invalidateOmpLoop(moduleTranslation, applyee);
5959
5960 return success();
5961}
5962
5963/// Convert an Atomic Ordering attribute to llvm::AtomicOrdering.
5964static llvm::AtomicOrdering
5965convertAtomicOrdering(std::optional<omp::ClauseMemoryOrderKind> ao) {
5966 if (!ao)
5967 return llvm::AtomicOrdering::Monotonic; // Default Memory Ordering
5968
5969 switch (*ao) {
5970 case omp::ClauseMemoryOrderKind::Seq_cst:
5971 return llvm::AtomicOrdering::SequentiallyConsistent;
5972 case omp::ClauseMemoryOrderKind::Acq_rel:
5973 return llvm::AtomicOrdering::AcquireRelease;
5974 case omp::ClauseMemoryOrderKind::Acquire:
5975 return llvm::AtomicOrdering::Acquire;
5976 case omp::ClauseMemoryOrderKind::Release:
5977 return llvm::AtomicOrdering::Release;
5978 case omp::ClauseMemoryOrderKind::Relaxed:
5979 return llvm::AtomicOrdering::Monotonic;
5980 }
5981 llvm_unreachable("Unknown ClauseMemoryOrderKind kind");
5982}
5983
5984/// Compute the cmpxchg failure ordering for an atomic compare op: use the
5985/// `fail` clause ordering when present (the verifier guarantees it is a valid
5986/// cmpxchg failure ordering), otherwise the strongest failure ordering derived
5987/// from the success ordering (which matches the OpenMPIRBuilder default).
5988static llvm::AtomicOrdering
5989getAtomicCompareFailureOrdering(omp::AtomicCompareOp atomicCompareOp,
5990 llvm::AtomicOrdering atomicOrdering) {
5991 if (atomicCompareOp.getFailMemoryOrder())
5992 return convertAtomicOrdering(atomicCompareOp.getFailMemoryOrder());
5993 return llvm::AtomicCmpXchgInst::getStrongestFailureOrdering(atomicOrdering);
5994}
5995
5996/// Convert omp.atomic.read operation to LLVM IR.
5997static LogicalResult
5998convertOmpAtomicRead(Operation &opInst, llvm::IRBuilderBase &builder,
5999 LLVM::ModuleTranslation &moduleTranslation) {
6000 auto readOp = cast<omp::AtomicReadOp>(opInst);
6001 if (failed(checkImplementationStatus(opInst)))
6002 return failure();
6003
6004 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
6005 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
6006 findAllocInsertPoints(builder, moduleTranslation);
6007
6008 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6009
6010 llvm::AtomicOrdering AO = convertAtomicOrdering(readOp.getMemoryOrder());
6011 llvm::Value *x = moduleTranslation.lookupValue(readOp.getX());
6012 llvm::Value *v = moduleTranslation.lookupValue(readOp.getV());
6013
6014 llvm::Type *elementType =
6015 moduleTranslation.convertType(readOp.getElementType());
6016
6017 llvm::OpenMPIRBuilder::AtomicOpValue V = {v, elementType, false, false};
6018 llvm::OpenMPIRBuilder::AtomicOpValue X = {x, elementType, false, false};
6019 builder.restoreIP(ompBuilder->createAtomicRead(ompLoc, X, V, AO, allocaIP));
6020 return success();
6021}
6022
6023/// Converts an omp.atomic.write operation to LLVM IR.
6024static LogicalResult
6025convertOmpAtomicWrite(Operation &opInst, llvm::IRBuilderBase &builder,
6026 LLVM::ModuleTranslation &moduleTranslation) {
6027 auto writeOp = cast<omp::AtomicWriteOp>(opInst);
6028 if (failed(checkImplementationStatus(opInst)))
6029 return failure();
6030
6031 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
6032 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
6033 findAllocInsertPoints(builder, moduleTranslation);
6034
6035 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6036 llvm::AtomicOrdering ao = convertAtomicOrdering(writeOp.getMemoryOrder());
6037 llvm::Value *expr = moduleTranslation.lookupValue(writeOp.getExpr());
6038 llvm::Value *dest = moduleTranslation.lookupValue(writeOp.getX());
6039 llvm::Type *ty = moduleTranslation.convertType(writeOp.getExpr().getType());
6040 llvm::OpenMPIRBuilder::AtomicOpValue x = {dest, ty, /*isSigned=*/false,
6041 /*isVolatile=*/false};
6042 builder.restoreIP(
6043 ompBuilder->createAtomicWrite(ompLoc, x, expr, ao, allocaIP));
6044 return success();
6045}
6046
6047/// Converts an LLVM dialect binary operation to the corresponding enum value
6048/// for `atomicrmw` supported binary operation.
6049static llvm::AtomicRMWInst::BinOp convertBinOpToAtomic(Operation &op) {
6051 .Case([&](LLVM::AddOp) { return llvm::AtomicRMWInst::BinOp::Add; })
6052 .Case([&](LLVM::SubOp) { return llvm::AtomicRMWInst::BinOp::Sub; })
6053 .Case([&](LLVM::AndOp) { return llvm::AtomicRMWInst::BinOp::And; })
6054 .Case([&](LLVM::OrOp) { return llvm::AtomicRMWInst::BinOp::Or; })
6055 .Case([&](LLVM::XOrOp) { return llvm::AtomicRMWInst::BinOp::Xor; })
6056 .Case([&](LLVM::SMaxOp) { return llvm::AtomicRMWInst::BinOp::Max; })
6057 .Case([&](LLVM::SMinOp) { return llvm::AtomicRMWInst::BinOp::Min; })
6058 .Case([&](LLVM::UMaxOp) { return llvm::AtomicRMWInst::BinOp::UMax; })
6059 .Case([&](LLVM::UMinOp) { return llvm::AtomicRMWInst::BinOp::UMin; })
6060 .Case([&](LLVM::FAddOp) { return llvm::AtomicRMWInst::BinOp::FAdd; })
6061 .Case([&](LLVM::FSubOp) { return llvm::AtomicRMWInst::BinOp::FSub; })
6062 .Case([&](LLVM::MaxNumOp) { return llvm::AtomicRMWInst::BinOp::FMax; })
6063 .Case([&](LLVM::MinNumOp) { return llvm::AtomicRMWInst::BinOp::FMin; })
6064 .Case(
6065 [&](LLVM::MaximumOp) { return llvm::AtomicRMWInst::BinOp::FMaximum; })
6066 .Case(
6067 [&](LLVM::MinimumOp) { return llvm::AtomicRMWInst::BinOp::FMinimum; })
6068 .Default(llvm::AtomicRMWInst::BinOp::BAD_BINOP);
6069}
6070
6071static void extractAtomicControlFlags(omp::AtomicUpdateOp atomicUpdateOp,
6072 bool &isIgnoreDenormalMode,
6073 bool &isFineGrainedMemory,
6074 bool &isRemoteMemory) {
6075 isIgnoreDenormalMode = false;
6076 isFineGrainedMemory = false;
6077 isRemoteMemory = false;
6078 if (atomicUpdateOp && atomicUpdateOp.getAtomicControlAttr()) {
6079 mlir::omp::AtomicControlAttr atomicControlAttr =
6080 atomicUpdateOp.getAtomicControlAttr();
6081 isIgnoreDenormalMode = atomicControlAttr.getIgnoreDenormalMode();
6082 isFineGrainedMemory = atomicControlAttr.getFineGrainedMemory();
6083 isRemoteMemory = atomicControlAttr.getRemoteMemory();
6084 }
6085}
6086
6087/// Converts an OpenMP atomic update operation using OpenMPIRBuilder.
6088static LogicalResult
6089convertOmpAtomicUpdate(omp::AtomicUpdateOp &opInst,
6090 llvm::IRBuilderBase &builder,
6091 LLVM::ModuleTranslation &moduleTranslation) {
6092 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
6093 if (failed(checkImplementationStatus(*opInst)))
6094 return failure();
6095
6096 // Convert values and types.
6097 auto &innerOpList = opInst.getRegion().front().getOperations();
6098 bool isXBinopExpr{false};
6099 llvm::AtomicRMWInst::BinOp binop;
6100 mlir::Value mlirExpr;
6101 llvm::Value *llvmExpr = nullptr;
6102 llvm::Value *llvmX = nullptr;
6103 llvm::Type *llvmXElementType = nullptr;
6104 if (innerOpList.size() == 2) {
6105 // The two operations here are the update and the terminator.
6106 // Since we can identify the update operation, there is a possibility
6107 // that we can generate the atomicrmw instruction.
6108 mlir::Operation &innerOp = *opInst.getRegion().front().begin();
6109 if (!llvm::is_contained(innerOp.getOperands(),
6110 opInst.getRegion().getArgument(0))) {
6111 return opInst.emitError("no atomic update operation with region argument"
6112 " as operand found inside atomic.update region");
6113 }
6114 binop = convertBinOpToAtomic(innerOp);
6115 isXBinopExpr = innerOp.getOperand(0) == opInst.getRegion().getArgument(0);
6116 mlirExpr = (isXBinopExpr ? innerOp.getOperand(1) : innerOp.getOperand(0));
6117 llvmExpr = moduleTranslation.lookupValue(mlirExpr);
6118 } else {
6119 // Since the update region includes more than one operation
6120 // we will resort to generating a cmpxchg loop.
6121 binop = llvm::AtomicRMWInst::BinOp::BAD_BINOP;
6122 }
6123 llvmX = moduleTranslation.lookupValue(opInst.getX());
6124 llvmXElementType = moduleTranslation.convertType(
6125 opInst.getRegion().getArgument(0).getType());
6126 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {llvmX, llvmXElementType,
6127 /*isSigned=*/false,
6128 /*isVolatile=*/false};
6129
6130 llvm::AtomicOrdering atomicOrdering =
6131 convertAtomicOrdering(opInst.getMemoryOrder());
6132
6133 // Generate update code.
6134 auto updateFn =
6135 [&opInst, &moduleTranslation](
6136 llvm::Value *atomicx,
6137 llvm::IRBuilder<> &builder) -> llvm::Expected<llvm::Value *> {
6138 Block &bb = *opInst.getRegion().begin();
6139 moduleTranslation.mapValue(*opInst.getRegion().args_begin(), atomicx);
6140 moduleTranslation.mapBlock(&bb, builder.GetInsertBlock());
6141 if (failed(moduleTranslation.convertBlock(bb, true, builder)))
6142 return llvm::make_error<PreviouslyReportedError>();
6143
6144 omp::YieldOp yieldop = dyn_cast<omp::YieldOp>(bb.getTerminator());
6145 assert(yieldop && yieldop.getResults().size() == 1 &&
6146 "terminator must be omp.yield op and it must have exactly one "
6147 "argument");
6148 return moduleTranslation.lookupValue(yieldop.getResults()[0]);
6149 };
6150
6151 bool isIgnoreDenormalMode;
6152 bool isFineGrainedMemory;
6153 bool isRemoteMemory;
6154 extractAtomicControlFlags(opInst, isIgnoreDenormalMode, isFineGrainedMemory,
6155 isRemoteMemory);
6156 // Handle ambiguous alloca, if any.
6157 auto allocaIP = findAllocInsertPoints(builder, moduleTranslation);
6158 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6159 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
6160 ompBuilder->createAtomicUpdate(ompLoc, allocaIP, llvmAtomicX, llvmExpr,
6161 atomicOrdering, binop, updateFn,
6162 isXBinopExpr, isIgnoreDenormalMode,
6163 isFineGrainedMemory, isRemoteMemory);
6164
6165 if (failed(handleError(afterIP, *opInst)))
6166 return failure();
6167
6168 builder.restoreIP(*afterIP);
6169 return success();
6170}
6171
6172/// Helper to extract the OMPAtomicCompareOp from an integer comparison
6173/// predicate. Returns std::nullopt for unsupported predicates.
6174static std::optional<llvm::omp::OMPAtomicCompareOp>
6175convertICmpPredicateToAtomicCompareOp(LLVM::ICmpPredicate predicate) {
6176 switch (predicate) {
6177 case LLVM::ICmpPredicate::eq:
6178 return llvm::omp::OMPAtomicCompareOp::EQ;
6179 case LLVM::ICmpPredicate::slt:
6180 case LLVM::ICmpPredicate::ult:
6181 return llvm::omp::OMPAtomicCompareOp::MIN;
6182 case LLVM::ICmpPredicate::sgt:
6183 case LLVM::ICmpPredicate::ugt:
6184 return llvm::omp::OMPAtomicCompareOp::MAX;
6185 default:
6186 return std::nullopt;
6187 }
6188}
6189
6190/// Helper to extract the OMPAtomicCompareOp from a floating-point comparison
6191/// predicate. Returns std::nullopt for unsupported predicates.
6192static std::optional<llvm::omp::OMPAtomicCompareOp>
6193convertFCmpPredicateToAtomicCompareOp(LLVM::FCmpPredicate predicate) {
6194 switch (predicate) {
6195 case LLVM::FCmpPredicate::oeq:
6196 case LLVM::FCmpPredicate::ueq:
6197 return llvm::omp::OMPAtomicCompareOp::EQ;
6198 case LLVM::FCmpPredicate::olt:
6199 case LLVM::FCmpPredicate::ult:
6200 return llvm::omp::OMPAtomicCompareOp::MIN;
6201 case LLVM::FCmpPredicate::ogt:
6202 case LLVM::FCmpPredicate::ugt:
6203 return llvm::omp::OMPAtomicCompareOp::MAX;
6204 default:
6205 return std::nullopt;
6206 }
6207}
6208
6209/// Result of matching the decomposed complex equality pattern inside an atomic
6210/// compare region.
6212 bool isComplex = false;
6213 bool isNE = false; // `or` of the field compares => NE (unsupported).
6214 mlir::Value eAggregate; // The complex expected value (`e`).
6215 bool isXBinopExpr = false; // True if x is the first fcmp operand.
6216};
6217
6218/// Detect a decomposed complex equality comparison in an atomic compare region:
6219/// %re_x = llvm.extractvalue %xval[0]
6220/// %re_e = llvm.extractvalue %eStruct[0]
6221/// %cmp_re = llvm.fcmp "oeq" %re_x, %re_e
6222/// %im_x = llvm.extractvalue %xval[1]
6223/// %im_e = llvm.extractvalue %eStruct[1]
6224/// %cmp_im = llvm.fcmp "oeq" %im_x, %im_e
6225/// %cmp = llvm.and %cmp_re, %cmp_im (llvm.or would be NE)
6226/// It is recognised by an and/or whose operands are both fcmps operating on
6227/// extractvalues, one chain rooted at the block argument (x) and the other at
6228/// the expected complex value (e).
6231 auto traceToAggregate = [](mlir::Value v) -> mlir::Value {
6232 if (auto extractOp = v.getDefiningOp<LLVM::ExtractValueOp>())
6233 return extractOp.getContainer();
6234 return nullptr;
6235 };
6236 for (Operation &op : block.getOperations()) {
6237 if (!isa<LLVM::AndOp, LLVM::OrOp>(op))
6238 continue;
6239 auto lhsFcmp = op.getOperand(0).getDefiningOp<LLVM::FCmpOp>();
6240 auto rhsFcmp = op.getOperand(1).getDefiningOp<LLVM::FCmpOp>();
6241 if (!lhsFcmp || !rhsFcmp)
6242 continue;
6243 mlir::Value lhsAgg0 = traceToAggregate(lhsFcmp.getOperand(0));
6244 mlir::Value lhsAgg1 = traceToAggregate(lhsFcmp.getOperand(1));
6245 bool lhsXIsOp0 = (lhsAgg0 == block.getArgument(0));
6246 bool lhsXIsOp1 = (lhsAgg1 == block.getArgument(0));
6247 if (!lhsXIsOp0 && !lhsXIsOp1)
6248 continue;
6249 mlir::Value eAggregate = lhsXIsOp0 ? lhsAgg1 : lhsAgg0;
6250 if (!eAggregate)
6251 continue;
6252 result.isComplex = true;
6253 result.isNE = isa<LLVM::OrOp>(op);
6254 result.eAggregate = eAggregate;
6255 result.isXBinopExpr = lhsXIsOp0;
6256 break;
6257 }
6258 return result;
6259}
6260
6261/// Emit an IEEE-754-correct `cmpxchg` for a complex (struct-typed) atomic
6262/// compare with `fcmp oeq`. The old value of X is returned (as the complex
6263/// struct type) in \p oldComplex and the success flag (i1) in \p cmpOk.
6264/// \p failOrdering is the memory ordering used when the compare-exchange does
6265/// not store; it must be a valid cmpxchg failure ordering.
6266static void emitComplexAtomicCmpXchg(llvm::IRBuilderBase &builder,
6267 llvm::Value *llvmX, llvm::Type *complexTy,
6268 llvm::Value *eVal, llvm::Value *dVal,
6269 llvm::AtomicOrdering atomicOrdering,
6270 llvm::AtomicOrdering failOrdering,
6271 bool isWeak, llvm::Value *&oldComplex,
6272 llvm::Value *&cmpOk) {
6273 const llvm::DataLayout &DL = builder.getDataLayout();
6274 unsigned totalBits = DL.getTypeStoreSizeInBits(complexTy).getFixedValue();
6275 llvm::IntegerType *intTy =
6276 llvm::IntegerType::get(builder.getContext(), totalBits);
6277 llvm::Align complexAlign = DL.getABITypeAlign(complexTy);
6278 llvm::Align intAlign = DL.getABITypeAlign(intTy);
6279 llvm::Align maxAlign = std::max(complexAlign, intAlign);
6280
6281 // Spill D to obtain its integer bit pattern for the swap value.
6282 llvm::AllocaInst *dAlloca =
6283 builder.CreateAlloca(complexTy, nullptr, "cmplx.d");
6284 dAlloca->setAlignment(maxAlign);
6285 builder.CreateAlignedStore(dVal, dAlloca, maxAlign);
6286 llvm::Value *dInt =
6287 builder.CreateAlignedLoad(intTy, dAlloca, maxAlign, "cmplx.d.int");
6288
6289 // Load X atomically and reinterpret as complex. Use the failure ordering: on
6290 // a failed component comparison we branch around the cmpxchg, so this load is
6291 // the only memory op on that path.
6292 llvm::LoadInst *xCurr =
6293 builder.CreateAlignedLoad(intTy, llvmX, maxAlign, "cmplx.x.load");
6294 xCurr->setAtomic(failOrdering);
6295 llvm::AllocaInst *xAlloca =
6296 builder.CreateAlloca(complexTy, nullptr, "cmplx.x");
6297 xAlloca->setAlignment(maxAlign);
6298 builder.CreateAlignedStore(xCurr, xAlloca, maxAlign);
6299 llvm::Value *xStruct =
6300 builder.CreateAlignedLoad(complexTy, xAlloca, maxAlign, "cmplx.x.val");
6301
6302 // Component-wise IEEE-754 equality: `fcmp oeq` yields false for NaN (so a
6303 // NaN component correctly makes the compare fail) and true for +0.0 vs -0.0
6304 // (so a zero-sign difference does not spuriously fail the compare).
6305 llvm::Value *reX = builder.CreateExtractValue(xStruct, 0);
6306 llvm::Value *imX = builder.CreateExtractValue(xStruct, 1);
6307 llvm::Value *reE = builder.CreateExtractValue(eVal, 0);
6308 llvm::Value *imE = builder.CreateExtractValue(eVal, 1);
6309 llvm::Value *reEq = builder.CreateFCmpOEQ(reX, reE, "cmplx.re.eq");
6310 llvm::Value *imEq = builder.CreateFCmpOEQ(imX, imE, "cmplx.im.eq");
6311 llvm::Value *fpEqual = builder.CreateAnd(reEq, imEq, "cmplx.eq");
6312
6313 // When the components compare equal, attempt the swap using X's own loaded
6314 // bit pattern as the comparand; otherwise the compare fails and X is left
6315 // unchanged (the captured old value is the value just loaded).
6316 llvm::BasicBlock *curBB = builder.GetInsertBlock();
6317 llvm::Function *fn = curBB->getParent();
6318 llvm::BasicBlock *swapBB =
6319 llvm::BasicBlock::Create(builder.getContext(), "cmplx.atomic.swap", fn);
6320 llvm::BasicBlock *exitBB =
6321 llvm::BasicBlock::Create(builder.getContext(), "cmplx.atomic.exit", fn);
6322 builder.CreateCondBr(fpEqual, swapBB, exitBB);
6323
6324 builder.SetInsertPoint(swapBB);
6325 llvm::AtomicCmpXchgInst *cmpXchg = builder.CreateAtomicCmpXchg(
6326 llvmX, xCurr, dInt, maxAlign, atomicOrdering, failOrdering);
6327 cmpXchg->setWeak(isWeak);
6328 llvm::Value *oldSwap = builder.CreateExtractValue(cmpXchg, 0);
6329 llvm::Value *okSwap = builder.CreateExtractValue(cmpXchg, 1);
6330 builder.CreateBr(exitBB);
6331
6332 // Merge the swap and no-swap paths.
6333 builder.SetInsertPoint(exitBB);
6334 llvm::PHINode *oldIntPHI = builder.CreatePHI(intTy, 2, "cmplx.old.int");
6335 oldIntPHI->addIncoming(oldSwap, swapBB);
6336 oldIntPHI->addIncoming(xCurr, curBB);
6337 llvm::PHINode *okPHI = builder.CreatePHI(builder.getInt1Ty(), 2, "cmplx.ok");
6338 okPHI->addIncoming(okSwap, swapBB);
6339 okPHI->addIncoming(builder.getFalse(), curBB);
6340
6341 // Reinterpret the old integer value as the complex struct via memory.
6342 llvm::AllocaInst *oldAlloca =
6343 builder.CreateAlloca(complexTy, nullptr, "cmplx.old");
6344 oldAlloca->setAlignment(maxAlign);
6345 builder.CreateAlignedStore(oldIntPHI, oldAlloca, maxAlign);
6346 oldComplex = builder.CreateAlignedLoad(complexTy, oldAlloca, maxAlign,
6347 "cmplx.old.val");
6348 cmpOk = okPHI;
6349}
6350
6351/// Holds the extracted comparison pattern information from an atomic compare
6352/// region.
6354 llvm::omp::OMPAtomicCompareOp compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
6355 llvm::Value *eVal = nullptr;
6356 llvm::Value *dVal = nullptr;
6357 bool isXBinopExpr = false;
6358 bool isSigned = false;
6359};
6360/// Extract comparison predicate, expected value (e), desired value (d), and
6361/// related flags from an atomic compare region block by scanning for
6362/// icmp/fcmp/select/min/max operations.
6363static LogicalResult extractAtomicComparePattern(
6364 Block &block,
6365 llvm::function_ref<llvm::Value *(mlir::Value)> materializeValue,
6366 omp::AtomicCompareOp atomicCompareOp, AtomicComparePatternInfo &info) {
6367 // Complex equality is a decomposed per-field pattern (extractvalue + fcmp +
6368 // and) rather than a single scalar compare. Detect it first so the scalar
6369 // icmp/fcmp handling below does not mistake a real/imaginary field for the
6370 // whole expected value.
6372 cplx.isComplex) {
6373 if (cplx.isNE)
6374 return atomicCompareOp.emitError(
6375 "unsupported comparison predicate (NE) for complex atomic compare");
6376 info.compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
6377 info.isXBinopExpr = cplx.isXBinopExpr;
6378 info.eVal = materializeValue(cplx.eAggregate);
6379 for (Operation &op : block.getOperations()) {
6380 if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
6381 info.dVal = materializeValue(selectOp.getTrueValue());
6382 break;
6383 }
6384 }
6385 return success();
6386 }
6387
6388 for (Operation &op : block.getOperations()) {
6389 // Pre-filter: skip icmps that don't involve the block argument
6390 // (e.g., truthiness extractions from logical-to-integer conversion).
6391 if (auto icmpOp = dyn_cast<LLVM::ICmpOp>(op);
6392 icmpOp && icmpOp.getOperand(0) != block.getArgument(0) &&
6393 icmpOp.getOperand(1) != block.getArgument(0))
6394 continue;
6395
6396 LogicalResult result =
6398 .Case<LLVM::ICmpOp>([&](LLVM::ICmpOp icmpOp) -> LogicalResult {
6399 auto maybeOp =
6400 convertICmpPredicateToAtomicCompareOp(icmpOp.getPredicate());
6401 if (!maybeOp)
6402 return atomicCompareOp.emitError(
6403 "unsupported comparison predicate in atomic compare");
6404 info.compareOp = *maybeOp;
6405 LLVM::ICmpPredicate pred = icmpOp.getPredicate();
6406 info.isSigned = (pred == LLVM::ICmpPredicate::slt ||
6407 pred == LLVM::ICmpPredicate::sgt ||
6408 pred == LLVM::ICmpPredicate::sle ||
6409 pred == LLVM::ICmpPredicate::sge);
6410 info.isXBinopExpr =
6411 (icmpOp.getOperand(0) == block.getArgument(0));
6412 mlir::Value eOperand = info.isXBinopExpr ? icmpOp.getOperand(1)
6413 : icmpOp.getOperand(0);
6414 info.eVal = materializeValue(eOperand);
6415 return success();
6416 })
6417 .Case<LLVM::FCmpOp>([&](LLVM::FCmpOp fcmpOp) -> LogicalResult {
6418 auto maybeOp =
6419 convertFCmpPredicateToAtomicCompareOp(fcmpOp.getPredicate());
6420 if (!maybeOp)
6421 return atomicCompareOp.emitError(
6422 "unsupported comparison predicate in atomic compare");
6423 info.compareOp = *maybeOp;
6424 info.isXBinopExpr =
6425 (fcmpOp.getOperand(0) == block.getArgument(0));
6426 mlir::Value eOperand = info.isXBinopExpr ? fcmpOp.getOperand(1)
6427 : fcmpOp.getOperand(0);
6428 info.eVal = materializeValue(eOperand);
6429 return success();
6430 })
6431 .Case<LLVM::SelectOp>([&](LLVM::SelectOp selectOp) {
6432 if (!info.dVal)
6433 info.dVal = materializeValue(selectOp.getTrueValue());
6434 return success();
6435 })
6436 .Case<mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
6437 mlir::arith::MaxUIOp, mlir::arith::MinUIOp,
6438 mlir::arith::MaximumFOp, mlir::arith::MinimumFOp,
6439 LLVM::SMaxOp, LLVM::SMinOp, LLVM::UMaxOp, LLVM::UMinOp,
6440 LLVM::MaxNumOp, LLVM::MinNumOp>([&](Operation *) {
6441 // Canonicalized min/max ops (arith or LLVM intrinsic form).
6442 // max(x,e) came from slt/ult/olt -> OMPAtomicCompareOp::MIN
6443 // min(x,e) came from sgt/ugt/ogt -> OMPAtomicCompareOp::MAX
6444 // (OMPIRBuilder inverts: MIN->atomicrmw max, MAX->atomicrmw min)
6445 bool isMax = isa<mlir::arith::MaxSIOp, mlir::arith::MaxUIOp,
6446 mlir::arith::MaximumFOp, LLVM::SMaxOp,
6447 LLVM::UMaxOp, LLVM::MaxNumOp>(op);
6448 info.compareOp = isMax ? llvm::omp::OMPAtomicCompareOp::MIN
6449 : llvm::omp::OMPAtomicCompareOp::MAX;
6450 info.isSigned = isa<mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
6451 LLVM::SMaxOp, LLVM::SMinOp>(op);
6452 info.isXBinopExpr = (op.getOperand(0) == block.getArgument(0));
6453 mlir::Value eOperand =
6454 info.isXBinopExpr ? op.getOperand(1) : op.getOperand(0);
6455 info.eVal = materializeValue(eOperand);
6456 info.dVal = info.eVal;
6457 return success();
6458 })
6459 .Default([](Operation *) { return success(); });
6460
6461 if (failed(result))
6462 return result;
6463 }
6464 return success();
6465}
6466
6467static LogicalResult
6468convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
6469 llvm::IRBuilderBase &builder,
6470 LLVM::ModuleTranslation &moduleTranslation) {
6471 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
6472 if (failed(checkImplementationStatus(*atomicCaptureOp)))
6473 return failure();
6474
6475 omp::AtomicUpdateOp atomicUpdateOp = atomicCaptureOp.getAtomicUpdateOp();
6476 omp::AtomicWriteOp atomicWriteOp = atomicCaptureOp.getAtomicWriteOp();
6477 omp::AtomicCompareOp atomicCompareOp = atomicCaptureOp.getAtomicCompareOp();
6478
6479 // If the capture contains an atomic.compare, delegate to
6480 // createAtomicCompare with the capture variable (V) set.
6481 if (atomicCompareOp) {
6482 omp::AtomicReadOp atomicReadOp = atomicCaptureOp.getAtomicReadOp();
6483 assert(atomicReadOp && "expected atomic.read in capture+compare");
6484
6485 Region &region = atomicCompareOp.getRegion();
6486 Block &block = region.front();
6487
6488 llvm::Type *llvmXElementType =
6489 moduleTranslation.convertType(block.getArgument(0).getType());
6490 llvm::Value *llvmX = moduleTranslation.lookupValue(atomicCompareOp.getX());
6491 llvm::Value *llvmV = moduleTranslation.lookupValue(atomicReadOp.getV());
6492
6493 bool isSigned = false;
6494 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {
6495 llvmX, llvmXElementType, isSigned, /*IsVolatile=*/false};
6496 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicV = {
6497 llvmV, llvmXElementType, /*isSigned=*/false, /*IsVolatile=*/false};
6498 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicR = {nullptr, nullptr, false,
6499 false};
6500
6501 llvm::AtomicOrdering atomicOrdering =
6502 convertAtomicOrdering(atomicCaptureOp.getMemoryOrder());
6503
6504 // Pre-translate non-pattern operations inside the compare region.
6505 auto isAtomicComparePatternOp = [](Operation &op) {
6506 return llvm::isa<LLVM::ICmpOp, LLVM::FCmpOp, LLVM::SelectOp, LLVM::AndOp,
6507 LLVM::OrOp, LLVM::SMaxOp, LLVM::SMinOp, LLVM::UMaxOp,
6508 LLVM::UMinOp, LLVM::MaxNumOp, LLVM::MinNumOp,
6509 mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
6510 mlir::arith::MaxUIOp, mlir::arith::MinUIOp,
6511 mlir::arith::MaximumFOp, mlir::arith::MinimumFOp>(op);
6512 };
6513 for (Operation &op : block.without_terminator()) {
6514 if (isAtomicComparePatternOp(op))
6515 continue;
6516 bool allOperandsMapped =
6517 llvm::all_of(op.getOperands(), [&](mlir::Value v) {
6518 return moduleTranslation.lookupValue(v) != nullptr;
6519 });
6520 if (!allOperandsMapped)
6521 continue;
6522 if (failed(moduleTranslation.convertOperation(op, builder)))
6523 return atomicCompareOp.emitError(
6524 "failed to translate operation inside atomic compare region");
6525 }
6526
6527 auto materializeValue = [&](mlir::Value val) -> llvm::Value * {
6528 if (llvm::Value *existing = moduleTranslation.lookupValue(val))
6529 return existing;
6530 if (auto loadOp = val.getDefiningOp<LLVM::LoadOp>()) {
6531 if (loadOp->getParentRegion() == &region) {
6532 llvm::Value *loadAddr =
6533 moduleTranslation.lookupValue(loadOp.getAddr());
6534 if (!loadAddr)
6535 return nullptr;
6536 llvm::Type *loadType =
6537 moduleTranslation.convertType(loadOp.getResult().getType());
6538 return builder.CreateLoad(loadType, loadAddr);
6539 }
6540 }
6541 return nullptr;
6542 };
6543
6544 // Extract comparison predicate, eVal, and dVal from the region.
6545 AtomicComparePatternInfo patternInfo;
6546 if (failed(extractAtomicComparePattern(block, materializeValue,
6547 atomicCompareOp, patternInfo)))
6548 return failure();
6549
6550 llvm::omp::OMPAtomicCompareOp compareOp = patternInfo.compareOp;
6551 llvm::Value *eVal = patternInfo.eVal;
6552 llvm::Value *dVal = patternInfo.dVal;
6553 bool isXBinopExpr = patternInfo.isXBinopExpr;
6554 isSigned = patternInfo.isSigned;
6555
6556 if (!eVal)
6557 return atomicCompareOp.emitError(
6558 "failed to extract expected value (e) from atomic compare region");
6559 if (!dVal) {
6560 auto yieldOp = cast<omp::YieldOp>(block.getTerminator());
6561 if (yieldOp.getResults().empty())
6562 return atomicCompareOp.emitError(
6563 "failed to extract desired value (d) from atomic compare region");
6564 dVal = materializeValue(yieldOp.getResults()[0]);
6565 }
6566
6567 llvmAtomicX.IsSigned = isSigned;
6568
6569 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6570 bool isReadFirst = isa<omp::AtomicReadOp>(atomicCaptureOp.getFirstOp());
6571 bool isPostfixCapture = !isReadFirst;
6572 bool isFailOnly = atomicCaptureOp.getFailOnly();
6573
6574 // Complex equality capture: x is struct-typed, which the OMPIRBuilder
6575 // cannot handle, so emit an IEEE-754-correct cmpxchg (as in the non-capture
6576 // complex path) and reconstruct the captured value from its result. The
6577 // helper compares components with `fcmp oeq` so `-0.0 == +0.0` and `NaN`
6578 // are handled as in the scalar float path. Complex only supports the ==
6579 // comparison.
6580 if (llvmXElementType->isStructTy()) {
6581 llvm::Value *oldComplex = nullptr;
6582 llvm::Value *cmpOk = nullptr;
6583 llvm::AtomicOrdering failOrdering =
6584 getAtomicCompareFailureOrdering(atomicCompareOp, atomicOrdering);
6585 emitComplexAtomicCmpXchg(builder, llvmX, llvmXElementType, eVal, dVal,
6586 atomicOrdering, failOrdering,
6587 atomicCompareOp.getWeak(), oldComplex, cmpOk);
6588
6589 if (isFailOnly) {
6590 // v is written only when the compare fails (cmpOk == false).
6591 llvm::Value *cmpFailed = builder.CreateNot(cmpOk);
6592 llvm::BasicBlock *curBB = builder.GetInsertBlock();
6593 llvm::Function *fn = curBB->getParent();
6594 llvm::BasicBlock *contBB = llvm::BasicBlock::Create(
6595 builder.getContext(), "omp.atomic.cont", fn);
6596 llvm::BasicBlock *exitBB = llvm::BasicBlock::Create(
6597 builder.getContext(), "omp.atomic.exit", fn);
6598 builder.CreateCondBr(cmpFailed, contBB, exitBB);
6599 builder.SetInsertPoint(contBB);
6600 builder.CreateStore(oldComplex, llvmAtomicV.Var,
6601 llvmAtomicV.IsVolatile);
6602 builder.CreateBr(exitBB);
6603 builder.SetInsertPoint(exitBB);
6604 } else if (isPostfixCapture) {
6605 // v gets the new value of x: d on success, old x otherwise.
6606 llvm::Value *newComplex = builder.CreateSelect(cmpOk, dVal, oldComplex);
6607 builder.CreateStore(newComplex, llvmAtomicV.Var,
6608 llvmAtomicV.IsVolatile);
6609 } else {
6610 // Prefix: v gets the old value of x.
6611 builder.CreateStore(oldComplex, llvmAtomicV.Var,
6612 llvmAtomicV.IsVolatile);
6613 }
6614
6615 // Emit flush after atomic compare if needed (release/acq_rel/seq_cst).
6616 if (atomicOrdering == llvm::AtomicOrdering::Release ||
6617 atomicOrdering == llvm::AtomicOrdering::AcquireRelease ||
6618 atomicOrdering == llvm::AtomicOrdering::SequentiallyConsistent) {
6619 llvm::OpenMPIRBuilder::LocationDescription flushLoc(builder);
6620 ompBuilder->createFlush(flushLoc);
6621 }
6622 return success();
6623 }
6624
6625 // Min/max (<, >) comparisons lower to an atomicrmw. The OMPIRBuilder has no
6626 // notion of a failed compare for an atomicrmw, so the fail-only capture
6627 // form (v written only when the compare fails) has no valid mapping and is
6628 // Min/max (<, >) comparisons lower to an atomicrmw. The OMPIRBuilder has no
6629 // notion of a failed compare for an atomicrmw (it asserts on IsFailOnly),
6630 // so for min/max the fail-only capture is reconstructed manually below.
6631 bool isMinMax = compareOp != llvm::omp::OMPAtomicCompareOp::EQ;
6632
6633 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicVForCall = llvmAtomicV;
6634 // Capture into V is reconstructed manually below for:
6635 // * postfix capture (v gets the new value of x): equality from the
6636 // cmpxchg result, min/max from the atomicrmw result;
6637 // * min/max fail-only capture (the OMPIRBuilder cannot express it).
6638 // Bypass V in the OMPIRBuilder for those cases so it does not also emit its
6639 // own (for min/max, incorrect or unsupported) capture store.
6640 bool minMaxManualCapture = isMinMax && (isPostfixCapture || isFailOnly);
6641 bool eqPostfixManualCapture = !isMinMax && isPostfixCapture && !isFailOnly;
6642 if (minMaxManualCapture || eqPostfixManualCapture)
6643 llvmAtomicVForCall = {nullptr, nullptr, false, false};
6644
6645 // The OMPIRBuilder only understands IsFailOnly for the equality (cmpxchg)
6646 // path; for min/max it would assert. Min/max fail-only is handled here.
6647 bool builderFailOnly = isFailOnly && !isMinMax;
6648
6649 // IsPostfixUpdate selects which value the OMPIRBuilder captures into V:
6650 // * min/max prefix and equality prefix: a direct store of the old value
6651 // (IsPostfixUpdate=true).
6652 // * equality fail-only: a conditional store (IsPostfixUpdate=false).
6653 // Manually-reconstructed captures bypass V above.
6654 bool isPostfixUpdate = !builderFailOnly;
6655
6656 bool isWeak = atomicCompareOp.getWeak();
6657 bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
6658 llvm::AtomicOrdering failureOrdering =
6659 getAtomicCompareFailureOrdering(atomicCompareOp, atomicOrdering);
6660 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
6661 ompBuilder->createAtomicCompare(
6662 ompLoc, llvmAtomicX, llvmAtomicVForCall, llvmAtomicR, eVal, dVal,
6663 atomicOrdering, compareOp, isXBinopExpr, isPostfixUpdate,
6664 builderFailOnly, failureOrdering, isWeak);
6665 ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
6666
6667 if (failed(handleError(afterIP, *atomicCaptureOp)))
6668 return failure();
6669
6670 builder.restoreIP(*afterIP);
6671
6672 // Min/max capture is reconstructed from the atomicrmw the OMPIRBuilder
6673 // emits (its result is the old value of x). V was bypassed above.
6674 // * postfix: v gets the new value min/max(old, e);
6675 // * fail-only: v gets the old value, but only when the compare failed
6676 // (i.e. the atomicrmw did not change x).
6677 // (Prefix min/max captures the old value directly through V, so nothing
6678 // extra is needed there.)
6679 if (isMinMax && (isPostfixCapture || isFailOnly)) {
6680 llvm::BasicBlock *curBB = builder.GetInsertBlock();
6681 llvm::AtomicRMWInst *rmw = nullptr;
6682 for (auto &inst : llvm::reverse(*curBB)) {
6683 if (auto *r = dyn_cast<llvm::AtomicRMWInst>(&inst)) {
6684 rmw = r;
6685 break;
6686 }
6687 }
6688 assert(rmw && "expected atomicrmw for min/max compare capture");
6689 llvm::Value *oldVal = rmw;
6690 llvm::Value *rhs = rmw->getValOperand();
6691
6692 if (isFailOnly) {
6693 // The compare "failed" (the else branch runs) exactly when the
6694 // atomicrmw did not change x. Recompute the original update condition
6695 // on the old value and negate it. v is stored only in that case.
6696 llvm::CmpInst::Predicate updatePred;
6697 switch (rmw->getOperation()) {
6698 case llvm::AtomicRMWInst::Min:
6699 updatePred = llvm::CmpInst::ICMP_SGT;
6700 break;
6701 case llvm::AtomicRMWInst::Max:
6702 updatePred = llvm::CmpInst::ICMP_SLT;
6703 break;
6704 case llvm::AtomicRMWInst::UMin:
6705 updatePred = llvm::CmpInst::ICMP_UGT;
6706 break;
6707 case llvm::AtomicRMWInst::UMax:
6708 updatePred = llvm::CmpInst::ICMP_ULT;
6709 break;
6710 case llvm::AtomicRMWInst::FMin:
6711 updatePred = llvm::CmpInst::FCMP_OGT;
6712 break;
6713 case llvm::AtomicRMWInst::FMax:
6714 updatePred = llvm::CmpInst::FCMP_OLT;
6715 break;
6716 default:
6717 llvm_unreachable(
6718 "unexpected atomicrmw op for min/max compare capture");
6719 }
6720 llvm::Value *updated = builder.CreateCmp(updatePred, oldVal, rhs);
6721 llvm::Value *failed = builder.CreateNot(updated);
6722 llvm::Function *fn = curBB->getParent();
6723 llvm::BasicBlock *contBB = llvm::BasicBlock::Create(
6724 builder.getContext(), "omp.atomic.cont", fn);
6725 llvm::BasicBlock *exitBB = llvm::BasicBlock::Create(
6726 builder.getContext(), "omp.atomic.exit", fn);
6727 builder.CreateCondBr(failed, contBB, exitBB);
6728 builder.SetInsertPoint(contBB);
6729 builder.CreateStore(oldVal, llvmAtomicV.Var, llvmAtomicV.IsVolatile);
6730 builder.CreateBr(exitBB);
6731 builder.SetInsertPoint(exitBB);
6732 } else {
6733 llvm::Intrinsic::ID id;
6734 switch (rmw->getOperation()) {
6735 case llvm::AtomicRMWInst::Min:
6736 id = llvm::Intrinsic::smin;
6737 break;
6738 case llvm::AtomicRMWInst::Max:
6739 id = llvm::Intrinsic::smax;
6740 break;
6741 case llvm::AtomicRMWInst::UMin:
6742 id = llvm::Intrinsic::umin;
6743 break;
6744 case llvm::AtomicRMWInst::UMax:
6745 id = llvm::Intrinsic::umax;
6746 break;
6747 case llvm::AtomicRMWInst::FMin:
6748 id = llvm::Intrinsic::minnum;
6749 break;
6750 case llvm::AtomicRMWInst::FMax:
6751 id = llvm::Intrinsic::maxnum;
6752 break;
6753 default:
6754 llvm_unreachable(
6755 "unexpected atomicrmw op for min/max compare capture");
6756 }
6757 llvm::Value *newVal = builder.CreateBinaryIntrinsic(id, oldVal, rhs);
6758 builder.CreateStore(newVal, llvmAtomicV.Var, llvmAtomicV.IsVolatile);
6759 }
6760 }
6761
6762 // Equality postfix: v = select(success, D, old) — reconstructs the new
6763 // value of x from the cmpxchg result.
6764 if (!isMinMax && isPostfixCapture && !isFailOnly) {
6765 llvm::BasicBlock *curBB = builder.GetInsertBlock();
6766 llvm::Value *oldVal = nullptr;
6767 llvm::Value *successVal = nullptr;
6768
6769 // Integer path (and non-HandleFPNegZero FP path): a single cmpxchg
6770 // lives in the current block.
6771 for (auto &inst : llvm::reverse(*curBB)) {
6772 if (isa<llvm::AtomicCmpXchgInst>(&inst)) {
6773 oldVal = builder.CreateExtractValue(&inst, /*Idxs=*/0);
6774 successVal = builder.CreateExtractValue(&inst, /*Idxs=*/1);
6775 break;
6776 }
6777 }
6778
6779 // FP HandleFPNegZero path: the OMPIRBuilder emits a multi-block
6780 // structure (NaN / ±0.0 handling) with cmpxchg in predecessor
6781 // blocks. Results are merged via PHI nodes in the current (exit)
6782 // block: an i1 PHI for success and a bitcast of an integer PHI
6783 // for the old FP value.
6784 if (!oldVal) {
6785 for (auto &inst : *curBB) {
6786 auto *phi = dyn_cast<llvm::PHINode>(&inst);
6787 if (!phi)
6788 break;
6789 if (phi->getType()->isIntegerTy(1))
6790 successVal = phi;
6791 }
6792 for (auto &inst : *curBB) {
6793 if (auto *bc = dyn_cast<llvm::BitCastInst>(&inst)) {
6794 oldVal = bc;
6795 break;
6796 }
6797 }
6798 }
6799
6800 assert(oldVal && "expected cmpxchg or PHI+bitcast for compare capture");
6801 assert(successVal && "expected success flag for compare capture");
6802 llvm::Value *newVal = builder.CreateSelect(successVal, dVal, oldVal);
6803 builder.CreateStore(newVal, llvmAtomicV.Var, llvmAtomicV.IsVolatile);
6804 }
6805
6806 return success();
6807 }
6808
6809 mlir::Value mlirExpr;
6810 bool isXBinopExpr = false, isPostfixUpdate = false;
6811 llvm::AtomicRMWInst::BinOp binop = llvm::AtomicRMWInst::BinOp::BAD_BINOP;
6812
6813 assert((atomicUpdateOp || atomicWriteOp) &&
6814 "internal op must be an atomic.update or atomic.write op");
6815
6816 if (atomicWriteOp) {
6817 isPostfixUpdate = true;
6818 mlirExpr = atomicWriteOp.getExpr();
6819 } else {
6820 isPostfixUpdate = atomicCaptureOp.getSecondOp() ==
6821 atomicCaptureOp.getAtomicUpdateOp().getOperation();
6822 auto &innerOpList = atomicUpdateOp.getRegion().front().getOperations();
6823 // Find the binary update operation that uses the region argument
6824 // and get the expression to update
6825 if (innerOpList.size() == 2) {
6826 mlir::Operation &innerOp = *atomicUpdateOp.getRegion().front().begin();
6827 if (!llvm::is_contained(innerOp.getOperands(),
6828 atomicUpdateOp.getRegion().getArgument(0))) {
6829 return atomicUpdateOp.emitError(
6830 "no atomic update operation with region argument"
6831 " as operand found inside atomic.update region");
6832 }
6833 binop = convertBinOpToAtomic(innerOp);
6834 isXBinopExpr =
6835 innerOp.getOperand(0) == atomicUpdateOp.getRegion().getArgument(0);
6836 mlirExpr = (isXBinopExpr ? innerOp.getOperand(1) : innerOp.getOperand(0));
6837 } else {
6838 binop = llvm::AtomicRMWInst::BinOp::BAD_BINOP;
6839 }
6840 }
6841
6842 llvm::Value *llvmExpr = moduleTranslation.lookupValue(mlirExpr);
6843 llvm::Value *llvmX =
6844 moduleTranslation.lookupValue(atomicCaptureOp.getAtomicReadOp().getX());
6845 llvm::Value *llvmV =
6846 moduleTranslation.lookupValue(atomicCaptureOp.getAtomicReadOp().getV());
6847 llvm::Type *llvmXElementType = moduleTranslation.convertType(
6848 atomicCaptureOp.getAtomicReadOp().getElementType());
6849 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {llvmX, llvmXElementType,
6850 /*isSigned=*/false,
6851 /*isVolatile=*/false};
6852 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicV = {llvmV, llvmXElementType,
6853 /*isSigned=*/false,
6854 /*isVolatile=*/false};
6855
6856 llvm::AtomicOrdering atomicOrdering =
6857 convertAtomicOrdering(atomicCaptureOp.getMemoryOrder());
6858
6859 auto updateFn =
6860 [&](llvm::Value *atomicx,
6861 llvm::IRBuilder<> &builder) -> llvm::Expected<llvm::Value *> {
6862 if (atomicWriteOp)
6863 return moduleTranslation.lookupValue(atomicWriteOp.getExpr());
6864 Block &bb = *atomicUpdateOp.getRegion().begin();
6865 moduleTranslation.mapValue(*atomicUpdateOp.getRegion().args_begin(),
6866 atomicx);
6867 moduleTranslation.mapBlock(&bb, builder.GetInsertBlock());
6868 if (failed(moduleTranslation.convertBlock(bb, true, builder)))
6869 return llvm::make_error<PreviouslyReportedError>();
6870
6871 omp::YieldOp yieldop = dyn_cast<omp::YieldOp>(bb.getTerminator());
6872 assert(yieldop && yieldop.getResults().size() == 1 &&
6873 "terminator must be omp.yield op and it must have exactly one "
6874 "argument");
6875 return moduleTranslation.lookupValue(yieldop.getResults()[0]);
6876 };
6877
6878 bool isIgnoreDenormalMode;
6879 bool isFineGrainedMemory;
6880 bool isRemoteMemory;
6881 extractAtomicControlFlags(atomicUpdateOp, isIgnoreDenormalMode,
6882 isFineGrainedMemory, isRemoteMemory);
6883 // Handle ambiguous alloca, if any.
6884 auto allocaIP = findAllocInsertPoints(builder, moduleTranslation);
6885 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
6886 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
6887 ompBuilder->createAtomicCapture(
6888 ompLoc, allocaIP, llvmAtomicX, llvmAtomicV, llvmExpr, atomicOrdering,
6889 binop, updateFn, atomicUpdateOp, isPostfixUpdate, isXBinopExpr,
6890 isIgnoreDenormalMode, isFineGrainedMemory, isRemoteMemory);
6891
6892 if (failed(handleError(afterIP, *atomicCaptureOp)))
6893 return failure();
6894
6895 builder.restoreIP(*afterIP);
6896 return success();
6897}
6898
6899/// Converts an omp.atomic.compare operation to LLVM IR.
6900///
6901/// if (x == e) x = d
6902/// The region contains a comparison + select pattern:
6903/// ^bb0(%xval: T):
6904/// %cmp = llvm.icmp/fcmp <pred> %xval, %e : T
6905/// %sel = llvm.select %cmp, %d, %xval : i1, T
6906/// omp.yield(%sel : T)
6907///
6908/// From MLIR extract:
6909/// 1) comparison operator
6910/// 2) expected value (e)
6911/// 3) desired value (d)
6912/// These are passed to OpenMPIRBuilder::createAtomicCompare which generates
6913/// the actual cmpxchg / atomicrmw instruction.
6914///
6915static LogicalResult
6916convertOmpAtomicCompare(omp::AtomicCompareOp atomicCompareOp,
6917 llvm::IRBuilderBase &builder,
6918 LLVM::ModuleTranslation &moduleTranslation) {
6919 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
6920 if (failed(checkImplementationStatus(*atomicCompareOp)))
6921 return failure();
6922
6923 Region &region = atomicCompareOp.getRegion();
6924 Block &block = region.front();
6925
6926 // Determine element type from the region block argument
6927 llvm::Type *llvmXElementType =
6928 moduleTranslation.convertType(block.getArgument(0).getType());
6929 if (!llvmXElementType)
6930 return atomicCompareOp.emitError(
6931 "unable to determine element type for atomic compare");
6932
6933 llvm::Value *llvmX = moduleTranslation.lookupValue(atomicCompareOp.getX());
6934
6935 // IsSigned is determined from the comparison predicate in the region.
6936 // Signed ICmp predicates (slt/sgt) set this to true; unsigned (ult/ugt)
6937 // leave it false. For EQ and float comparisons, signedness is irrelevant.
6938 bool isSigned = false;
6939 llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {llvmX, llvmXElementType,
6940 isSigned,
6941 /*IsVolatile=*/false};
6942
6943 llvm::AtomicOrdering atomicOrdering =
6944 convertAtomicOrdering(atomicCompareOp.getMemoryOrder());
6945
6946 auto isAtomicComparePatternOp = [](Operation &op) {
6947 return llvm::isa<LLVM::ICmpOp, LLVM::FCmpOp, LLVM::SelectOp, LLVM::AndOp,
6948 LLVM::OrOp, LLVM::SMaxOp, LLVM::SMinOp, LLVM::UMaxOp,
6949 LLVM::UMinOp, LLVM::MaxNumOp, LLVM::MinNumOp,
6950 mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
6951 mlir::arith::MaxUIOp, mlir::arith::MinUIOp,
6952 mlir::arith::MaximumFOp, mlir::arith::MinimumFOp>(op);
6953 };
6954
6955 // Pre-translate operations inside the region that compute e and d (e.g.,
6956 // GEP, loads for dereferencing Fortran pointers) but are not part of the
6957 // atomic compare-and-swap pattern (icmp/fcmp, select, and/or).
6958 //
6959 // 1) Validity: The OpenMP spec requires e and d to be evaluated before the
6960 // atomic operation, so emitting their computation here is correct.
6961 // 2) Memory effects: These ops only depend on values defined outside the
6962 // region. They cannot observe the block argument (%xval), which is the
6963 // value loaded atomically by cmpxchg and does not exist yet.
6964 // 3) Invariant enforcement: The `allOperandsMapped` check below skips any
6965 // op whose operands include the unmapped block argument, guaranteeing
6966 // only region-external-dependent ops are pre-translated.
6967 for (Operation &op : block.without_terminator()) {
6968 // Skip operations that form the atomic compare pattern — these are
6969 // not emitted as individual instructions but are analyzed below to
6970 // extract the comparison predicate, expected value (e), and desired
6971 // value (d) for generating a single cmpxchg/atomicrmw.
6972 if (isAtomicComparePatternOp(op))
6973 continue;
6974
6975 // Avoid translating ops that depend on the unmapped block argument.
6976 bool allOperandsMapped = llvm::all_of(op.getOperands(), [&](mlir::Value v) {
6977 return moduleTranslation.lookupValue(v) != nullptr;
6978 });
6979 if (!allOperandsMapped)
6980 continue;
6981
6982 if (failed(moduleTranslation.convertOperation(op, builder)))
6983 return atomicCompareOp.emitError(
6984 "failed to translate operation inside atomic compare region");
6985 }
6986
6987 // Look up a value that may have been pre-translated or defined outside the
6988 // region.
6989 auto materializeValue = [&](mlir::Value val) -> llvm::Value * {
6990 // Check if the value is already mapped (pre-translated or defined outside).
6991 if (llvm::Value *existing = moduleTranslation.lookupValue(val))
6992 return existing;
6993 // Fallback for a single LoadOp whose address is mapped but whose result
6994 // was not pre-translated.
6995 if (auto loadOp = val.getDefiningOp<LLVM::LoadOp>()) {
6996 if (loadOp->getParentRegion() == &region) {
6997 llvm::Value *loadAddr = moduleTranslation.lookupValue(loadOp.getAddr());
6998 if (!loadAddr)
6999 return nullptr;
7000 llvm::Type *loadType =
7001 moduleTranslation.convertType(loadOp.getResult().getType());
7002 return builder.CreateLoad(loadType, loadAddr);
7003 }
7004 }
7005 return nullptr;
7006 };
7007
7008 // Walk the region to extract comparison predicate, eVal, and dVal.
7009 // if (x == eVal) x = dVal
7010 llvm::omp::OMPAtomicCompareOp compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
7011 llvm::Value *eVal = nullptr;
7012 llvm::Value *dVal = nullptr;
7013 bool isXBinopExpr = false;
7014
7015 // Check for a decomposed complex comparison pattern (extractvalue + fcmp +
7016 // and/or of the real/imaginary fields).
7018 bool isComplexPattern = cplx.isComplex;
7019 if (isComplexPattern) {
7020 if (cplx.isNE)
7021 // OrOp corresponds to NE, which is not a valid atomic compare op.
7022 return atomicCompareOp.emitError(
7023 "unsupported comparison predicate (NE) for complex atomic compare");
7024 compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
7025 isXBinopExpr = cplx.isXBinopExpr;
7026 eVal = materializeValue(cplx.eAggregate);
7027 }
7028
7029 if (isComplexPattern) {
7030 // dVal from SelectOp or YieldOp.
7031 for (Operation &op : block.getOperations()) {
7032 if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
7033 dVal = materializeValue(selectOp.getTrueValue());
7034 break;
7035 }
7036 }
7037 if (!dVal) {
7038 auto yieldOp = cast<omp::YieldOp>(block.getTerminator());
7039 if (yieldOp.getResults().empty())
7040 return atomicCompareOp.emitError(
7041 "failed to extract desired value (d) from atomic compare region");
7042 dVal = materializeValue(yieldOp.getResults()[0]);
7043 }
7044
7045 llvm::Value *oldComplex = nullptr;
7046 llvm::Value *cmpOk = nullptr;
7047 llvm::AtomicOrdering failOrdering =
7048 getAtomicCompareFailureOrdering(atomicCompareOp, atomicOrdering);
7049 emitComplexAtomicCmpXchg(builder, llvmX, llvmXElementType, eVal, dVal,
7050 atomicOrdering, failOrdering,
7051 atomicCompareOp.getWeak(), oldComplex, cmpOk);
7052 (void)oldComplex;
7053 (void)cmpOk;
7054
7055 // Emit flush after atomic compare if needed (for release, acq_rel,
7056 // seq_cst orderings).
7057 if (atomicOrdering == llvm::AtomicOrdering::Release ||
7058 atomicOrdering == llvm::AtomicOrdering::AcquireRelease ||
7059 atomicOrdering == llvm::AtomicOrdering::SequentiallyConsistent) {
7060 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
7061 ompBuilder->createFlush(ompLoc);
7062 }
7063 return success();
7064 } else {
7065 AtomicComparePatternInfo patternInfo;
7066 if (failed(extractAtomicComparePattern(block, materializeValue,
7067 atomicCompareOp, patternInfo)))
7068 return failure();
7069 compareOp = patternInfo.compareOp;
7070 eVal = patternInfo.eVal;
7071 dVal = patternInfo.dVal;
7072 isXBinopExpr = patternInfo.isXBinopExpr;
7073 isSigned = patternInfo.isSigned;
7074 }
7075
7076 if (!eVal)
7077 return atomicCompareOp.emitError(
7078 "failed to extract expected value (e) from atomic compare region");
7079 if (!dVal) {
7080 // Fall back to the yield operand.
7081 auto yieldOp = cast<omp::YieldOp>(block.getTerminator());
7082 if (yieldOp.getResults().empty())
7083 return atomicCompareOp.emitError(
7084 "failed to extract desired value (d) from atomic compare region");
7085 dVal = materializeValue(yieldOp.getResults()[0]);
7086 }
7087
7088 llvmAtomicX.IsSigned = isSigned;
7089
7090 llvm::OpenMPIRBuilder::AtomicOpValue vOpVal = {nullptr, nullptr, false,
7091 false};
7092 llvm::OpenMPIRBuilder::AtomicOpValue rOpVal = {nullptr, nullptr, false,
7093 false};
7094 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
7095
7096 bool isWeak = atomicCompareOp.getWeak();
7097
7098 bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
7099 llvm::AtomicOrdering failureOrdering =
7100 getAtomicCompareFailureOrdering(atomicCompareOp, atomicOrdering);
7101 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
7102 ompBuilder->createAtomicCompare(
7103 ompLoc, llvmAtomicX, vOpVal, rOpVal, eVal, dVal, atomicOrdering,
7104 compareOp, isXBinopExpr, /*IsPostfixUpdate=*/false,
7105 /*IsFailOnly=*/false, failureOrdering, isWeak);
7106 ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
7107
7108 if (failed(handleError(afterIP, *atomicCompareOp)))
7109 return failure();
7110
7111 builder.restoreIP(*afterIP);
7112 return success();
7113}
7114
7115static llvm::omp::Directive convertCancellationConstructType(
7116 omp::ClauseCancellationConstructType directive) {
7117 switch (directive) {
7118 case omp::ClauseCancellationConstructType::Loop:
7119 return llvm::omp::Directive::OMPD_for;
7120 case omp::ClauseCancellationConstructType::Parallel:
7121 return llvm::omp::Directive::OMPD_parallel;
7122 case omp::ClauseCancellationConstructType::Sections:
7123 return llvm::omp::Directive::OMPD_sections;
7124 case omp::ClauseCancellationConstructType::Taskgroup:
7125 return llvm::omp::Directive::OMPD_taskgroup;
7126 }
7127 llvm_unreachable("Unhandled cancellation construct type");
7128}
7129
7130static LogicalResult
7131convertOmpCancel(omp::CancelOp op, llvm::IRBuilderBase &builder,
7132 LLVM::ModuleTranslation &moduleTranslation) {
7133 if (failed(checkImplementationStatus(*op.getOperation())))
7134 return failure();
7135
7136 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
7137 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
7138
7139 llvm::Value *ifCond = nullptr;
7140 if (Value ifVar = op.getIfExpr())
7141 ifCond = moduleTranslation.lookupValue(ifVar);
7142
7143 llvm::omp::Directive cancelledDirective =
7144 convertCancellationConstructType(op.getCancelDirective());
7145
7146 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
7147 ompBuilder->createCancel(ompLoc, ifCond, cancelledDirective);
7148
7149 if (failed(handleError(afterIP, *op.getOperation())))
7150 return failure();
7151
7152 builder.restoreIP(afterIP.get());
7153
7154 return success();
7155}
7156
7157static LogicalResult
7158convertOmpCancellationPoint(omp::CancellationPointOp op,
7159 llvm::IRBuilderBase &builder,
7160 LLVM::ModuleTranslation &moduleTranslation) {
7161 if (failed(checkImplementationStatus(*op.getOperation())))
7162 return failure();
7163
7164 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
7165 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
7166
7167 llvm::omp::Directive cancelledDirective =
7168 convertCancellationConstructType(op.getCancelDirective());
7169
7170 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
7171 ompBuilder->createCancellationPoint(ompLoc, cancelledDirective);
7172
7173 if (failed(handleError(afterIP, *op.getOperation())))
7174 return failure();
7175
7176 builder.restoreIP(afterIP.get());
7177
7178 return success();
7179}
7180
7181/// Converts an OpenMP Threadprivate operation into LLVM IR using
7182/// OpenMPIRBuilder.
7183static LogicalResult
7184convertOmpThreadprivate(Operation &opInst, llvm::IRBuilderBase &builder,
7185 LLVM::ModuleTranslation &moduleTranslation) {
7186 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
7187 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
7188 auto threadprivateOp = cast<omp::ThreadprivateOp>(opInst);
7189
7190 if (failed(checkImplementationStatus(opInst)))
7191 return failure();
7192
7193 Value symAddr = threadprivateOp.getSymAddr();
7194 auto *symOp = symAddr.getDefiningOp();
7195
7196 if (auto asCast = dyn_cast<LLVM::AddrSpaceCastOp>(symOp))
7197 symOp = asCast.getOperand().getDefiningOp();
7198
7199 if (!isa<LLVM::AddressOfOp>(symOp))
7200 return opInst.emitError("Addressing symbol not found");
7201 LLVM::AddressOfOp addressOfOp = dyn_cast<LLVM::AddressOfOp>(symOp);
7202
7203 LLVM::GlobalOp global =
7204 addressOfOp.getGlobal(moduleTranslation.symbolTable());
7205 llvm::GlobalValue *globalValue = moduleTranslation.lookupGlobal(global);
7206 llvm::Type *type = globalValue->getValueType();
7207 llvm::TypeSize typeSize = builder.getDataLayout().getTypeStoreSize(type);
7208 llvm::ConstantInt *size = builder.getInt64(typeSize.getFixedValue());
7209 llvm::Value *callInst = ompBuilder->createCachedThreadPrivate(
7210 ompLoc, globalValue, size, global.getSymName() + ".cache");
7211 moduleTranslation.mapValue(opInst.getResult(0), callInst);
7212
7213 return success();
7214}
7215
7216static llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseKind
7217convertToDeviceClauseKind(mlir::omp::DeclareTargetDeviceType deviceClause) {
7218 switch (deviceClause) {
7219 case mlir::omp::DeclareTargetDeviceType::host:
7220 return llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseHost;
7221 break;
7222 case mlir::omp::DeclareTargetDeviceType::nohost:
7223 return llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseNoHost;
7224 break;
7225 case mlir::omp::DeclareTargetDeviceType::any:
7226 return llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseAny;
7227 break;
7228 }
7229 llvm_unreachable("unhandled device clause");
7230}
7231
7232static llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryKind
7234 mlir::omp::DeclareTargetCaptureClause captureClause) {
7235 switch (captureClause) {
7236 case mlir::omp::DeclareTargetCaptureClause::to:
7237 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryTo;
7238 case mlir::omp::DeclareTargetCaptureClause::link:
7239 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryLink;
7240 case mlir::omp::DeclareTargetCaptureClause::enter:
7241 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryEnter;
7242 case mlir::omp::DeclareTargetCaptureClause::none:
7243 return llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryNone;
7244 }
7245 llvm_unreachable("unhandled capture clause");
7246}
7247
7249 Operation *op = value.getDefiningOp();
7250 if (auto addrCast = dyn_cast_if_present<LLVM::AddrSpaceCastOp>(op))
7251 op = addrCast->getOperand(0).getDefiningOp();
7252 if (auto addressOfOp = dyn_cast_if_present<LLVM::AddressOfOp>(op)) {
7253 auto modOp = addressOfOp->getParentOfType<mlir::ModuleOp>();
7254 return modOp.lookupSymbol(addressOfOp.getGlobalName());
7255 }
7256 return nullptr;
7257}
7258
7260 while (Operation *op = value.getDefiningOp()) {
7261 if (auto addrCast = dyn_cast_if_present<LLVM::AddrSpaceCastOp>(op))
7262 value = addrCast.getOperand();
7263 // Traces through hlfir.declare, fir.declare to reach the base address and
7264 // use for type lookup.
7265 else if (op->getName().getIdentifier() &&
7266 (op->getName().getIdentifier().str() == "hlfir.declare" ||
7267 op->getName().getIdentifier().str() == "fir.declare")) {
7268 if (op->getNumOperands() > 0)
7269 value = op->getOperand(0);
7270 else
7271 break;
7272 } else {
7273 break;
7274 }
7275 }
7276 return value;
7277}
7278
7279// Determine the LLVM type whose storage size should be allocated for an
7280// OpenMP allocate directive list item. Opaque pointers lose element type, so
7281// trace through declare wrappers to the underlying global or stack allocation.
7282static llvm::Type *
7284 LLVM::ModuleTranslation &moduleTranslation) {
7285 llvm::Type *llvmVarTy = moduleTranslation.convertType(var.getType());
7286 if (!llvmVarTy->isPointerTy())
7287 return llvmVarTy;
7288
7289 if (Operation *globalOp = getGlobalOpFromValue(baseVar))
7290 if (auto gop = dyn_cast<LLVM::GlobalOp>(globalOp))
7291 return moduleTranslation.convertType(gop.getGlobalType());
7292
7293 if (auto allocaOp =
7294 dyn_cast_if_present<LLVM::AllocaOp>(baseVar.getDefiningOp()))
7295 return moduleTranslation.convertType(allocaOp.getElemType());
7296
7297 if (llvm::Value *baseLlvm = moduleTranslation.lookupValue(baseVar))
7298 if (auto *allocaInst = dyn_cast<llvm::AllocaInst>(baseLlvm))
7299 return allocaInst->getAllocatedType();
7300
7301 return llvmVarTy;
7302}
7303
7304// For dynamically-sized stack allocations, compute the allocation size from
7305// the alloca's element count at runtime.
7306static std::optional<llvm::Value *> getDynamicAllocatedSize(
7307 Value var, Value baseVar, LLVM::ModuleTranslation &moduleTranslation,
7308 llvm::IRBuilderBase &builder, const llvm::DataLayout &dataLayout) {
7309 if (auto allocaOp =
7310 dyn_cast_if_present<LLVM::AllocaOp>(baseVar.getDefiningOp())) {
7311 if (Value arraySize = allocaOp.getArraySize()) {
7312 llvm::Type *elemTy =
7313 moduleTranslation.convertType(allocaOp.getElemType());
7314 llvm::Value *numElems = moduleTranslation.lookupValue(arraySize);
7315 if (!numElems->getType()->isIntegerTy(64))
7316 numElems = builder.CreateZExt(numElems, builder.getInt64Ty());
7317 uint64_t elemSize = dataLayout.getTypeAllocSize(elemTy).getFixedValue();
7318 return builder.CreateMul(numElems, builder.getInt64(elemSize));
7319 }
7320 }
7321 if (llvm::Value *baseLlvm = moduleTranslation.lookupValue(baseVar)) {
7322 if (auto *allocaInst = dyn_cast<llvm::AllocaInst>(baseLlvm)) {
7323 if (allocaInst->isArrayAllocation() &&
7324 !llvm::isa<llvm::ArrayType>(allocaInst->getAllocatedType())) {
7325 uint64_t elemSize =
7326 dataLayout.getTypeAllocSize(allocaInst->getAllocatedType())
7327 .getFixedValue();
7328 return builder.CreateMul(allocaInst->getArraySize(),
7329 builder.getInt64(elemSize));
7330 }
7331 }
7332 }
7333 return std::nullopt;
7334}
7335
7336static llvm::SmallString<64>
7337getDeclareTargetRefPtrSuffix(LLVM::GlobalOp globalOp,
7338 llvm::OpenMPIRBuilder &ompBuilder,
7339 llvm::vfs::FileSystem &vfs) {
7340 llvm::SmallString<64> suffix;
7341 llvm::raw_svector_ostream os(suffix);
7342 if (globalOp.getVisibility() == mlir::SymbolTable::Visibility::Private) {
7343 auto loc = globalOp->getLoc()->findInstanceOf<FileLineColLoc>();
7344 auto fileInfoCallBack = [&loc]() {
7345 return std::pair<std::string, uint64_t>(
7346 llvm::StringRef(loc.getFilename()), loc.getLine());
7347 };
7348
7349 os << llvm::format(
7350 "_%x",
7351 ompBuilder.getTargetEntryUniqueInfo(fileInfoCallBack, vfs).FileID);
7352 }
7353 os << "_decl_tgt_ref_ptr";
7354
7355 return suffix;
7356}
7357
7358static bool isDeclareTargetLink(Value value) {
7359 if (auto declareTargetGlobal =
7360 dyn_cast_if_present<omp::DeclareTargetInterface>(
7361 getGlobalOpFromValue(value))) {
7362 omp::DeclareTargetAttr declareTargetAttr =
7363 declareTargetGlobal.getDeclareTarget();
7364 if (declareTargetAttr && declareTargetAttr.getCaptureClause() ==
7365 omp::DeclareTargetCaptureClause::link)
7366 return true;
7367 }
7368 return false;
7369}
7370
7371static bool isDeclareTargetTo(Value value) {
7372 if (auto declareTargetGlobal =
7373 dyn_cast_if_present<omp::DeclareTargetInterface>(
7374 getGlobalOpFromValue(value))) {
7375 omp::DeclareTargetAttr declareTargetAttr =
7376 declareTargetGlobal.getDeclareTarget();
7377 if (declareTargetAttr && (declareTargetAttr.getCaptureClause() ==
7378 omp::DeclareTargetCaptureClause::to ||
7379 declareTargetAttr.getCaptureClause() ==
7380 omp::DeclareTargetCaptureClause::enter))
7381 return true;
7382 }
7383 return false;
7384}
7385
7386// Returns the reference pointer generated by the lowering of the declare
7387// target operation in cases where the link clause is used or the to clause is
7388// used in USM mode.
7389static llvm::Value *
7391 LLVM::ModuleTranslation &moduleTranslation) {
7392 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
7393 if (auto gOp =
7394 dyn_cast_or_null<LLVM::GlobalOp>(getGlobalOpFromValue(value))) {
7395 // In this case, we must utilise the reference pointer generated by
7396 // the declare target operation, similar to Clang
7397 if (isDeclareTargetLink(value) ||
7398 (isDeclareTargetTo(value) &&
7399 ompBuilder->Config.hasRequiresUnifiedSharedMemory())) {
7401 gOp, *ompBuilder, moduleTranslation.getFileSystem());
7402
7403 if (gOp.getSymName().contains(suffix))
7404 return moduleTranslation.getLLVMModule()->getNamedValue(
7405 gOp.getSymName());
7406
7407 return moduleTranslation.getLLVMModule()->getNamedValue(
7408 (gOp.getSymName().str() + suffix.str()).str());
7409 }
7410 }
7411 return nullptr;
7412}
7413
7414namespace {
7415// Append customMappers information to existing MapInfosTy
7416struct MapInfosTy : llvm::OpenMPIRBuilder::MapInfosTy {
7417 SmallVector<Operation *, 4> Mappers;
7418
7419 /// Append arrays in \a CurInfo.
7420 void append(MapInfosTy &curInfo) {
7421 Mappers.append(curInfo.Mappers.begin(), curInfo.Mappers.end());
7422 llvm::OpenMPIRBuilder::MapInfosTy::append(curInfo);
7423 }
7424};
7425// A small helper structure to contain data gathered
7426// for map lowering and coalese it into one area and
7427// avoiding extra computations such as searches in the
7428// llvm module for lowered mapped variables or checking
7429// if something is declare target (and retrieving the
7430// value) more than neccessary.
7431struct MapInfoData : MapInfosTy {
7432 llvm::SmallVector<bool, 4> IsDeclareTarget;
7433 llvm::SmallVector<bool, 4> IsAMember;
7434 // Identify if mapping was added by mapClause or use_device clauses.
7435 llvm::SmallVector<bool, 4> IsAMapping;
7436 llvm::SmallVector<mlir::Operation *, 4> MapClause;
7437 llvm::SmallVector<llvm::Value *, 4> OriginalValue;
7438 // Stripped off array/pointer to get the underlying
7439 // element type
7440 llvm::SmallVector<llvm::Type *, 4> BaseType;
7441
7442 /// Append arrays in \a CurInfo.
7443 void append(MapInfoData &CurInfo) {
7444 IsDeclareTarget.append(CurInfo.IsDeclareTarget.begin(),
7445 CurInfo.IsDeclareTarget.end());
7446 MapClause.append(CurInfo.MapClause.begin(), CurInfo.MapClause.end());
7447 OriginalValue.append(CurInfo.OriginalValue.begin(),
7448 CurInfo.OriginalValue.end());
7449 BaseType.append(CurInfo.BaseType.begin(), CurInfo.BaseType.end());
7450 MapInfosTy::append(CurInfo);
7451 }
7452};
7453
7454enum class TargetDirectiveEnumTy : uint32_t {
7455 None = 0,
7456 Target = 1,
7457 TargetData = 2,
7458 TargetEnterData = 3,
7459 TargetExitData = 4,
7460 TargetUpdate = 5
7461};
7462
7463static TargetDirectiveEnumTy getTargetDirectiveEnumTyFromOp(Operation *op) {
7464 return llvm::TypeSwitch<Operation *, TargetDirectiveEnumTy>(op)
7465 .Case([](omp::TargetDataOp) { return TargetDirectiveEnumTy::TargetData; })
7466 .Case([](omp::TargetEnterDataOp) {
7467 return TargetDirectiveEnumTy::TargetEnterData;
7468 })
7469 .Case([&](omp::TargetExitDataOp) {
7470 return TargetDirectiveEnumTy::TargetExitData;
7471 })
7472 .Case([&](omp::TargetUpdateOp) {
7473 return TargetDirectiveEnumTy::TargetUpdate;
7474 })
7475 .Case([&](omp::TargetOp) { return TargetDirectiveEnumTy::Target; })
7476 .Default([&](Operation *op) { return TargetDirectiveEnumTy::None; });
7477}
7478} // namespace
7479
7480static uint64_t getArrayElementSizeInBits(LLVM::LLVMArrayType arrTy,
7481 DataLayout &dl) {
7482 if (auto nestedArrTy = llvm::dyn_cast_if_present<LLVM::LLVMArrayType>(
7483 arrTy.getElementType()))
7484 return getArrayElementSizeInBits(nestedArrTy, dl);
7485 return dl.getTypeSizeInBits(arrTy.getElementType());
7486}
7487
7488// The intent is to verify if the mapped data being passed is a
7489// pointer -> pointee that requires special handling in certain cases,
7490// e.g. applying the OMP_MAP_PTR_AND_OBJ map type.
7491//
7492// There may be a better way to verify this, but unfortunately with
7493// opaque pointers we lose the ability to easily check if something is
7494// a pointer whilst maintaining access to the underlying type.
7495static bool checkIfPointerMap(omp::MapInfoOp mapOp) {
7496 // If we have a varPtrPtr field assigned then the underlying type is a pointer
7497 if (mapOp.getVarPtrPtr())
7498 return true;
7499
7500 // If the map data is declare target with a link clause, then it's represented
7501 // as a pointer when we lower it to LLVM-IR even if at the MLIR level it has
7502 // no relation to pointers.
7503 if (isDeclareTargetLink(mapOp.getVarPtr()))
7504 return true;
7505
7506 return false;
7507}
7508
7509// A privatizeable attach map is a pointer/descriptor that is privatized and
7510// passed directly as a kernel argument (target_param) rather than undergoing
7511// the standard attach/parent mapping. These are handled specially in a couple
7512// of places in map lowering.
7513static bool isPrivatizeableAttachMap(omp::ClauseMapFlags mapType) {
7514 return bitEnumContainsAll(mapType, omp::ClauseMapFlags::priv |
7515 omp::ClauseMapFlags::target_param |
7516 omp::ClauseMapFlags::attach);
7517}
7518
7519// This function calculates the size to be offloaded for a specified type, given
7520// its associated map clause (which can contain bounds information which affects
7521// the total size), this size is calculated based on the underlying element type
7522// e.g. given a 1-D array of ints, we will calculate the size from the integer
7523// type * number of elements in the array. This size can be used in other
7524// calculations but is ultimately used as an argument to the OpenMP runtimes
7525// kernel argument structure which is generated through the combinedInfo data
7526// structures.
7527// This function is somewhat equivalent to Clang's getExprTypeSize inside of
7528// CGOpenMPRuntime.cpp.
7529static llvm::Value *getSizeInBytes(DataLayout &dl, const mlir::Type &type,
7530 Operation *clauseOp,
7531 llvm::Value *basePointer,
7532 llvm::Type *baseType,
7533 llvm::IRBuilderBase &builder,
7534 LLVM::ModuleTranslation &moduleTranslation) {
7535 if (auto memberClause =
7536 mlir::dyn_cast_if_present<mlir::omp::MapInfoOp>(clauseOp)) {
7537 // This calculates the size to transfer based on bounds and the underlying
7538 // element type, provided bounds have been specified (Fortran
7539 // pointers/allocatables/target and arrays that have sections specified fall
7540 // into this as well)
7541 if (!memberClause.getBounds().empty()) {
7542 llvm::Value *elementCount = builder.getInt64(1);
7543 for (auto bounds : memberClause.getBounds()) {
7544 if (auto boundOp = mlir::dyn_cast_if_present<mlir::omp::MapBoundsOp>(
7545 bounds.getDefiningOp())) {
7546 // The below calculation for the size to be mapped calculated from the
7547 // map.info's bounds is: (elemCount * [UB - LB] + 1), later we
7548 // multiply by the underlying element types byte size to get the full
7549 // size to be offloaded based on the bounds
7550 elementCount = builder.CreateMul(
7551 elementCount,
7552 builder.CreateAdd(
7553 builder.CreateSub(
7554 moduleTranslation.lookupValue(boundOp.getUpperBound()),
7555 moduleTranslation.lookupValue(boundOp.getLowerBound())),
7556 builder.getInt64(1)));
7557 }
7558 }
7559
7560 // utilising getTypeSizeInBits instead of getTypeSize as getTypeSize gives
7561 // the size in inconsistent byte or bit format.
7562 uint64_t underlyingTypeSzInBits = dl.getTypeSizeInBits(type);
7563 if (auto arrTy = llvm::dyn_cast_if_present<LLVM::LLVMArrayType>(type))
7564 underlyingTypeSzInBits = getArrayElementSizeInBits(arrTy, dl);
7565
7566 // The size in bytes x number of elements, the sizeInBytes stored is
7567 // the underyling types size, e.g. if ptr<i32>, it'll be the i32's
7568 // size, so we do some on the fly runtime math to get the size in
7569 // bytes from the extent (ub - lb) * sizeInBytes. NOTE: This may need
7570 // some adjustment for members with more complex types.
7571 llvm::Value *sizeCalc = builder.CreateMul(
7572 elementCount, builder.getInt64(underlyingTypeSzInBits / 8),
7573 "element_count");
7574
7575 // This is a part of a "complicated" bit of size calculation logic that is
7576 // in place to handle a couple of scenarios, one specific to Fortran and
7577 // the other a more general OpenMP issue. The other piece of the
7578 // calculation can be found as the final size calculation within the
7579 // processIndividualMap function. Ideally we would move it here, but due
7580 // to the complexity of calculating the final base address of some
7581 // constructs (required for a nullary check), it's left as the final step.
7582 // So, in the below 2 cases, the nullary check is in processIndividualMap
7583 // and the size equality check is here. The cases this modifications help
7584 // cover are:
7585 //
7586 // 1) If an argument has a null base pointer, then the size must be set to
7587 // 0 to avoid the runtime exploding/complaining about an illegal
7588 // pointer map. The size returning non-zero is feasible in certain
7589 // cases if for example someone has specified there own bounds/range.
7590 // 2) We wish to support a very specific OpenMP Fortran edge-case where a
7591 // size zero array can be legally presence checked and found to be on
7592 // device when it has been mapped. In these rare occasions the
7593 // allocatable/pointer will have a size of 1 allocated for the
7594 // underlying data, but this wall not be represented within the size of
7595 // the descriptor, so we get a non-nullary pointer and a size of 0,
7596 // allowing us to specify a size of 1 in these cases registering it on
7597 // the device mapping table as present.
7598 //
7599 // The default fall through case is just returning the size calculation
7600 // above, if we are not nullary and the size we calculate is non-zero,
7601 // which is basically any pointer type that is allocated in someway
7602 // (providing you are not running on a rare system that allows malloc's of
7603 // size 0 with whatever caveats that may come with).
7604 //
7605 // Later in the nullary check in processIndividualMap it just devolves to
7606 // selecting a size of 0 if we are nullary, if we are not, we will return
7607 // either 1 or the calculated size, depending on the outcome of this
7608 // select.
7609 if (checkIfPointerMap(memberClause)) {
7610 return builder.CreateSelect(
7611 builder.CreateICmpEQ(sizeCalc, builder.getInt64(0)),
7612 builder.getInt64(1), sizeCalc);
7613 }
7614
7615 return sizeCalc;
7616 }
7617 }
7618
7619 return builder.getInt64(dl.getTypeSizeInBits(type) / 8);
7620}
7621
7622// Convert the MLIR map flag set to the runtime map flag set for embedding
7623// in LLVM-IR. This is important as the two bit-flag lists do not correspond
7624// 1-to-1 as there's flags the runtime doesn't care about and vice versa.
7625// Certain flags are discarded here such as RefPtee and co.
7626static llvm::omp::OpenMPOffloadMappingFlags
7627convertClauseMapFlags(omp::ClauseMapFlags mlirFlags) {
7628 const bool hasExplicitMap =
7629 (mlirFlags & ~omp::ClauseMapFlags::is_device_ptr) !=
7630 omp::ClauseMapFlags::none;
7631
7632 llvm::omp::OpenMPOffloadMappingFlags mapType =
7633 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE;
7634
7635 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::to))
7636 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TO;
7637
7638 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::from))
7639 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_FROM;
7640
7641 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::always))
7642 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ALWAYS;
7643
7644 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::del))
7645 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_DELETE;
7646
7647 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::return_param))
7648 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
7649
7650 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::priv))
7651 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_PRIVATE;
7652
7653 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::literal))
7654 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL;
7655
7656 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::implicit))
7657 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_IMPLICIT;
7658
7659 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::close))
7660 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_CLOSE;
7661
7662 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::present))
7663 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_PRESENT;
7664
7665 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::ompx_hold))
7666 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_OMPX_HOLD;
7667
7668 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::attach))
7669 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH;
7670
7671 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::target_param))
7672 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TARGET_PARAM;
7673
7674 if (bitEnumContainsAll(mlirFlags, omp::ClauseMapFlags::is_device_ptr)) {
7675 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TARGET_PARAM;
7676 if (!hasExplicitMap)
7677 mapType |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL;
7678 }
7679
7680 return mapType;
7681}
7682
7683static StringRef getMapClauseName(omp::MapInfoOp mapOp) {
7684 if (StringAttr name = mapOp.getNameAttr())
7685 return name.getValue();
7686 return {};
7687}
7688
7690 MapInfoData &mapData, SmallVectorImpl<Value> &mapVars,
7691 LLVM::ModuleTranslation &moduleTranslation, DataLayout &dl,
7692 llvm::IRBuilderBase &builder, ArrayRef<Value> useDevPtrOperands = {},
7693 ArrayRef<Value> useDevAddrOperands = {},
7694 ArrayRef<Value> hasDevAddrOperands = {}) {
7695
7696 auto checkRefPtrOrPteeMapWithAttach = [](omp::ClauseMapFlags mapType) {
7697 bool hasRefType =
7698 bitEnumContainsAll(mapType, omp::ClauseMapFlags::ref_ptr) ||
7699 bitEnumContainsAll(mapType, omp::ClauseMapFlags::ref_ptee);
7700 return hasRefType &&
7701 bitEnumContainsAll(mapType, omp::ClauseMapFlags::attach);
7702 };
7703
7704 auto checkIsAMember = [](const auto &mapVars, auto mapOp) {
7705 // Check if this is a member mapping and correctly assign that it is, if
7706 // it is a member of a larger object.
7707 // TODO: Need better handling of members, and distinguishing of members
7708 // that are implicitly allocated on device vs explicitly passed in as
7709 // arguments.
7710 // TODO: May require some further additions to support nested record
7711 // types, i.e. member maps that can have member maps.
7712 for (Value mapValue : mapVars) {
7713 auto map = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
7714 for (auto member : map.getMembers())
7715 if (member == mapOp)
7716 return true;
7717 }
7718 return false;
7719 };
7720
7721 // Process MapOperands
7722 for (Value mapValue : mapVars) {
7723 auto mapOp = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
7724 bool isAttachStyleMap =
7725 checkRefPtrOrPteeMapWithAttach(mapOp.getMapType()) ||
7726 isPrivatizeableAttachMap(mapOp.getMapType());
7727 Value offloadPtr = (mapOp.getVarPtrPtr() && !isAttachStyleMap)
7728 ? mapOp.getVarPtrPtr()
7729 : mapOp.getVarPtr();
7730 mapData.OriginalValue.push_back(moduleTranslation.lookupValue(offloadPtr));
7731 mapData.Pointers.push_back(
7732 isAttachStyleMap ? moduleTranslation.lookupValue(mapOp.getVarPtrPtr())
7733 : mapData.OriginalValue.back());
7734
7735 if (llvm::Value *refPtr =
7736 getRefPtrIfDeclareTarget(offloadPtr, moduleTranslation)) {
7737 mapData.IsDeclareTarget.push_back(true);
7738 mapData.BasePointers.push_back(refPtr);
7739 } else if (isDeclareTargetTo(offloadPtr)) {
7740 mapData.IsDeclareTarget.push_back(true);
7741 mapData.BasePointers.push_back(mapData.OriginalValue.back());
7742 } else { // regular mapped variable
7743 mapData.IsDeclareTarget.push_back(false);
7744 mapData.BasePointers.push_back(mapData.OriginalValue.back());
7745 }
7746
7747 // In every situation we currently have if we have a varPtrPtr present
7748 // we wish to utilise it's type for the base type, main cases are
7749 // currently Fortran descriptor base address maps and attach maps.
7750 mapData.BaseType.push_back(moduleTranslation.convertType(
7751 mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtrType().value()
7752 : mapOp.getVarPtrType()));
7753
7754 // For the attach map cases, it's a little odd, as we effectively have to
7755 // utilise the base address (including all bounds offsets) for the pointer
7756 // field, the pointer address for the base address field, and the pointer
7757 // not the data (base addresses) size. So we end up with a mix of base
7758 // types and sizes we wish to insert here.
7759 mlir::Type sizeType = (isAttachStyleMap || !mapOp.getVarPtrPtr())
7760 ? mapOp.getVarPtrType()
7761 : mapOp.getVarPtrPtrType().value();
7762 mapData.Sizes.push_back(getSizeInBytes(
7763 dl, sizeType, isAttachStyleMap ? nullptr : mapOp,
7764 mapData.Pointers.back(), moduleTranslation.convertType(sizeType),
7765 builder, moduleTranslation));
7766 mapData.MapClause.push_back(mapOp.getOperation());
7767 mapData.Types.push_back(convertClauseMapFlags(mapOp.getMapType()));
7768 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7769 mapData.HasAttachPtr.push_back(false);
7770 mapData.Names.push_back(LLVM::createMappingInformation(
7771 mapOp.getLoc(), *getOpenMPBuilder(moduleTranslation),
7772 getMapClauseName(mapOp)));
7773 mapData.DevicePointers.push_back(llvm::OpenMPIRBuilder::DeviceInfoTy::None);
7774 if (mapOp.getMapperId())
7775 mapData.Mappers.push_back(
7777 mapOp, mapOp.getMapperIdAttr()));
7778 else
7779 mapData.Mappers.push_back(nullptr);
7780 mapData.IsAMapping.push_back(true);
7781 mapData.IsAMember.push_back(checkIsAMember(mapVars, mapOp));
7782 }
7783
7784 auto findMapInfo = [&mapData](llvm::Value *val,
7785 llvm::OpenMPIRBuilder::DeviceInfoTy devInfoTy,
7786 size_t memberCount) {
7787 unsigned index = 0;
7788 bool found = false;
7789 for (llvm::Value *basePtr : mapData.OriginalValue) {
7790 auto mapOp = cast<omp::MapInfoOp>(mapData.MapClause[index]);
7791 // TODO: Currently we define an equivalent mapping as
7792 // the same base pointer and an equivalent member count, but
7793 // that is a loose definition. We may have to extend to check
7794 // for other fields (varPtrPtr/individual members being mapped).
7795 // Note: Attach maps are not the same as a normal data transfer
7796 // they specify to the runtime to perform an attach map and they
7797 // (at least at the moment) are never something we would aim to
7798 // return in a use_dev_* clause, so they are skipped in terms of
7799 // duplicate maps.
7800 bool isAttachMap =
7801 (mapData.Types[index] &
7802 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) ==
7803 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH;
7804 if (!isAttachMap && basePtr == val && mapData.IsAMapping[index] &&
7805 memberCount == mapOp.getMembers().size()) {
7806 found = true;
7807 mapData.Types[index] |=
7808 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
7809 mapData.DevicePointers[index] = devInfoTy;
7810 }
7811 index++;
7812 }
7813 return found;
7814 };
7815
7816 // Process useDevPtr(Addr)Operands
7817 auto addDevInfos = [&](const llvm::ArrayRef<Value> &useDevOperands,
7818 llvm::OpenMPIRBuilder::DeviceInfoTy devInfoTy) {
7819 for (Value mapValue : useDevOperands) {
7820 auto mapOp = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
7821 Value offloadPtr =
7822 mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtr() : mapOp.getVarPtr();
7823 llvm::Value *origValue = moduleTranslation.lookupValue(offloadPtr);
7824
7825 // Check if map info is already present for this entry.
7826 if (!findMapInfo(origValue, devInfoTy, mapOp.getMembers().size())) {
7827 mapData.OriginalValue.push_back(origValue);
7828 mapData.Pointers.push_back(mapData.OriginalValue.back());
7829 mapData.IsDeclareTarget.push_back(false);
7830 mapData.BasePointers.push_back(mapData.OriginalValue.back());
7831 mlir::Type baseTy = mapOp.getVarPtrPtr()
7832 ? mapOp.getVarPtrPtrType().value()
7833 : mapOp.getVarPtrType();
7834 mapData.BaseType.push_back(moduleTranslation.convertType(baseTy));
7835 mapData.Sizes.push_back(builder.getInt64(0));
7836 mapData.MapClause.push_back(mapOp.getOperation());
7837 mapData.Types.push_back(
7838 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM);
7839 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7840 mapData.HasAttachPtr.push_back(false);
7841 mapData.Names.push_back(LLVM::createMappingInformation(
7842 mapOp.getLoc(), *getOpenMPBuilder(moduleTranslation),
7843 getMapClauseName(mapOp)));
7844 mapData.DevicePointers.push_back(devInfoTy);
7845 mapData.Mappers.push_back(nullptr);
7846 mapData.IsAMapping.push_back(false);
7847 mapData.IsAMember.push_back(checkIsAMember(useDevOperands, mapOp));
7848 }
7849 }
7850 };
7851
7852 addDevInfos(useDevAddrOperands, llvm::OpenMPIRBuilder::DeviceInfoTy::Address);
7853 addDevInfos(useDevPtrOperands, llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer);
7854
7855 for (Value mapValue : hasDevAddrOperands) {
7856 auto mapOp = cast<omp::MapInfoOp>(mapValue.getDefiningOp());
7857 Value offloadPtr =
7858 mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtr() : mapOp.getVarPtr();
7859 llvm::Value *origValue = moduleTranslation.lookupValue(offloadPtr);
7860 auto mapType = convertClauseMapFlags(mapOp.getMapType());
7861 auto mapTypeAlways = llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ALWAYS;
7862 bool isDevicePtr =
7863 (mapOp.getMapType() & omp::ClauseMapFlags::is_device_ptr) !=
7864 omp::ClauseMapFlags::none;
7865
7866 mapData.OriginalValue.push_back(origValue);
7867 mapData.BasePointers.push_back(origValue);
7868 mapData.Pointers.push_back(origValue);
7869 mapData.IsDeclareTarget.push_back(false);
7870
7871 mlir::Type baseTy = mapOp.getVarPtrPtr() ? mapOp.getVarPtrPtrType().value()
7872 : mapOp.getVarPtrType();
7873 mapData.BaseType.push_back(moduleTranslation.convertType(baseTy));
7874 mapData.Sizes.push_back(builder.getInt64(dl.getTypeSize(baseTy)));
7875
7876 mapData.MapClause.push_back(mapOp.getOperation());
7877 if (llvm::to_underlying(mapType & mapTypeAlways)) {
7878 // Descriptors are mapped with the ALWAYS flag, since they can get
7879 // rematerialized, so the address of the decriptor for a given object
7880 // may change from one place to another.
7881 mapData.Types.push_back(mapType);
7882 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7883 mapData.HasAttachPtr.push_back(false);
7884 // Technically it's possible for a non-descriptor mapping to have
7885 // both has-device-addr and ALWAYS, so lookup the mapper in case it
7886 // exists.
7887 if (mapOp.getMapperId()) {
7888 mapData.Mappers.push_back(
7890 mapOp, mapOp.getMapperIdAttr()));
7891 } else {
7892 mapData.Mappers.push_back(nullptr);
7893 }
7894 } else {
7895 // For is_device_ptr we need the map type to propagate so the runtime
7896 // can materialize the device-side copy of the pointer container.
7897 mapData.Types.push_back(
7898 isDevicePtr ? mapType
7899 : llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL);
7900 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
7901 mapData.HasAttachPtr.push_back(false);
7902 mapData.Mappers.push_back(nullptr);
7903 }
7904 mapData.Names.push_back(LLVM::createMappingInformation(
7905 mapOp.getLoc(), *getOpenMPBuilder(moduleTranslation),
7906 getMapClauseName(mapOp)));
7907 mapData.DevicePointers.push_back(
7908 isDevicePtr ? llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer
7909 : llvm::OpenMPIRBuilder::DeviceInfoTy::Address);
7910 mapData.IsAMapping.push_back(false);
7911 mapData.IsAMember.push_back(checkIsAMember(hasDevAddrOperands, mapOp));
7912 }
7913}
7914
7915static int getMapDataMemberIdx(MapInfoData &mapData, omp::MapInfoOp memberOp) {
7916 auto *res = llvm::find(mapData.MapClause, memberOp);
7917 assert(res != mapData.MapClause.end() &&
7918 "MapInfoOp for member not found in MapData, cannot return index");
7919 return std::distance(mapData.MapClause.begin(), res);
7920}
7921
7923 omp::MapInfoOp mapInfo, bool first = true) {
7924 ArrayAttr indexAttr = mapInfo.getMembersIndexAttr();
7925 llvm::SmallVector<size_t> occludedChildren;
7926 llvm::sort(
7927 indices.begin(), indices.end(), [&](const size_t a, const size_t b) {
7928 // Bail early if we are asked to look at the same index. If we do not
7929 // bail early, we can end up mistakenly adding indices to
7930 // occludedChildren. This can occur with some types of libc++ hardening.
7931 if (a == b)
7932 return false;
7933
7934 auto memberIndicesA = cast<ArrayAttr>(indexAttr[a]);
7935 auto memberIndicesB = cast<ArrayAttr>(indexAttr[b]);
7936
7937 for (auto it : llvm::zip(memberIndicesA, memberIndicesB)) {
7938 int64_t aIndex = mlir::cast<IntegerAttr>(std::get<0>(it)).getInt();
7939 int64_t bIndex = mlir::cast<IntegerAttr>(std::get<1>(it)).getInt();
7940
7941 if (aIndex == bIndex)
7942 continue;
7943
7944 if (aIndex < bIndex)
7945 return first;
7946
7947 if (aIndex > bIndex)
7948 return !first;
7949 }
7950
7951 // Iterated up until the end of the smallest member and
7952 // they were found to be equal up to that point, so select
7953 // the member with the lowest index count, so the "parent"
7954 bool memberAParent = memberIndicesA.size() < memberIndicesB.size();
7955 if (memberAParent)
7956 occludedChildren.push_back(b);
7957 else
7958 occludedChildren.push_back(a);
7959 return memberAParent;
7960 });
7961
7962 for (auto v : occludedChildren)
7963 indices.erase(std::remove(indices.begin(), indices.end(), v),
7964 indices.end());
7965}
7966
7967static omp::MapInfoOp getFirstOrLastMappedMemberPtr(omp::MapInfoOp mapInfo,
7968 bool first) {
7969 ArrayAttr indexAttr = mapInfo.getMembersIndexAttr();
7970 // Only 1 member has been mapped, we can return it.
7971 if (indexAttr.size() == 1)
7972 return cast<omp::MapInfoOp>(mapInfo.getMembers()[0].getDefiningOp());
7973 llvm::SmallVector<size_t> indices(indexAttr.size());
7974 std::iota(indices.begin(), indices.end(), 0);
7975 sortMapIndices(indices, mapInfo, first);
7976 return llvm::cast<omp::MapInfoOp>(
7977 mapInfo.getMembers()[indices.front()].getDefiningOp());
7978}
7979
7980/// This function calculates the array/pointer offset for map data provided
7981/// with bounds operations, e.g. when provided something like the following:
7982///
7983/// Fortran
7984/// map(tofrom: array(2:5, 3:2))
7985///
7986/// We must calculate the initial pointer offset to pass across, this function
7987/// performs this using bounds.
7988///
7989/// TODO/WARNING: This only supports Fortran's column major indexing currently
7990/// as is noted in the note below and comments in the function, we must extend
7991/// this function when we add a C++ frontend.
7992/// NOTE: which while specified in row-major order it currently needs to be
7993/// flipped for Fortran's column order array allocation and access (as
7994/// opposed to C++'s row-major, hence the backwards processing where order is
7995/// important). This is likely important to keep in mind for the future when
7996/// we incorporate a C++ frontend, both frontends will need to agree on the
7997/// ordering of generated bounds operations (one may have to flip them) to
7998/// make the below lowering frontend agnostic. The offload size
7999/// calcualtion may also have to be adjusted for C++.
8000static std::vector<llvm::Value *>
8002 llvm::IRBuilderBase &builder, bool isArrayTy,
8003 OperandRange bounds, bool &isByteOffset) {
8004 std::vector<llvm::Value *> idx;
8005 isByteOffset = false;
8006 // There's no bounds to calculate an offset from, we can safely
8007 // ignore and return no indices.
8008 if (bounds.empty())
8009 return idx;
8010
8011 // If we have an array type, then we have its type so can treat it as a
8012 // normal GEP instruction where the bounds operations are simply indexes
8013 // into the array. We currently do reverse order of the bounds, which
8014 // I believe leans more towards Fortran's column-major in memory.
8015 if (isArrayTy) {
8016 idx.push_back(builder.getInt64(0));
8017 for (int i = bounds.size() - 1; i >= 0; --i) {
8018 if (auto boundOp = dyn_cast_if_present<omp::MapBoundsOp>(
8019 bounds[i].getDefiningOp())) {
8020 idx.push_back(moduleTranslation.lookupValue(boundOp.getLowerBound()));
8021 }
8022 }
8023 } else {
8024 // Check if Fortran descriptors provide strides in bytes
8025 llvm::Value *offset = builder.getInt64(0);
8026 for (Value bound : bounds) {
8027 auto boundOp =
8028 dyn_cast_if_present<omp::MapBoundsOp>(bound.getDefiningOp());
8029 if (!boundOp || !boundOp.getStride())
8030 continue;
8031 // Fortran descriptors provide strides in bytes; when that's the case the
8032 // accumulated offset is a byte offset and must be consumed with a byte
8033 // (i8) GEP at the call site to avoid double-scaling by the element size.
8034 isByteOffset = boundOp.getStrideInBytes();
8035 }
8036 if (isByteOffset) {
8037 for (Value bound : bounds) {
8038 auto boundOp =
8039 dyn_cast_if_present<omp::MapBoundsOp>(bound.getDefiningOp());
8040 if (!boundOp || !boundOp.getStride())
8041 continue;
8042 // If we do not have an array type, but we have bounds, then we're
8043 // dealing with a pointer that's being treated like an array
8044 // and we have the underlying type e.g. an i32, or f64 etc,
8045 // e.g. a fortran descriptor base address (pointer pointing to
8046 // the actual data) and we know that the offset stride is expressed
8047 // in bytes, so we must calculate the offset to the first mapped
8048 // element as a single linear index into that data.
8049 //
8050 // Each bound carries the stride between consecutive elements along its
8051 // dimension, so the linear offset to the section's origin is:
8052 //
8053 // offset += lower_bound[d] * stride[d]
8054 llvm::Value *lb =
8055 moduleTranslation.lookupValue(boundOp.getLowerBound());
8056 llvm::Value *stride =
8057 moduleTranslation.lookupValue(boundOp.getStride());
8058 offset = builder.CreateAdd(offset, builder.CreateMul(lb, stride));
8059 }
8060 idx.push_back(offset);
8061 } else {
8062 for (int i = bounds.size() - 1; i >= 0; --i) {
8063 if (auto boundOp = dyn_cast_if_present<omp::MapBoundsOp>(
8064 bounds[i].getDefiningOp())) {
8065 // If the stride in bytes is unknown, so we must calculate the
8066 // offset using a single index which the following loop attempts to
8067 // compute using the standard column-major algorithm e.g for a 3D
8068 // array:
8069 //
8070 // ((((c_idx * b_len) + b_idx) * a_len) + a_idx)
8071 //
8072 // It is of note that it's doing column-major rather than row-major at
8073 // the moment, but having a way for the frontend to indicate which
8074 // major format to use or standardizing/canonicalizing the order of
8075 // the bounds to compute the offset may be useful in the future when
8076 // there's other frontends with different formats.
8077 if (i == ((int)bounds.size() - 1))
8078 idx.emplace_back(
8079 moduleTranslation.lookupValue(boundOp.getLowerBound()));
8080 else
8081 idx.back() = builder.CreateAdd(
8082 builder.CreateMul(idx.back(), moduleTranslation.lookupValue(
8083 boundOp.getExtent())),
8084 moduleTranslation.lookupValue(boundOp.getLowerBound()));
8085 }
8086 }
8087 }
8088 }
8089
8090 return idx;
8091}
8092
8094 llvm::transform(values, std::back_inserter(ints), [](Attribute value) {
8095 return cast<IntegerAttr>(value).getInt();
8096 });
8097}
8098
8099// Gathers members that are overlapping in the parent, excluding members that
8100// themselves overlap, keeping the top-most (closest to parents level) map.
8101static void
8103 omp::MapInfoOp parentOp) {
8104 // No members mapped, no overlaps.
8105 if (parentOp.getMembers().empty())
8106 return;
8107
8108 // Single member, we can insert and return early.
8109 if (parentOp.getMembers().size() == 1) {
8110 overlapMapDataIdxs.push_back(0);
8111 return;
8112 }
8113
8114 ArrayAttr indexAttr = parentOp.getMembersIndexAttr();
8115 size_t numMembers = indexAttr.size();
8116
8117 // Pre-convert all member indices to integer arrays for efficient comparison.
8118 llvm::SmallVector<llvm::SmallVector<int64_t>> memberIndices(numMembers);
8119 for (auto [i, indicesAttr] : llvm::enumerate(indexAttr))
8120 getAsIntegers(cast<ArrayAttr>(indicesAttr), memberIndices[i]);
8121
8122 // For each member, check if it's superseded by another (shorter prefix)
8123 // member. If member j's indices are a prefix of member i's indices, then
8124 // i is a child of j and should be skipped. e.g. if member [0] is mapped,
8125 // we skip members [0,1], [0,2], etc.
8126 llvm::SmallDenseSet<size_t> skipIndices;
8127 for (size_t i = 0; i < numMembers; ++i) {
8128 const auto &iIndices = memberIndices[i];
8129 for (size_t j = 0; j < numMembers; ++j) {
8130 if (i == j)
8131 continue;
8132 const auto &jIndices = memberIndices[j];
8133 // If j's indices are a strict prefix of i's indices, skip i
8134 if (jIndices.size() < iIndices.size() &&
8135 std::equal(jIndices.begin(), jIndices.end(), iIndices.begin())) {
8136 skipIndices.insert(i);
8137 break; // No need to check other potential parents
8138 }
8139 }
8140 }
8141
8142 // Collect indices of members that are not superseded by a parent.
8143 for (size_t i = 0; i < numMembers; ++i)
8144 if (!skipIndices.contains(i))
8145 overlapMapDataIdxs.push_back(i);
8146}
8147
8148/// Describe a bounded update as a set of contiguous pieces. Bounds follow the
8149/// dialect's normalized fastest-to-slowest varying order, which matches
8150/// Fortran's column-major dimension order. Frontends for row-major languages
8151/// such as C/C++ must normalize their source dimensions to this order.
8152/// OpenMPIRBuilder reverses the vectors when emitting the runtime descriptor,
8153/// which uses byte offsets and strides with the slowest varying dimension
8154/// first.
8156 llvm::IRBuilderBase &builder,
8157 MapInfoData &mapData, size_t mapDataIdx,
8158 MapInfosTy &combinedInfo) {
8159 auto mapOp = cast<omp::MapInfoOp>(mapData.MapClause[mapDataIdx]);
8160 if (mapOp.getBounds().empty() || mapOp.getMapperId() ||
8161 mapOp->getParentOfType<omp::DeclareMapperOp>() ||
8162 bitEnumContainsAll(mapOp.getMapType(), omp::ClauseMapFlags::attach))
8163 return;
8164
8165 llvm::Type *elementType = mapData.BaseType[mapDataIdx];
8166 // Bounds extents may be absent, and a scalar subscript has extent one.
8167 // Preserve the full source shape: for a(4,5), a(2,:) still needs a
8168 // second-dimension stride of four elements.
8169 SmallVector<uint64_t> arrayExtents;
8170 while (auto *arrayType = dyn_cast<llvm::ArrayType>(elementType)) {
8171 arrayExtents.push_back(arrayType->getNumElements());
8172 elementType = arrayType->getElementType();
8173 }
8174 llvm::Value *elementSize = builder.getInt64(
8175 builder.GetInsertBlock()->getModule()->getDataLayout().getTypeAllocSize(
8176 elementType));
8177
8178 // Include a byte dimension so that a non-unit descriptor stride in the
8179 // innermost array dimension still transfers one element at a time.
8180 llvm::OpenMPIRBuilder::MapValuesArrayTy offsets{builder.getInt64(0)};
8181 llvm::OpenMPIRBuilder::MapValuesArrayTy counts{elementSize};
8182 llvm::OpenMPIRBuilder::MapValuesArrayTy strides{builder.getInt64(1)};
8183 llvm::Value *sourceStride = elementSize;
8184 llvm::Value *contiguousSize = elementSize;
8185 // Unknown descriptor strides conservatively use a descriptor. The runtime
8186 // merges contiguous dimensions from their actual values.
8187 bool isContiguous = true;
8188 for (auto [dim, bound] : llvm::enumerate(mapOp.getBounds())) {
8189 auto bounds = cast<omp::MapBoundsOp>(bound.getDefiningOp());
8190 auto getInt64 = [&](Value value) {
8191 return builder.CreateSExtOrTrunc(moduleTranslation.lookupValue(value),
8192 builder.getInt64Ty());
8193 };
8194 llvm::Value *lower = bounds.getLowerBound()
8195 ? getInt64(bounds.getLowerBound())
8196 : builder.getInt64(0);
8197 llvm::Value *count = builder.CreateAdd(
8198 builder.CreateSub(getInt64(bounds.getUpperBound()), lower),
8199 builder.getInt64(1));
8200 count =
8201 builder.CreateSelect(builder.CreateICmpSGT(count, builder.getInt64(0)),
8202 count, builder.getInt64(0));
8203 llvm::Value *stride = sourceStride;
8204 if (bounds.getStrideInBytes() && bounds.getStride())
8205 stride = getInt64(bounds.getStride());
8206 else if (arrayExtents.empty() && bounds.getStride())
8207 stride = builder.CreateMul(getInt64(bounds.getStride()), elementSize);
8208 offsets.push_back(builder.CreateMul(lower, stride));
8209 counts.push_back(count);
8210 strides.push_back(stride);
8211 isContiguous &= stride == contiguousSize;
8212 contiguousSize = builder.CreateMul(contiguousSize, count);
8213
8214 // Static array types carry the full source shape, including dimensions
8215 // selected with scalar subscripts. Otherwise the frontend supplies the
8216 // physical stride in elements (or descriptor bytes) for every dimension.
8217 if (dim < arrayExtents.size())
8218 sourceStride = builder.CreateMul(
8219 sourceStride,
8220 builder.getInt64(arrayExtents[arrayExtents.size() - dim - 1]));
8221 else if (bounds.getExtent())
8222 sourceStride =
8223 builder.CreateMul(sourceStride, getInt64(bounds.getExtent()));
8224 }
8225 if (isContiguous)
8226 return;
8227
8228 llvm::Value *base = mapData.OriginalValue[mapDataIdx];
8229 if (mapOp.getVarPtrPtr())
8230 base = builder.CreateLoad(builder.getPtrTy(), base);
8231 // Absent optional arrays and unallocated descriptors must not issue
8232 // transfers, including sections with explicit nonempty bounds.
8233 counts[1] = builder.CreateSelect(builder.CreateIsNull(base),
8234 builder.getInt64(0), counts[1]);
8235 // The descriptor offsets already locate the first selected element. The
8236 // runtime needs the pointee base, not the descriptor or a section address.
8237 combinedInfo.BasePointers.back() = base;
8238 combinedInfo.Types.back() |=
8239 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NON_CONTIG;
8240 auto &nonContig = combinedInfo.NonContigInfo;
8241 nonContig.IsNonContiguous = true;
8242 nonContig.Dims.resize(combinedInfo.Types.size(), 1);
8243 nonContig.Dims.back() = counts.size();
8244 nonContig.Offsets.push_back(std::move(offsets));
8245 nonContig.Counts.push_back(std::move(counts));
8246 nonContig.Strides.push_back(std::move(strides));
8247}
8248
8249/// This function handles the insertion of a single item of map data from
8250/// MapInfoData into the OMPIRBuilder's MapInfo list. Utilising this function
8251/// means the map being inserted can be treated as a non-parent map entity,
8252/// if the memberOfFlag is set then the map being inserted is treated as
8253/// a member map of a larger entity. The insertion into the MapInfo list of
8254/// the OMPIRBuilder can vary based on a number of factors, such as if it's
8255/// a ref_ptr or ref_ptee map, if it's a member of a record, what construct
8256/// the map belongs to and the various map type bit flags that are set for
8257/// the map.
8259 LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder,
8260 llvm::OpenMPIRBuilder &ompBuilder, MapInfoData &mapData, size_t mapDataIdx,
8261 MapInfosTy &combinedInfo, TargetDirectiveEnumTy targetDirective,
8262 llvm::omp::OpenMPOffloadMappingFlags memberOfFlag =
8263 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE,
8264 bool isTargetParam = true, int mapDataParentIdx = -1) {
8265 auto mapFlag = mapData.Types[mapDataIdx];
8266 auto mapInfoOp = llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIdx]);
8267
8268 bool isPtrTy = checkIfPointerMap(mapInfoOp);
8269 bool isAttachMap = ((convertClauseMapFlags(mapInfoOp.getMapType()) &
8270 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) ==
8271 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH);
8272
8273 // Declare target variables are not passed to the kernel, and for the moment
8274 // attach maps are not passed to the kernel. However, it is possible to create
8275 // attach maps that transfer data and thus can be kernel arguments, but our
8276 // existing frontend does not do this.
8277 if (isTargetParam &&
8278 (targetDirective == TargetDirectiveEnumTy::Target &&
8279 !mapData.IsDeclareTarget[mapDataIdx]) &&
8280 !isAttachMap)
8281 mapFlag |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TARGET_PARAM;
8282
8283 if (mapInfoOp.getMapCaptureType() == omp::VariableCaptureKind::ByCopy &&
8284 !isPtrTy)
8285 mapFlag |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL;
8286
8287 // If we have a pointer and it's part of a MEMBER_OF mapping we do not apply
8288 // MEMBER_OF, as the runtime currently has a work-around that utilises
8289 // MEMBER_OF to prevent reference updating in certain scenarios instead of
8290 // target_param. However, this causes a noticeable issue in cases where we
8291 // map some data (Fortran descriptor primarily at the moment), alter it on
8292 // the host, and then expect it to not be updated in a subsequent implicit map
8293 // (such as an implicit map on a target).
8294 if (memberOfFlag != llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE) {
8295 if (!isPtrTy && !isAttachMap)
8296 ompBuilder.setCorrectMemberOfFlag(mapFlag, memberOfFlag);
8297
8298 // The return parameter should be the over-riding parent in cases where we
8299 // have a return parameter that is echoed to all members, the main case of
8300 // this currently is with fortran descriptors. It may need more finessing
8301 // for C/C++ in the future or descriptors that are members of derived
8302 // types.
8303 mapFlag &= ~llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
8304 }
8305
8306 // We apply MAP_PTR_AND_OBJ when within a declare mapper object as it enforces
8307 // MEMBER_OF mappings on maps that are passed the initial nesting depth, which
8308 // includes pointed to data and attach members, both of which are technically
8309 // not part of the main object. This has the side effect of causing early
8310 // map-backs in certain cases where an implicit declare mapper has been
8311 // emitted for a target region. Applying MAP_PTR_AND_OBJ in these situations
8312 // circumvents this.
8313 if (isPtrTy && !isAttachMap && mapData.IsDeclareTarget[mapDataIdx])
8314 mapFlag |= llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_PTR_AND_OBJ;
8315
8316 // if we're provided a mapDataParentIdx, then the data being mapped is
8317 // part of a larger object (in a parent <-> member mapping) and in this
8318 // case our BasePointer should be the parent. Except in the edge case
8319 // where we are mapping pointee data, where we try staying close to
8320 // what Clang currently does and utilise the regular base pointer of the
8321 // data.
8322 bool isRefPtee =
8323 !bitEnumContainsAll(mapInfoOp.getMapType(),
8324 omp::ClauseMapFlags::ref_ptr) &&
8325 bitEnumContainsAll(mapInfoOp.getMapType(), omp::ClauseMapFlags::ref_ptee);
8326 bool isRefPtrPtee = bitEnumContainsAll(mapInfoOp.getMapType(),
8327 omp::ClauseMapFlags::ref_ptr |
8328 omp::ClauseMapFlags::ref_ptee);
8329
8330 if (!mapInfoOp->getParentOfType<omp::DeclareMapperOp>() &&
8331 mapDataParentIdx >= 0 && !(isRefPtee || (isRefPtrPtee && isPtrTy))) {
8332 combinedInfo.BasePointers.emplace_back(
8333 mapData.BasePointers[mapDataParentIdx]);
8334 } else {
8335 combinedInfo.BasePointers.emplace_back(mapData.BasePointers[mapDataIdx]);
8336 }
8337
8338 combinedInfo.Pointers.emplace_back(mapData.Pointers[mapDataIdx]);
8339 combinedInfo.DevicePointers.emplace_back(
8340 memberOfFlag != llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE
8341 ? llvm::OpenMPIRBuilder::DeviceInfoTy::None
8342 : mapData.DevicePointers[mapDataIdx]);
8343 combinedInfo.Mappers.emplace_back(mapData.Mappers[mapDataIdx]);
8344 combinedInfo.Names.emplace_back(mapData.Names[mapDataIdx]);
8345 combinedInfo.Types.emplace_back(mapFlag);
8346 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
8347 combinedInfo.HasAttachPtr.emplace_back(false);
8348 // A privatized attach map needs storage for the pointer or descriptor even
8349 // when its pointee is null. Its size describes that storage, not the pointee,
8350 // and is needed by the runtime for corresponding-pointer-initialization.
8351 combinedInfo.Sizes.emplace_back(
8352 isPtrTy && !isPrivatizeableAttachMap(mapInfoOp.getMapType())
8353 ? builder.CreateSelect(
8354 builder.CreateIsNull(mapData.Pointers[mapDataIdx]),
8355 builder.getInt64(0), mapData.Sizes[mapDataIdx])
8356 : mapData.Sizes[mapDataIdx]);
8357 if (targetDirective == TargetDirectiveEnumTy::TargetUpdate)
8358 addNonContiguousUpdate(moduleTranslation, builder, mapData, mapDataIdx,
8359 combinedInfo);
8360}
8361
8362// This creates two insertions into the MapInfosTy data structure for the
8363// "parent" of a set of members, (usually a container e.g.
8364// class/structure/derived type) when subsequent members have also been
8365// explicitly mapped on the same map clause. Certain types, such as Fortran
8366// descriptors are mapped like this as well, however, the members are
8367// implicit as far as a user is concerned, but we must explicitly map them
8368// internally.
8369//
8370// This function also returns the memberOfFlag for this particular parent,
8371// which is utilised in subsequent member mappings (by modifying there map type
8372// with it) to indicate that a member is part of this parent and should be
8373// treated by the runtime as such. Important to achieve the correct mapping.
8374//
8375// This function borrows a lot from Clang's emitCombinedEntry function
8376// inside of CGOpenMPRuntime.cpp
8378 LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder,
8379 llvm::OpenMPIRBuilder &ompBuilder, DataLayout &dl, MapInfosTy &combinedInfo,
8380 MapInfoData &mapData, uint64_t mapDataIndex,
8381 llvm::omp::OpenMPOffloadMappingFlags memberOfFlag,
8382 TargetDirectiveEnumTy targetDirective) {
8383 using MapFlags = llvm::omp::OpenMPOffloadMappingFlags;
8384 assert(!ompBuilder.Config.isTargetDevice() &&
8385 "function only supported for host device codegen");
8386 auto parentClause =
8387 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
8388 auto *parentMapper = mapData.Mappers[mapDataIndex];
8389
8390 // Map the first segment of the parent. If a user-defined mapper is attached,
8391 // include the parent's to/from-style bits (and common modifiers) in this
8392 // base entry so the mapper receives correct copy semantics via its 'type'
8393 // parameter. Also keep TARGET_PARAM when required for kernel arguments.
8394 MapFlags baseFlag = (targetDirective == TargetDirectiveEnumTy::Target &&
8395 !mapData.IsDeclareTarget[mapDataIndex])
8396 ? MapFlags::OMP_MAP_TARGET_PARAM
8397 : MapFlags::OMP_MAP_NONE;
8398
8399 if (parentMapper) {
8400 // Preserve relevant map-type bits from the parent clause. These include
8401 // the copy direction (TO/FROM), as well as commonly used modifiers that
8402 // should be visible to the mapper for correct behaviour.
8403 MapFlags parentFlags = mapData.Types[mapDataIndex];
8404 MapFlags preserve = MapFlags::OMP_MAP_TO | MapFlags::OMP_MAP_FROM |
8405 MapFlags::OMP_MAP_ALWAYS | MapFlags::OMP_MAP_CLOSE |
8406 MapFlags::OMP_MAP_PRESENT |
8407 MapFlags::OMP_MAP_OMPX_HOLD |
8408 MapFlags::OMP_MAP_IMPLICIT;
8409 baseFlag |= (parentFlags & preserve);
8410 } else {
8411 MapFlags parentFlags = mapData.Types[mapDataIndex];
8412 MapFlags preserve = MapFlags::OMP_MAP_TO | MapFlags::OMP_MAP_FROM |
8413 MapFlags::OMP_MAP_PRESENT |
8414 MapFlags::OMP_MAP_RETURN_PARAM |
8415 MapFlags::OMP_MAP_IMPLICIT;
8416 baseFlag |= (parentFlags & preserve);
8417 }
8418
8419 combinedInfo.Types.emplace_back(baseFlag);
8420 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
8421 combinedInfo.HasAttachPtr.emplace_back(false);
8422 combinedInfo.DevicePointers.emplace_back(
8423 mapData.DevicePointers[mapDataIndex]);
8424 // Only attach the mapper to the base entry when we are mapping the whole
8425 // parent. Combined/segment entries must not carry a mapper; otherwise the
8426 // mapper can be invoked with a partial size, which is undefined behaviour.
8427 combinedInfo.Mappers.emplace_back(
8428 parentMapper && !parentClause.getPartialMap() ? parentMapper : nullptr);
8429 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
8430 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder,
8431 getMapClauseName(cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]))));
8432 combinedInfo.BasePointers.emplace_back(mapData.BasePointers[mapDataIndex]);
8433
8434 // Calculate size of the parent object being mapped based on the
8435 // addresses at runtime, highAddr - lowAddr = size. This of course
8436 // doesn't factor in allocated data like pointers, hence the further
8437 // processing of members specified by users, or in the case of
8438 // Fortran pointers and allocatables, the mapping of the pointed to
8439 // data by the descriptor (which itself, is a structure containing
8440 // runtime information on the dynamically allocated data).
8441 llvm::Value *lowAddr, *highAddr;
8442 if (!parentClause.getPartialMap()) {
8443 lowAddr = builder.CreatePointerCast(mapData.Pointers[mapDataIndex],
8444 builder.getPtrTy());
8445 highAddr = builder.CreatePointerCast(
8446 builder.CreateConstGEP1_32(mapData.BaseType[mapDataIndex],
8447 mapData.Pointers[mapDataIndex], 1),
8448 builder.getPtrTy());
8449 combinedInfo.Pointers.emplace_back(mapData.Pointers[mapDataIndex]);
8450 } else {
8451 auto mapOp = dyn_cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
8452 int firstMemberIdx = getMapDataMemberIdx(
8453 mapData, getFirstOrLastMappedMemberPtr(mapOp, true));
8454 lowAddr = builder.CreatePointerCast(mapData.BasePointers[firstMemberIdx],
8455 builder.getPtrTy());
8456
8457 int lastMemberIdx = getMapDataMemberIdx(
8458 mapData, getFirstOrLastMappedMemberPtr(mapOp, false));
8459 auto lastMemberMapInfo =
8460 cast<omp::MapInfoOp>(mapData.MapClause[lastMemberIdx]);
8461
8462 // NOTE: Currently, for RefPtee the BaseType is set to the varPtrPtr field,
8463 // which is the pointer datas type and not the member within the structure
8464 // that it's part of, so we have to make sure we use the member type in this
8465 // case when calculating the parents size offsets.
8466 // TODO: May be good to extend MapInfoData to support tracking of both
8467 // VarPtr/VarPtrPtr BaseType's to better distinguish what's being used more
8468 // consistently.
8469 bool isRefPteeMap = bitEnumContainsAll(lastMemberMapInfo.getMapType(),
8470 omp::ClauseMapFlags::ref_ptee) &&
8471 !bitEnumContainsAll(lastMemberMapInfo.getMapType(),
8472 omp::ClauseMapFlags::ref_ptr);
8473 llvm::Type *castType = mapData.BaseType[lastMemberIdx];
8474 if (isRefPteeMap)
8475 castType =
8476 moduleTranslation.convertType(lastMemberMapInfo.getVarPtrType());
8477 highAddr = builder.CreatePointerCast(
8478 builder.CreateGEP(castType, mapData.BasePointers[lastMemberIdx],
8479 builder.getInt64(1)),
8480 builder.getPtrTy());
8481 combinedInfo.Pointers.emplace_back(mapData.BasePointers[firstMemberIdx]);
8482 }
8483
8484 llvm::Value *size = builder.CreateIntCast(
8485 builder.CreatePtrDiff(builder.getInt8Ty(), highAddr, lowAddr),
8486 builder.getInt64Ty(),
8487 /*isSigned=*/false);
8488 combinedInfo.Sizes.push_back(size);
8489
8490 // This creates the initial MEMBER_OF mapping that consists of
8491 // the parent/top level container (same as above effectively, except
8492 // with a fixed initial compile time size and separate maptype which
8493 // indicates the true mape type (tofrom etc.). This parent mapping is
8494 // only relevant if the structure in its totality is being mapped,
8495 // otherwise the above suffices.
8496 if (!parentClause.getPartialMap()) {
8497 // TODO: This will need to be expanded to include the whole host of logic
8498 // for the map flags that Clang currently supports (e.g. it should do some
8499 // further case specific flag modifications). For the moment, it handles
8500 // what we support as expected.
8501 MapFlags mapFlag = mapData.Types[mapDataIndex];
8502 bool hasMapClose = (MapFlags(mapFlag) & MapFlags::OMP_MAP_CLOSE) ==
8503 MapFlags::OMP_MAP_CLOSE;
8504 ompBuilder.setCorrectMemberOfFlag(mapFlag, memberOfFlag);
8505
8506 llvm::SmallVector<size_t> overlapIdxs;
8507 // Find all of the members that "overlap", i.e. occlude other members that
8508 // were mapped alongside the parent, e.g. member [0], occludes [0,1] and
8509 // [0,2], but not [1,0].
8510 getOverlappedMembers(overlapIdxs, parentClause);
8511
8512 // When we only have one overlap we skip the case that tries to segment the
8513 // mapping as best it can without creating holes, as the calculation is more
8514 // likely to have more overhead than anything we gain from mapping a smaller
8515 // chunk of data. This can be seen in cases where we are mapping Fortran
8516 // descriptors which are a special case of record type mapping.
8517 //
8518 // The cases for close and update are unique edge cases where the segmenting
8519 // does not play well with the runtime currently.
8520 if (targetDirective == TargetDirectiveEnumTy::TargetUpdate || hasMapClose ||
8521 overlapIdxs.size() == 1) {
8522 combinedInfo.Types.emplace_back(mapFlag);
8523 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
8524 combinedInfo.HasAttachPtr.emplace_back(false);
8525 combinedInfo.DevicePointers.emplace_back(
8526 mapData.DevicePointers[mapDataIndex]);
8527 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
8528 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder,
8530 cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]))));
8531 combinedInfo.BasePointers.emplace_back(
8532 mapData.BasePointers[mapDataIndex]);
8533 combinedInfo.Pointers.emplace_back(mapData.Pointers[mapDataIndex]);
8534 combinedInfo.Sizes.emplace_back(mapData.Sizes[mapDataIndex]);
8535 combinedInfo.Mappers.emplace_back(nullptr);
8536 } else {
8537 // We need to make sure the overlapped members are sorted in order of
8538 // lowest address to highest address.
8539 sortMapIndices(overlapIdxs, parentClause);
8540
8541 lowAddr = builder.CreatePointerCast(mapData.Pointers[mapDataIndex],
8542 builder.getPtrTy());
8543 highAddr = builder.CreatePointerCast(
8544 builder.CreateConstGEP1_32(mapData.BaseType[mapDataIndex],
8545 mapData.Pointers[mapDataIndex], 1),
8546 builder.getPtrTy());
8547
8548 // Currently, the return parameter should be the over-riding parent in
8549 // cases where we have a return parameter that is echoed to all members,
8550 // the main case of this currently is with fortran descriptors. It may
8551 // need more finessing for C/C++ in the future or descriptors that are
8552 // members of derived types.
8553 mapFlag &= ~llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_RETURN_PARAM;
8554
8555 // TODO: We may want to skip arrays/array sections in this as Clang does.
8556 // It appears to be an optimisation rather than a necessity though,
8557 // but this requires further investigation. However, we would have to make
8558 // sure to not exclude maps with bounds that ARE pointers, as these are
8559 // processed as separate components, i.e. pointer + data.
8560 for (auto v : overlapIdxs) {
8561 auto mapDataOverlapIdx = getMapDataMemberIdx(
8562 mapData,
8563 cast<omp::MapInfoOp>(parentClause.getMembers()[v].getDefiningOp()));
8564 auto isPtrMap = checkIfPointerMap(
8565 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataOverlapIdx]));
8566 combinedInfo.Types.emplace_back(mapFlag);
8567 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
8568 combinedInfo.HasAttachPtr.emplace_back(false);
8569 combinedInfo.DevicePointers.emplace_back(
8570 llvm::OpenMPIRBuilder::DeviceInfoTy::None);
8571 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
8572 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder,
8574 cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]))));
8575 combinedInfo.BasePointers.emplace_back(
8576 mapData.BasePointers[mapDataIndex]);
8577 combinedInfo.Mappers.emplace_back(nullptr);
8578 combinedInfo.Pointers.emplace_back(lowAddr);
8579 auto sizeCalc = builder.CreateIntCast(
8580 builder.CreatePtrDiff(builder.getInt8Ty(),
8581 mapData.OriginalValue[mapDataOverlapIdx],
8582 lowAddr),
8583 builder.getInt64Ty(), /*isSigned=*/true);
8584 // In certain cases, we'll generate a size of 0 if we're not careful
8585 // (e.g. if lowAddr happens to be the first member), which isn't
8586 // correct, even if the runtimes is sometimes fine with it so, in these
8587 // scenarios we select the types size instead.
8588 llvm::DataLayout dataLayout = builder.getDataLayout();
8589 auto sizeSel = builder.CreateSelect(
8590 builder.CreateICmpNE(builder.getInt64(0), sizeCalc), sizeCalc,
8591 isPtrMap ? builder.getInt64(dataLayout.getPointerSize())
8592 : mapData.Sizes[mapDataOverlapIdx]);
8593 combinedInfo.Sizes.emplace_back(sizeSel);
8594 lowAddr = builder.CreateConstGEP1_32(
8595 isPtrMap ? builder.getPtrTy() : mapData.BaseType[mapDataOverlapIdx],
8596 mapData.BasePointers[mapDataOverlapIdx], 1);
8597 }
8598
8599 combinedInfo.Types.emplace_back(mapFlag);
8600 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
8601 combinedInfo.HasAttachPtr.emplace_back(false);
8602 combinedInfo.DevicePointers.emplace_back(
8603 llvm::OpenMPIRBuilder::DeviceInfoTy::None);
8604 combinedInfo.Names.emplace_back(LLVM::createMappingInformation(
8605 mapData.MapClause[mapDataIndex]->getLoc(), ompBuilder,
8607 cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]))));
8608 combinedInfo.BasePointers.emplace_back(
8609 mapData.BasePointers[mapDataIndex]);
8610 combinedInfo.Mappers.emplace_back(nullptr);
8611 combinedInfo.Pointers.emplace_back(lowAddr);
8612 combinedInfo.Sizes.emplace_back(builder.CreateIntCast(
8613 builder.CreatePtrDiff(builder.getInt8Ty(), highAddr, lowAddr),
8614 builder.getInt64Ty(), true));
8615 }
8616 }
8617}
8618
8620 llvm::IRBuilderBase &builder,
8621 llvm::OpenMPIRBuilder &ompBuilder,
8622 DataLayout &dl, MapInfosTy &combinedInfo,
8623 MapInfoData &mapData, uint64_t mapDataIndex,
8624 TargetDirectiveEnumTy targetDirective) {
8625 assert(!ompBuilder.Config.isTargetDevice() &&
8626 "function only supported for host device codegen");
8627
8628 auto parentClause =
8629 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
8630
8631 // If we have a partial map (no parent referenced in the map clauses of the
8632 // directive, only members) and only a single member, we do not need to bind
8633 // the map of the member to the parent, we can pass the member separately.
8634 if (parentClause.getMembers().size() == 1 && parentClause.getPartialMap()) {
8635 auto memberClause = llvm::cast<omp::MapInfoOp>(
8636 parentClause.getMembers()[0].getDefiningOp());
8637 int memberDataIdx = getMapDataMemberIdx(mapData, memberClause);
8638 // Note: Clang treats arrays with explicit bounds that fall into this
8639 // category as a parent with map case, however, it seems this isn't a
8640 // requirement, and processing them as an individual map is fine. So,
8641 // we will handle them as individual maps for the moment, as it's
8642 // difficult for us to check this as we always require bounds to be
8643 // specified currently and it's also marginally more optimal (single
8644 // map rather than two). The difference may come from the fact that
8645 // Clang maps array without bounds as pointers (which we do not
8646 // currently do), whereas we treat them as arrays in all cases
8647 // currently.
8649 moduleTranslation, builder, ompBuilder, mapData, memberDataIdx,
8650 combinedInfo, targetDirective,
8651 /*MemberOfFlag=*/llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE,
8652 /*isTargetParam=*/true, mapDataIndex);
8653 return;
8654 }
8655
8656 auto collectMapInfoIdxs =
8657 [&](llvm::SmallVectorImpl<int64_t> &mapsAndInfoIdx) {
8658 auto parentClause =
8659 llvm::cast<omp::MapInfoOp>(mapData.MapClause[mapDataIndex]);
8660 mapsAndInfoIdx.push_back(getMapDataMemberIdx(mapData, parentClause));
8661 for (auto member : parentClause.getMembers())
8662 mapsAndInfoIdx.push_back(getMapDataMemberIdx(
8663 mapData, llvm::cast<omp::MapInfoOp>(member.getDefiningOp())));
8664 };
8665
8666 llvm::SmallVector<int64_t> mapInfoIdx;
8667 collectMapInfoIdxs(mapInfoIdx);
8668
8669 llvm::omp::OpenMPOffloadMappingFlags memberOfFlag =
8670 ompBuilder.getMemberOfFlag(combinedInfo.Types.size());
8671
8672 // The first index is the parent map, the rest are its members. The parent
8673 // normally undergoes the standard parent-with-members mapping, contributing
8674 // the MEMBER_OF flag that binds each member to it. The one exception is a
8675 // privatizeable attach map (a privatized pointer/descriptor passed directly
8676 // as a kernel argument): here the parent is emitted as an individual map
8677 // instead, for the time being, as it's used only in pointer/allocatable to
8678 // array cases for the moment. This only ever applies to the parent, so it is
8679 // checked once here rather than inside the loop below.
8680 bool parentIsPrivatizeableAttach =
8681 isPrivatizeableAttachMap(parentClause.getMapType());
8682 for (auto [i, idx] : llvm::enumerate(mapInfoIdx)) {
8683 bool emitParentMap = i == 0 && !parentIsPrivatizeableAttach;
8684 if (emitParentMap) {
8685 mapParentWithMembers(moduleTranslation, builder, ompBuilder, dl,
8686 combinedInfo, mapData, idx, memberOfFlag,
8687 targetDirective);
8688 } else {
8690 moduleTranslation, builder, ompBuilder, mapData, idx, combinedInfo,
8691 targetDirective,
8692 parentIsPrivatizeableAttach
8693 ? llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_NONE
8694 : memberOfFlag,
8695 /*isTargetParam=*/false, mapDataIndex);
8696 }
8697 }
8698}
8699
8700// This is a variation on Clang's GenerateOpenMPCapturedVars, which
8701// generates different operation (e.g. load/store) combinations for
8702// arguments to the kernel, based on map capture kinds which are then
8703// utilised in the combinedInfo in place of the original Map value.
8704static void
8705createAlteredByCaptureMap(MapInfoData &mapData,
8706 LLVM::ModuleTranslation &moduleTranslation,
8707 llvm::IRBuilderBase &builder) {
8708 assert(!getOpenMPBuilder(moduleTranslation)->Config.isTargetDevice() &&
8709 "function only supported for host device codegen");
8710 for (size_t i = 0; i < mapData.MapClause.size(); ++i) {
8711 auto mapOp = cast<omp::MapInfoOp>(mapData.MapClause[i]);
8712 bool isAttachMap =
8713 ((convertClauseMapFlags(mapOp.getMapType()) &
8714 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH) ==
8715 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_ATTACH);
8716
8717 // If it's declare target, skip it, it's handled separately. However, if
8718 // it's declare target, and an attach map, we want to calculate the exact
8719 // address offset so that we attach correctly.
8720 if (!mapData.IsDeclareTarget[i] ||
8721 (mapData.IsDeclareTarget[i] && isAttachMap)) {
8722 omp::VariableCaptureKind captureKind = mapOp.getMapCaptureType();
8723 bool isPtrTy = checkIfPointerMap(mapOp);
8724
8725 // Currently handles array sectioning lowerbound case, but more
8726 // logic may be required in the future. Clang invokes EmitLValue,
8727 // which has specialised logic for special Clang types such as user
8728 // defines, so it is possible we will have to extend this for
8729 // structures or other complex types. As the general idea is that this
8730 // function mimics some of the logic from Clang that we require for
8731 // kernel argument passing from host -> device.
8732 switch (captureKind) {
8733 case omp::VariableCaptureKind::ByRef: {
8734 llvm::Value *newV = mapData.Pointers[i];
8735 bool isByteOffset = false;
8736 std::vector<llvm::Value *> offsetIdx = calculateBoundsOffset(
8737 moduleTranslation, builder, mapData.BaseType[i]->isArrayTy(),
8738 mapOp.getBounds(), isByteOffset);
8739 if (isPtrTy)
8740 newV = builder.CreateLoad(builder.getPtrTy(), newV);
8741
8742 if (!offsetIdx.empty())
8743 newV = builder.CreateInBoundsGEP(isByteOffset ? builder.getInt8Ty()
8744 : mapData.BaseType[i],
8745 newV, offsetIdx, "array_offset");
8746 mapData.Pointers[i] = newV;
8747 } break;
8748 case omp::VariableCaptureKind::ByCopy: {
8749 llvm::Type *type = mapData.BaseType[i];
8750 llvm::Value *newV;
8751 if (mapData.Pointers[i]->getType()->isPointerTy())
8752 newV = builder.CreateLoad(type, mapData.Pointers[i]);
8753 else
8754 newV = mapData.Pointers[i];
8755
8756 if (!isPtrTy) {
8757 auto curInsert = builder.saveIP();
8758 llvm::DebugLoc DbgLoc = builder.getCurrentDebugLocation();
8759 builder.restoreIP(findAllocInsertPoints(builder, moduleTranslation));
8760 auto *memTempAlloc =
8761 builder.CreateAlloca(builder.getPtrTy(), nullptr, ".casted");
8762 builder.SetCurrentDebugLocation(DbgLoc);
8763 builder.restoreIP(curInsert);
8764
8765 builder.CreateStore(newV, memTempAlloc);
8766 newV = builder.CreateLoad(builder.getPtrTy(), memTempAlloc);
8767 }
8768
8769 mapData.Pointers[i] = newV;
8770 mapData.BasePointers[i] = newV;
8771 } break;
8772 case omp::VariableCaptureKind::This:
8773 case omp::VariableCaptureKind::VLAType:
8774 mapData.MapClause[i]->emitOpError("Unhandled capture kind");
8775 break;
8776 }
8777 }
8778 }
8779}
8780
8781// Generate all map related information and fill the combinedInfo.
8782static void genMapInfos(llvm::IRBuilderBase &builder,
8783 LLVM::ModuleTranslation &moduleTranslation,
8784 DataLayout &dl, MapInfosTy &combinedInfo,
8785 MapInfoData &mapData,
8786 TargetDirectiveEnumTy targetDirective) {
8787 assert(!getOpenMPBuilder(moduleTranslation)->Config.isTargetDevice() &&
8788 "function only supported for host device codegen");
8789 // We wish to modify some of the methods in which arguments are
8790 // passed based on their capture type by the target region, this can
8791 // involve generating new loads and stores, which changes the
8792 // MLIR value to LLVM value mapping, however, we only wish to do this
8793 // locally for the current function/target and also avoid altering
8794 // ModuleTranslation, so we remap the base pointer or pointer stored
8795 // in the map infos corresponding MapInfoData, which is later accessed
8796 // by genMapInfos and createTarget to help generate the kernel and
8797 // kernel arg structure. It primarily becomes relevant in cases like
8798 // bycopy, or byref range'd arrays. In the default case, we simply
8799 // pass thee pointer byref as both basePointer and pointer.
8800 createAlteredByCaptureMap(mapData, moduleTranslation, builder);
8801
8802 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
8803
8804 // We operate under the assumption that all vectors that are
8805 // required in MapInfoData are of equal lengths (either filled with
8806 // default constructed data or appropiate information) so we can
8807 // utilise the size from any component of MapInfoData, if we can't
8808 // something is missing from the initial MapInfoData construction.
8809 for (size_t i = 0; i < mapData.MapClause.size(); ++i) {
8810 if (mapData.IsAMember[i])
8811 continue;
8812
8813 auto mapInfoOp = dyn_cast<omp::MapInfoOp>(mapData.MapClause[i]);
8814 if (!mapInfoOp.getMembers().empty()) {
8815 processMapWithMembersOf(moduleTranslation, builder, *ompBuilder, dl,
8816 combinedInfo, mapData, i, targetDirective);
8817 continue;
8818 }
8819
8820 processIndividualMap(moduleTranslation, builder, *ompBuilder, mapData, i,
8821 combinedInfo, targetDirective);
8822 }
8823 if (!combinedInfo.NonContigInfo.Dims.empty())
8824 combinedInfo.NonContigInfo.Dims.resize(combinedInfo.Types.size(), 1);
8825}
8826
8827static llvm::Expected<llvm::Function *>
8828emitUserDefinedMapper(Operation *declMapperOp, llvm::IRBuilderBase &builder,
8829 LLVM::ModuleTranslation &moduleTranslation,
8830 llvm::StringRef mapperFuncName,
8831 TargetDirectiveEnumTy targetDirective);
8832
8833static llvm::Expected<llvm::Function *>
8834getOrCreateUserDefinedMapperFunc(Operation *op, llvm::IRBuilderBase &builder,
8835 LLVM::ModuleTranslation &moduleTranslation,
8836 TargetDirectiveEnumTy targetDirective) {
8837 assert(!getOpenMPBuilder(moduleTranslation)->Config.isTargetDevice() &&
8838 "function only supported for host device codegen");
8839 auto declMapperOp = cast<omp::DeclareMapperOp>(op);
8840 std::string mapperFuncName =
8841 getOpenMPBuilder(moduleTranslation)
8842 ->createPlatformSpecificName(
8843 {"omp_mapper", declMapperOp.getSymName()});
8844
8845 if (auto *lookupFunc = moduleTranslation.lookupFunction(mapperFuncName))
8846 return lookupFunc;
8847
8848 // Recursive types can cause re-entrant mapper emission. The mapper function
8849 // is created by OpenMPIRBuilder before the callbacks run, so it may already
8850 // exist in the LLVM module even though it is not yet registered in the
8851 // ModuleTranslation mapping table. Reuse and register it to break the
8852 // recursion.
8853 if (llvm::Function *existingFunc =
8854 moduleTranslation.getLLVMModule()->getFunction(mapperFuncName)) {
8855 moduleTranslation.mapFunction(mapperFuncName, existingFunc);
8856 return existingFunc;
8857 }
8858
8859 return emitUserDefinedMapper(declMapperOp, builder, moduleTranslation,
8860 mapperFuncName, targetDirective);
8861}
8862
8863static llvm::Expected<llvm::Function *>
8864emitUserDefinedMapper(Operation *op, llvm::IRBuilderBase &builder,
8865 LLVM::ModuleTranslation &moduleTranslation,
8866 llvm::StringRef mapperFuncName,
8867 TargetDirectiveEnumTy targetDirective) {
8868 assert(!getOpenMPBuilder(moduleTranslation)->Config.isTargetDevice() &&
8869 "function only supported for host device codegen");
8870 auto declMapperOp = cast<omp::DeclareMapperOp>(op);
8871 auto declMapperInfoOp = declMapperOp.getDeclareMapperInfo();
8872 if (failed(checkImplementationStatus(*declMapperInfoOp)))
8873 return llvm::make_error<PreviouslyReportedError>();
8874
8875 DataLayout dl = DataLayout(declMapperOp->getParentOfType<ModuleOp>());
8876 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
8877 llvm::Type *varType = moduleTranslation.convertType(declMapperOp.getType());
8878 SmallVector<Value> mapVars = declMapperInfoOp.getMapVars();
8879
8880 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
8881
8882 // Fill up the arrays with all the mapped variables.
8883 MapInfosTy combinedInfo;
8884 auto genMapInfoCB =
8885 [&](InsertPointTy codeGenIP, llvm::Value *ptrPHI,
8886 llvm::Value *unused2) -> llvm::OpenMPIRBuilder::MapInfosOrErrorTy {
8887 builder.restoreIP(codeGenIP);
8888 moduleTranslation.mapValue(declMapperOp.getSymVal(), ptrPHI);
8889 moduleTranslation.mapBlock(&declMapperOp.getRegion().front(),
8890 builder.GetInsertBlock());
8891 if (failed(moduleTranslation.convertBlock(declMapperOp.getRegion().front(),
8892 /*ignoreArguments=*/true,
8893 builder)))
8894 return llvm::make_error<PreviouslyReportedError>();
8895 MapInfoData mapData;
8896 collectMapDataFromMapOperands(mapData, mapVars, moduleTranslation, dl,
8897 builder);
8898 genMapInfos(builder, moduleTranslation, dl, combinedInfo, mapData,
8899 targetDirective);
8900
8901 // Drop the mapping that is no longer necessary so that the same region
8902 // can be processed multiple times.
8903 moduleTranslation.forgetMapping(declMapperOp.getRegion());
8904 return combinedInfo;
8905 };
8906
8907 auto customMapperCB = [&](unsigned i) -> llvm::Expected<llvm::Function *> {
8908 if (!combinedInfo.Mappers[i])
8909 return nullptr;
8910 return getOrCreateUserDefinedMapperFunc(combinedInfo.Mappers[i], builder,
8911 moduleTranslation, targetDirective);
8912 };
8913
8914 llvm::Expected<llvm::Function *> newFn = ompBuilder->emitUserDefinedMapper(
8915 genMapInfoCB, varType, mapperFuncName, customMapperCB,
8916 /*PreserveMemberOfFlags=*/true, /*PropagatePresentToPointeeStorage=*/true,
8917 /*PropagatePresentToNonPointeeStorage=*/true);
8918 if (!newFn)
8919 return newFn.takeError();
8920 if ([[maybe_unused]] llvm::Function *mappedFunc =
8921 moduleTranslation.lookupFunction(mapperFuncName)) {
8922 assert(mappedFunc == *newFn &&
8923 "mapper function mapping disagrees with emitted function");
8924 } else {
8925 moduleTranslation.mapFunction(mapperFuncName, *newFn);
8926 }
8927 return *newFn;
8928}
8929
8930static llvm::Value *getSourceLocIdentFromOp(llvm::IRBuilderBase &builder,
8931 llvm::OpenMPIRBuilder &ompBuilder,
8932 Operation *op) {
8933 auto fileLoc = op->getLoc()->findInstanceOf<FileLineColLoc>();
8934 if (!fileLoc)
8935 return nullptr;
8936 uint32_t strSize;
8937 llvm::Function *parentFn = builder.GetInsertBlock()->getParent();
8938 llvm::StringRef fnName = parentFn ? parentFn->getName() : "";
8939 llvm::Constant *srcStr = LLVM::createSourceLocStrFromLocation(
8940 fileLoc, ompBuilder, fnName, strSize);
8941 return ompBuilder.getOrCreateIdent(srcStr, strSize);
8942}
8943
8944static LogicalResult
8945convertOmpTargetData(Operation *op, llvm::IRBuilderBase &builder,
8946 LLVM::ModuleTranslation &moduleTranslation) {
8947 llvm::Value *ifCond = nullptr;
8948 llvm::Value *deviceID = builder.getInt64(llvm::omp::OMP_DEVICEID_UNDEF);
8949 SmallVector<Value> mapVars;
8950 SmallVector<Value> useDevicePtrVars;
8951 SmallVector<Value> useDeviceAddrVars;
8952 llvm::omp::RuntimeFunction RTLFn;
8953 DataLayout DL = DataLayout(op->getParentOfType<ModuleOp>());
8954 TargetDirectiveEnumTy targetDirective = getTargetDirectiveEnumTyFromOp(op);
8955
8956 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
8957 llvm::OpenMPIRBuilder::TargetDataInfo info(
8958 /*RequiresDevicePointerInfo=*/true,
8959 /*SeparateBeginEndCalls=*/true);
8960
8961 if (ompBuilder->Config.isTargetDevice())
8962 return op->emitOpError() << "not allowed in a target device";
8963
8964 bool isOffloadEntry = !ompBuilder->Config.TargetTriples.empty();
8965
8966 auto getDeviceID = [&](mlir::Value dev) -> llvm::Value * {
8967 llvm::Value *v = moduleTranslation.lookupValue(dev);
8968 return builder.CreateIntCast(v, builder.getInt64Ty(), /*isSigned=*/true);
8969 };
8970
8971 LogicalResult result =
8973 .Case([&](omp::TargetDataOp dataOp) {
8974 if (failed(checkImplementationStatus(*dataOp)))
8975 return failure();
8976
8977 if (auto ifVar = dataOp.getIfExpr())
8978 ifCond = moduleTranslation.lookupValue(ifVar);
8979
8980 if (mlir::Value devId = dataOp.getDevice())
8981 deviceID = getDeviceID(devId);
8982
8983 mapVars = dataOp.getMapVars();
8984 useDevicePtrVars = dataOp.getUseDevicePtrVars();
8985 useDeviceAddrVars = dataOp.getUseDeviceAddrVars();
8986 return success();
8987 })
8988 .Case([&](omp::TargetEnterDataOp enterDataOp) -> LogicalResult {
8989 if (failed(checkImplementationStatus(*enterDataOp)))
8990 return failure();
8991
8992 if (auto ifVar = enterDataOp.getIfExpr())
8993 ifCond = moduleTranslation.lookupValue(ifVar);
8994
8995 if (mlir::Value devId = enterDataOp.getDevice())
8996 deviceID = getDeviceID(devId);
8997
8998 RTLFn =
8999 enterDataOp.getNowait()
9000 ? llvm::omp::OMPRTL___tgt_target_data_begin_nowait_mapper
9001 : llvm::omp::OMPRTL___tgt_target_data_begin_mapper;
9002 mapVars = enterDataOp.getMapVars();
9003 info.HasNoWait = enterDataOp.getNowait();
9004 return success();
9005 })
9006 .Case([&](omp::TargetExitDataOp exitDataOp) -> LogicalResult {
9007 if (failed(checkImplementationStatus(*exitDataOp)))
9008 return failure();
9009
9010 if (auto ifVar = exitDataOp.getIfExpr())
9011 ifCond = moduleTranslation.lookupValue(ifVar);
9012
9013 if (mlir::Value devId = exitDataOp.getDevice())
9014 deviceID = getDeviceID(devId);
9015
9016 RTLFn = exitDataOp.getNowait()
9017 ? llvm::omp::OMPRTL___tgt_target_data_end_nowait_mapper
9018 : llvm::omp::OMPRTL___tgt_target_data_end_mapper;
9019 mapVars = exitDataOp.getMapVars();
9020 info.HasNoWait = exitDataOp.getNowait();
9021 return success();
9022 })
9023 .Case([&](omp::TargetUpdateOp updateDataOp) -> LogicalResult {
9024 if (failed(checkImplementationStatus(*updateDataOp)))
9025 return failure();
9026
9027 if (auto ifVar = updateDataOp.getIfExpr())
9028 ifCond = moduleTranslation.lookupValue(ifVar);
9029
9030 if (mlir::Value devId = updateDataOp.getDevice())
9031 deviceID = getDeviceID(devId);
9032
9033 RTLFn =
9034 updateDataOp.getNowait()
9035 ? llvm::omp::OMPRTL___tgt_target_data_update_nowait_mapper
9036 : llvm::omp::OMPRTL___tgt_target_data_update_mapper;
9037 mapVars = updateDataOp.getMapVars();
9038 info.HasNoWait = updateDataOp.getNowait();
9039 return success();
9040 })
9041 .DefaultUnreachable("unexpected operation");
9042
9043 if (failed(result))
9044 return failure();
9045 // Pretend we have IF(false) if we're not doing offload.
9046 if (!isOffloadEntry)
9047 ifCond = builder.getFalse();
9048
9049 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
9050 MapInfoData mapData;
9051 collectMapDataFromMapOperands(mapData, mapVars, moduleTranslation, DL,
9052 builder, useDevicePtrVars, useDeviceAddrVars);
9053
9054 // Fill up the arrays with all the mapped variables.
9055 MapInfosTy combinedInfo;
9056 auto genMapInfoCB = [&](InsertPointTy codeGenIP) -> MapInfosTy & {
9057 builder.restoreIP(codeGenIP);
9058 genMapInfos(builder, moduleTranslation, DL, combinedInfo, mapData,
9059 targetDirective);
9060 return combinedInfo;
9061 };
9062
9063 // Define a lambda to apply mappings between use_device_addr and
9064 // use_device_ptr base pointers, and their associated block arguments.
9065 auto mapUseDevice =
9066 [&moduleTranslation](
9067 llvm::OpenMPIRBuilder::DeviceInfoTy type,
9069 llvm::SmallVectorImpl<Value> &useDeviceVars, MapInfoData &mapInfoData,
9070 llvm::function_ref<llvm::Value *(llvm::Value *)> mapper = nullptr) {
9071 for (auto [arg, useDevVar] :
9072 llvm::zip_equal(blockArgs, useDeviceVars)) {
9073
9074 auto getMapBasePtr = [](omp::MapInfoOp mapInfoOp) {
9075 return mapInfoOp.getVarPtrPtr() ? mapInfoOp.getVarPtrPtr()
9076 : mapInfoOp.getVarPtr();
9077 };
9078
9079 auto useDevMap = cast<omp::MapInfoOp>(useDevVar.getDefiningOp());
9080 for (auto [mapClause, devicePointer, basePointer] : llvm::zip_equal(
9081 mapInfoData.MapClause, mapInfoData.DevicePointers,
9082 mapInfoData.BasePointers)) {
9083 auto mapOp = cast<omp::MapInfoOp>(mapClause);
9084 if (getMapBasePtr(mapOp) != getMapBasePtr(useDevMap) ||
9085 devicePointer != type)
9086 continue;
9087
9088 if (llvm::Value *devPtrInfoMap =
9089 mapper ? mapper(basePointer) : basePointer) {
9090 moduleTranslation.mapValue(arg, devPtrInfoMap);
9091 break;
9092 }
9093 }
9094 }
9095 };
9096
9097 using BodyGenTy = llvm::OpenMPIRBuilder::BodyGenTy;
9098 auto bodyGenCB = [&](InsertPointTy codeGenIP, BodyGenTy bodyGenType)
9099 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
9100 // We must always restoreIP regardless of doing anything the caller
9101 // does not restore it, leading to incorrect (no) branch generation.
9102 builder.restoreIP(codeGenIP);
9103 assert(isa<omp::TargetDataOp>(op) &&
9104 "BodyGen requested for non TargetDataOp");
9105 auto blockArgIface = cast<omp::BlockArgOpenMPOpInterface>(op);
9106 Region &region = cast<omp::TargetDataOp>(op).getRegion();
9107 switch (bodyGenType) {
9108 case BodyGenTy::Priv:
9109 // Check if any device ptr/addr info is available
9110 if (!info.DevicePtrInfoMap.empty()) {
9111 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Address,
9112 blockArgIface.getUseDeviceAddrBlockArgs(),
9113 useDeviceAddrVars, mapData,
9114 [&](llvm::Value *basePointer) -> llvm::Value * {
9115 if (!info.DevicePtrInfoMap[basePointer].second)
9116 return nullptr;
9117 return builder.CreateLoad(
9118 builder.getPtrTy(),
9119 info.DevicePtrInfoMap[basePointer].second);
9120 });
9121 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer,
9122 blockArgIface.getUseDevicePtrBlockArgs(), useDevicePtrVars,
9123 mapData, [&](llvm::Value *basePointer) {
9124 return info.DevicePtrInfoMap[basePointer].second;
9125 });
9126
9127 if (failed(inlineConvertOmpRegions(region, "omp.data.region", builder,
9128 moduleTranslation)))
9129 return llvm::make_error<PreviouslyReportedError>();
9130 }
9131 break;
9132 case BodyGenTy::DupNoPriv:
9133 if (info.DevicePtrInfoMap.empty()) {
9134 // For host device we still need to do the mapping for codegen,
9135 // otherwise it may try to lookup a missing value.
9136 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Address,
9137 blockArgIface.getUseDeviceAddrBlockArgs(),
9138 useDeviceAddrVars, mapData);
9139 mapUseDevice(llvm::OpenMPIRBuilder::DeviceInfoTy::Pointer,
9140 blockArgIface.getUseDevicePtrBlockArgs(), useDevicePtrVars,
9141 mapData);
9142 }
9143 break;
9144 case BodyGenTy::NoPriv:
9145 // If device info is available then region has already been generated
9146 if (info.DevicePtrInfoMap.empty()) {
9147 if (failed(inlineConvertOmpRegions(region, "omp.data.region", builder,
9148 moduleTranslation)))
9149 return llvm::make_error<PreviouslyReportedError>();
9150 }
9151 break;
9152 }
9153 return builder.saveIP();
9154 };
9155
9156 auto customMapperCB =
9157 [&](unsigned int i) -> llvm::Expected<llvm::Function *> {
9158 if (!combinedInfo.Mappers[i])
9159 return nullptr;
9160 info.HasMapper = true;
9161 return getOrCreateUserDefinedMapperFunc(combinedInfo.Mappers[i], builder,
9162 moduleTranslation, targetDirective);
9163 };
9164
9165 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
9167 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
9168 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
9169
9170 // Pass the region's source location to the runtime, taken from the op's own
9171 // location; only offloading entries emit the mapper calls that consume it.
9172 // No need to also guard on !isTargetDevice here: this function bails out
9173 // earlier for the target device, so isOffloadEntry alone is sufficient.
9174 llvm::Value *srcLocOverride =
9175 isOffloadEntry ? getSourceLocIdentFromOp(builder, *ompBuilder, op)
9176 : nullptr;
9177
9178 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP = [&]() {
9179 if (isa<omp::TargetDataOp>(op))
9180 return ompBuilder->createTargetData(
9181 ompLoc, allocaIP, builder.saveIP(), deallocBlocks, deviceID, ifCond,
9182 info, genMapInfoCB, customMapperCB,
9183 /*MapperFunc=*/nullptr, bodyGenCB,
9184 /*DeviceAddrCB=*/nullptr, srcLocOverride);
9185 return ompBuilder->createTargetData(
9186 ompLoc, allocaIP, builder.saveIP(), deallocBlocks, deviceID, ifCond,
9187 info, genMapInfoCB, customMapperCB, &RTLFn,
9188 /*BodyGenCB=*/nullptr,
9189 /*DeviceAddrCB=*/nullptr, srcLocOverride);
9190 }();
9191
9192 if (failed(handleError(afterIP, *op)))
9193 return failure();
9194
9195 builder.restoreIP(*afterIP);
9196 return success();
9197}
9198
9199static LogicalResult
9200convertOmpDistribute(Operation &opInst, llvm::IRBuilderBase &builder,
9201 LLVM::ModuleTranslation &moduleTranslation) {
9202 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
9203 auto distributeOp = cast<omp::DistributeOp>(opInst);
9204 if (failed(checkImplementationStatus(opInst)))
9205 return failure();
9206
9207 /// Process teams op reduction in distribute if the reduction is contained in
9208 /// this specific distribute op.
9209 omp::TeamsOp teamsOp = opInst.getParentOfType<omp::TeamsOp>();
9210 bool doDistributeReduction =
9211 teamsOp && getDistributeCapturingTeamsReduction(teamsOp) == distributeOp;
9212
9213 DenseMap<Value, llvm::Value *> reductionVariableMap;
9214 unsigned numReductionVars = teamsOp ? teamsOp.getNumReductionVars() : 0;
9216 SmallVector<llvm::Value *> privateReductionVariables(numReductionVars);
9217 llvm::ArrayRef<bool> isByRef;
9218
9219 if (doDistributeReduction) {
9220 isByRef = getIsByRef(teamsOp.getReductionByref());
9221 assert(isByRef.size() == teamsOp.getNumReductionVars());
9222
9223 collectReductionDecls(teamsOp, reductionDecls);
9224 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
9225 findAllocInsertPoints(builder, moduleTranslation);
9226
9227 MutableArrayRef<BlockArgument> reductionArgs =
9228 llvm::cast<omp::BlockArgOpenMPOpInterface>(*teamsOp)
9229 .getReductionBlockArgs();
9230
9232 teamsOp, reductionArgs, builder, moduleTranslation, allocaIP,
9233 reductionDecls, privateReductionVariables, reductionVariableMap,
9234 isByRef)))
9235 return failure();
9236 }
9237
9238 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
9239 auto bodyGenCB =
9240 [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
9241 llvm::ArrayRef<llvm::BasicBlock *> deallocBlocks) -> llvm::Error {
9242 llvm::BasicBlock *allocaBlock = allocaIP.getNodeParent();
9243 // DistributeOp has only one region associated with it.
9244 builder.restoreIP(codeGenIP);
9245 PrivateVarsInfo privVarsInfo(distributeOp);
9246
9248 distributeOp, builder, moduleTranslation, privVarsInfo, allocaIP);
9249 if (handleError(afterAllocas, opInst).failed())
9250 return llvm::make_error<PreviouslyReportedError>();
9251
9252 // Save the alloca insertion point on ModuleTranslation stack for use in
9253 // nested regions. This must be after allocatePrivateVars, which splits
9254 // the alloca block and updates allocaIP.
9256 moduleTranslation, allocaIP.getNodeParent(), deallocBlocks);
9257
9258 if (handleError(initPrivateVars(builder, moduleTranslation, privVarsInfo),
9259 opInst)
9260 .failed())
9261 return llvm::make_error<PreviouslyReportedError>();
9262
9263 if (failed(completePrivateVars(distributeOp, builder, moduleTranslation,
9264 privVarsInfo.mlirVars, privVarsInfo.llvmVars,
9265 privVarsInfo.privatizers,
9266 distributeOp.getPrivateNeedsBarrier())))
9267 return llvm::make_error<PreviouslyReportedError>();
9268
9269 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
9270 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
9272 convertOmpOpRegions(distributeOp.getRegion(), "omp.distribute.region",
9273 builder, moduleTranslation);
9274 if (!regionBlock)
9275 return regionBlock.takeError();
9276 builder.SetInsertPoint((*regionBlock)->begin());
9277
9278 // Skip applying a workshare loop below when translating 'distribute
9279 // parallel do' (it's been already handled by this point while translating
9280 // the nested omp.wsloop).
9281 if (!isa_and_present<omp::WsloopOp>(distributeOp.getNestedWrapper())) {
9282 // TODO: Add support for clauses which are valid for DISTRIBUTE
9283 // constructs. Static schedule is the default.
9284 bool hasDistSchedule = distributeOp.getDistScheduleStatic();
9285 auto schedule = hasDistSchedule ? omp::ClauseScheduleKind::Distribute
9286 : omp::ClauseScheduleKind::Static;
9287 // dist_schedule clauses are ordered - otherise this should be false
9288 bool isOrdered = hasDistSchedule;
9289 std::optional<omp::ScheduleModifier> scheduleMod;
9290 bool isSimd = false;
9291 llvm::omp::WorksharingLoopType workshareLoopType =
9292 llvm::omp::WorksharingLoopType::DistributeStaticLoop;
9293 bool loopNeedsBarrier = false;
9294 llvm::Value *chunk = moduleTranslation.lookupValue(
9295 distributeOp.getDistScheduleChunkSize());
9296 llvm::CanonicalLoopInfo *loopInfo =
9297 findCurrentLoopInfo(moduleTranslation);
9298 // Nested regions can move the instruction referenced by allocaIP.
9299 allocaIP = allocaBlock->getFirstInsertionPt();
9300 llvm::OpenMPIRBuilder::InsertPointOrErrorTy wsloopIP =
9301 ompBuilder->applyWorkshareLoop(
9302 ompLoc.DL, loopInfo, allocaIP, loopNeedsBarrier,
9303 convertToScheduleKind(schedule), chunk, isSimd,
9304 scheduleMod == omp::ScheduleModifier::monotonic,
9305 scheduleMod == omp::ScheduleModifier::nonmonotonic, isOrdered,
9306 workshareLoopType, false, hasDistSchedule, chunk);
9307
9308 if (!wsloopIP)
9309 return wsloopIP.takeError();
9310 }
9311 if (failed(cleanupPrivateVars(distributeOp, builder, moduleTranslation,
9312 distributeOp.getLoc(), privVarsInfo)))
9313 return llvm::make_error<PreviouslyReportedError>();
9314
9315 return llvm::Error::success();
9316 };
9317
9319 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
9320 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
9321 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
9322 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
9323 ompBuilder->createDistribute(ompLoc, allocaIP, deallocBlocks, bodyGenCB);
9324
9325 if (failed(handleError(afterIP, opInst)))
9326 return failure();
9327
9328 builder.restoreIP(*afterIP);
9329
9330 if (doDistributeReduction) {
9331 // Process the reductions if required.
9333 teamsOp, builder, moduleTranslation, allocaIP, reductionDecls,
9334 privateReductionVariables, isByRef,
9335 /*isNoWait*/ false, /*isTeamsReduction*/ true);
9336 }
9337 return success();
9338}
9339
9340/// Lowers the FlagsAttr which is applied to the module when offloading. This
9341/// attribute contains OpenMP RTL globals that can be passed as flags to the
9342/// frontend, otherwise they are set to default
9343static LogicalResult
9344convertFlagsAttr(Operation *op, mlir::omp::FlagsAttr attribute,
9345 LLVM::ModuleTranslation &moduleTranslation) {
9346 auto offloadMod = dyn_cast<omp::OffloadModuleInterface>(op);
9347 if (!offloadMod)
9348 return op->emitOpError() << "omp flags attached to non offload module op";
9349
9350 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
9351
9352 if (offloadMod.getIsTargetDevice())
9353 ompBuilder->M.addModuleFlag(llvm::Module::Max, "openmp-device",
9354 attribute.getOpenmpDeviceVersion());
9355
9356 // The flags below are only intended to be emitted for GPU offload targets.
9357 if (!offloadMod.getIsGPU())
9358 return success();
9359
9360 if (attribute.getNoGpuLib())
9361 return success();
9362
9363 ompBuilder->createGlobalFlag(attribute.getDebugKind(),
9364 "__omp_rtl_debug_kind");
9365 ompBuilder->createGlobalFlag(attribute.getAssumeTeamsOversubscription(),
9366 "__omp_rtl_assume_teams_oversubscription");
9367 ompBuilder->createGlobalFlag(attribute.getAssumeThreadsOversubscription(),
9368 "__omp_rtl_assume_threads_oversubscription");
9369 ompBuilder->createGlobalFlag(attribute.getAssumeNoThreadState(),
9370 "__omp_rtl_assume_no_thread_state");
9371 ompBuilder->createGlobalFlag(attribute.getAssumeNoNestedParallelism(),
9372 "__omp_rtl_assume_no_nested_parallelism");
9373 return success();
9374}
9375
9376static void getTargetEntryUniqueInfo(llvm::TargetRegionEntryInfo &targetInfo,
9377 omp::TargetOp targetOp,
9378 llvm::OpenMPIRBuilder &ompBuilder,
9379 llvm::vfs::FileSystem &vfs,
9380 llvm::StringRef parentName = "") {
9381 auto fileLoc = targetOp.getLoc()->findInstanceOf<FileLineColLoc>();
9382 assert(fileLoc && "No file found from location");
9383
9384 auto fileInfoCallBack = [&fileLoc]() {
9385 return std::pair<std::string, uint64_t>(
9386 llvm::StringRef(fileLoc.getFilename()), fileLoc.getLine());
9387 };
9388
9389 targetInfo =
9390 ompBuilder.getTargetEntryUniqueInfo(fileInfoCallBack, vfs, parentName);
9391}
9392
9393// The createDeviceArgumentAccessor function generates
9394// instructions for retrieving (acessing) kernel
9395// arguments inside of the device kernel for use by
9396// the kernel. This enables different semantics such as
9397// the creation of temporary copies of data allowing
9398// semantics like read-only/no host write back kernel
9399// arguments.
9400//
9401// This currently implements a very light version of Clang's
9402// EmitParmDecl's handling of direct argument handling as well
9403// as a portion of the argument access generation based on
9404// capture types found at the end of emitOutlinedFunctionPrologue
9405// in Clang. The indirect path handling of EmitParmDecl's may be
9406// required for future work, but a direct 1-to-1 copy doesn't seem
9407// possible as the logic is rather scattered throughout Clang's
9408// lowering and perhaps we wish to deviate slightly.
9409//
9410// \param mapData - A container containing vectors of information
9411// corresponding to the input argument, which should have a
9412// corresponding entry in the MapInfoData containers
9413// OrigialValue's.
9414// \param arg - This is the generated kernel function argument that
9415// corresponds to the passed in input argument. We generated different
9416// accesses of this Argument, based on capture type and other Input
9417// related information.
9418// \param input - This is the host side value that will be passed to
9419// the kernel i.e. the kernel input, we rewrite all uses of this within
9420// the kernel (as we generate the kernel body based on the target's region
9421// which maintians references to the original input) to the retVal argument
9422// apon exit of this function inside of the OMPIRBuilder. This interlinks
9423// the kernel argument to future uses of it in the function providing
9424// appropriate "glue" instructions inbetween.
9425// \param retVal - This is the value that all uses of input inside of the
9426// kernel will be re-written to, the goal of this function is to generate
9427// an appropriate location for the kernel argument to be accessed from,
9428// e.g. ByRef will result in a temporary allocation location and then
9429// a store of the kernel argument into this allocated memory which
9430// will then be loaded from, ByCopy will use the allocated memory
9431// directly.
9432static llvm::IRBuilderBase::InsertPoint createDeviceArgumentAccessor(
9433 omp::TargetOp targetOp, MapInfoData &mapData, llvm::Argument &arg,
9434 llvm::Value *input, llvm::Value *&retVal, llvm::IRBuilderBase &builder,
9435 llvm::OpenMPIRBuilder &ompBuilder,
9436 LLVM::ModuleTranslation &moduleTranslation,
9437 llvm::IRBuilderBase::InsertPoint allocaIP,
9438 llvm::IRBuilderBase::InsertPoint codeGenIP,
9440 assert(ompBuilder.Config.isTargetDevice() &&
9441 "function only supported for target device codegen");
9442 builder.restoreIP(allocaIP);
9443
9444 omp::VariableCaptureKind capture = omp::VariableCaptureKind::ByRef;
9445 LLVM::TypeToLLVMIRTranslator typeToLLVMIRTranslator(
9446 ompBuilder.M.getContext());
9447 unsigned alignmentValue = 0;
9448 BlockArgument mlirArg;
9450 cast<omp::BlockArgOpenMPOpInterface>(*targetOp).getBlockArgsPairs(
9451 blockArgsPairs);
9452 // Find the associated MapInfoData entry for the current input
9453 for (size_t i = 0; i < mapData.MapClause.size(); ++i) {
9454 if (mapData.OriginalValue[i] == input) {
9455 auto mapOp = cast<omp::MapInfoOp>(mapData.MapClause[i]);
9456 capture = mapOp.getMapCaptureType();
9457 // Get information of alignment of mapped object
9458 alignmentValue = typeToLLVMIRTranslator.getPreferredAlignment(
9459 mapOp.getVarPtrType(), ompBuilder.M.getDataLayout());
9460
9461 // Find the corresponding entry block argument, which can be associated to
9462 // a map, use_device* or has_device* clause.
9463 for (auto &[val, arg] : blockArgsPairs) {
9464 if (mapOp.getResult() == val) {
9465 mlirArg = arg;
9466 break;
9467 }
9468 }
9469 assert(mlirArg && "expected to find entry block argument for map clause");
9470 break;
9471 }
9472 }
9473
9474 unsigned int allocaAS = ompBuilder.M.getDataLayout().getAllocaAddrSpace();
9475 unsigned int defaultAS =
9476 ompBuilder.M.getDataLayout().getProgramAddressSpace();
9477
9478 // Create the allocation for the argument.
9479 llvm::Value *v = nullptr;
9480 if (omp::opInSharedDeviceContext(*targetOp) &&
9482 // Use the beginning of the codeGenIP rather than the usual allocation point
9483 // for shared memory allocations because otherwise these would be done prior
9484 // to the target initialization call. Also, the exit block (where the
9485 // deallocation is placed) is only executed if the initialization call
9486 // succeeds.
9487 builder.SetInsertPoint(codeGenIP.getNodeParent()->getFirstInsertionPt());
9488 v = ompBuilder.createOMPAllocShared(builder, arg.getType());
9489
9490 // Create deallocations in all provided deallocation points and then restore
9491 // the insertion point to right after the new allocations.
9492 llvm::IRBuilderBase::InsertPointGuard guard(builder);
9493 for (auto deallocIP : deallocIPs) {
9494 builder.SetInsertPoint(deallocIP);
9495 ompBuilder.createOMPFreeShared(builder, v, arg.getType());
9496 }
9497 } else {
9498 // Use the current point, which was previously set to allocaIP.
9499 v = builder.CreateAlloca(arg.getType(), allocaAS);
9500
9501 if (allocaAS != defaultAS && arg.getType()->isPointerTy())
9502 v = builder.CreateAddrSpaceCast(v, builder.getPtrTy(defaultAS));
9503 }
9504
9505 builder.CreateStore(&arg, v);
9506
9507 builder.restoreIP(codeGenIP);
9508
9509 switch (capture) {
9510 case omp::VariableCaptureKind::ByCopy: {
9511 retVal = v;
9512 break;
9513 }
9514 case omp::VariableCaptureKind::ByRef: {
9515 llvm::LoadInst *loadInst = builder.CreateAlignedLoad(
9516 v->getType(), v,
9517 ompBuilder.M.getDataLayout().getPrefTypeAlign(v->getType()));
9518 // CreateAlignedLoad function creates similar LLVM IR:
9519 // %res = load ptr, ptr %input, align 8
9520 // This LLVM IR does not contain information about alignment
9521 // of the loaded value. We need to add !align metadata to unblock
9522 // optimizer. The existence of the !align metadata on the instruction
9523 // tells the optimizer that the value loaded is known to be aligned to
9524 // a boundary specified by the integer value in the metadata node.
9525 // Example:
9526 // %res = load ptr, ptr %input, align 8, !align !align_md_node
9527 // ^ ^
9528 // | |
9529 // alignment of %input address |
9530 // |
9531 // alignment of %res object
9532 if (v->getType()->isPointerTy() && alignmentValue) {
9533 llvm::MDBuilder MDB(builder.getContext());
9534 loadInst->setMetadata(
9535 llvm::LLVMContext::MD_align,
9536 llvm::MDNode::get(builder.getContext(),
9537 MDB.createConstant(llvm::ConstantInt::get(
9538 llvm::Type::getInt64Ty(builder.getContext()),
9539 alignmentValue))));
9540 }
9541 retVal = loadInst;
9542
9543 break;
9544 }
9545 case omp::VariableCaptureKind::This:
9546 case omp::VariableCaptureKind::VLAType:
9547 // TODO: Consider returning error to use standard reporting for
9548 // unimplemented features.
9549 assert(false && "Currently unsupported capture kind");
9550 break;
9551 }
9552
9553 return builder.saveIP();
9554}
9555
9556/// Follow uses of `host_eval`-defined block arguments of the given `omp.target`
9557/// operation and populate output variables with their corresponding host value
9558/// (i.e. operand evaluated outside of the target region), based on their uses
9559/// inside of the target region.
9560///
9561/// Loop bounds and steps are only optionally populated, if output vectors are
9562/// provided.
9563static void
9564extractHostEvalClauses(omp::TargetOp targetOp, Value &numThreads,
9565 Value &numTeamsLower, Value &numTeamsUpper,
9566 Value &threadLimit,
9567 llvm::SmallVectorImpl<Value> *lowerBounds = nullptr,
9568 llvm::SmallVectorImpl<Value> *upperBounds = nullptr,
9569 llvm::SmallVectorImpl<Value> *steps = nullptr) {
9570 auto blockArgIface = llvm::cast<omp::BlockArgOpenMPOpInterface>(*targetOp);
9571 for (auto item : llvm::zip_equal(targetOp.getHostEvalVars(),
9572 blockArgIface.getHostEvalBlockArgs())) {
9573 Value hostEvalVar = std::get<0>(item), blockArg = std::get<1>(item);
9574
9575 for (Operation *user : blockArg.getUsers()) {
9577 .Case([&](omp::TeamsOp teamsOp) {
9578 if (teamsOp.getNumTeamsLower() == blockArg)
9579 numTeamsLower = hostEvalVar;
9580 else if (llvm::is_contained(teamsOp.getNumTeamsUpperVars(),
9581 blockArg))
9582 numTeamsUpper = hostEvalVar;
9583 else if (!teamsOp.getThreadLimitVars().empty() &&
9584 teamsOp.getThreadLimit(0) == blockArg)
9585 threadLimit = hostEvalVar;
9586 else
9587 llvm_unreachable("unsupported host_eval use");
9588 })
9589 .Case([&](omp::ParallelOp parallelOp) {
9590 if (!parallelOp.getNumThreadsVars().empty() &&
9591 parallelOp.getNumThreads(0) == blockArg)
9592 numThreads = hostEvalVar;
9593 else
9594 llvm_unreachable("unsupported host_eval use");
9595 })
9596 .Case([&](omp::LoopNestOp loopOp) {
9597 auto processBounds =
9598 [&](OperandRange opBounds,
9599 llvm::SmallVectorImpl<Value> *outBounds) -> bool {
9600 bool found = false;
9601 for (auto [i, lb] : llvm::enumerate(opBounds)) {
9602 if (lb == blockArg) {
9603 found = true;
9604 if (outBounds)
9605 (*outBounds)[i] = hostEvalVar;
9606 }
9607 }
9608 return found;
9609 };
9610 bool found =
9611 processBounds(loopOp.getLoopLowerBounds(), lowerBounds);
9612 found = processBounds(loopOp.getLoopUpperBounds(), upperBounds) ||
9613 found;
9614 found = processBounds(loopOp.getLoopSteps(), steps) || found;
9615 (void)found;
9616 assert(found && "unsupported host_eval use");
9617 })
9618 .DefaultUnreachable("unsupported host_eval use");
9619 }
9620 }
9621}
9622
9623/// If \p op is of the given type parameter, return it casted to that type.
9624/// Otherwise, if its immediate parent operation (or some other higher-level
9625/// parent, if \p immediateParent is false) is of that type, return that parent
9626/// casted to the given type.
9627///
9628/// If \p op is \c null or neither it or its parent(s) are of the specified
9629/// type, return a \c null operation.
9630template <typename OpTy>
9631static OpTy castOrGetParentOfType(Operation *op, bool immediateParent = false) {
9632 if (!op)
9633 return OpTy();
9634
9635 if (OpTy casted = dyn_cast<OpTy>(op))
9636 return casted;
9637
9638 if (immediateParent)
9639 return dyn_cast_if_present<OpTy>(op->getParentOp());
9640
9641 return op->getParentOfType<OpTy>();
9642}
9643
9644/// If the given \p value is defined by an \c llvm.mlir.constant operation and
9645/// it is of an integer type, return its value.
9646static std::optional<int64_t> extractConstInteger(Value value) {
9647 if (!value)
9648 return std::nullopt;
9649
9650 if (auto constOp = value.getDefiningOp<LLVM::ConstantOp>())
9651 if (auto constAttr = dyn_cast<IntegerAttr>(constOp.getValue()))
9652 return constAttr.getInt();
9653
9654 return std::nullopt;
9655}
9656
9657static uint64_t getTypeByteSize(mlir::Type type, const DataLayout &dl) {
9658 uint64_t sizeInBits = dl.getTypeSizeInBits(type);
9659 uint64_t sizeInBytes = sizeInBits / 8;
9660 return sizeInBytes;
9661}
9662
9663template <typename OpTy>
9664static uint64_t getReductionDataSize(OpTy &op) {
9665 if (op.getNumReductionVars() > 0) {
9667 collectReductionDecls(op, reductions);
9668
9670 members.reserve(reductions.size());
9671 for (omp::DeclareReductionOp &red : reductions) {
9672 // For by-ref reductions, use the actual element type rather than the
9673 // pointer type so that the buffer size matches the access pattern in
9674 // the copy/reduce callbacks generated by OMPIRBuilder.
9675 if (red.getByrefElementType())
9676 members.push_back(*red.getByrefElementType());
9677 else
9678 members.push_back(red.getType());
9679 }
9680 Operation *opp = op.getOperation();
9681 auto structType = mlir::LLVM::LLVMStructType::getLiteral(
9682 opp->getContext(), members, /*isPacked=*/false);
9683 DataLayout dl = DataLayout(opp->getParentOfType<ModuleOp>());
9684 return getTypeByteSize(structType, dl);
9685 }
9686 return 0;
9687}
9688
9689/// Populate default `MinTeams`, `MaxTeams` and `MaxThreads` to their default
9690/// values as stated by the corresponding clauses, if constant.
9691///
9692/// These default values must be set before the creation of the outlined LLVM
9693/// function for the target region, so that they can be used to initialize the
9694/// corresponding global `ConfigurationEnvironmentTy` structure.
9695static void
9696initTargetDefaultAttrs(omp::TargetOp targetOp, Operation *capturedOp,
9697 llvm::OpenMPIRBuilder::TargetKernelDefaultAttrs &attrs,
9698 bool isTargetDevice, bool isGPU) {
9699 // TODO: Handle constant 'if' clauses.
9700
9701 Value numThreads, numTeamsLower, numTeamsUpper, threadLimit;
9702 if (!isTargetDevice) {
9703 extractHostEvalClauses(targetOp, numThreads, numTeamsLower, numTeamsUpper,
9704 threadLimit);
9705 } else {
9706 // In the target device, values for these clauses are not passed as
9707 // host_eval, but instead evaluated prior to entry to the region. This
9708 // ensures values are mapped and available inside of the target region.
9709 if (auto teamsOp = castOrGetParentOfType<omp::TeamsOp>(capturedOp)) {
9710 numTeamsLower = teamsOp.getNumTeamsLower();
9711 // Handle num_teams upper bounds (only first value for now)
9712 if (!teamsOp.getNumTeamsUpperVars().empty())
9713 numTeamsUpper = teamsOp.getNumTeams(0);
9714 if (!teamsOp.getThreadLimitVars().empty())
9715 threadLimit = teamsOp.getThreadLimit(0);
9716 }
9717
9718 if (auto parallelOp = castOrGetParentOfType<omp::ParallelOp>(capturedOp)) {
9719 if (!parallelOp.getNumThreadsVars().empty())
9720 numThreads = parallelOp.getNumThreads(0);
9721 }
9722 }
9723
9724 // Handle clauses impacting the number of teams.
9725
9726 int32_t minTeamsVal = 1, maxTeamsVal = -1;
9727 if (castOrGetParentOfType<omp::TeamsOp>(capturedOp)) {
9728 // TODO: Use `hostNumTeamsLower` to initialize `minTeamsVal`. For now,
9729 // match clang and set min and max to the same value.
9730 if (numTeamsUpper) {
9731 if (auto val = extractConstInteger(numTeamsUpper))
9732 minTeamsVal = maxTeamsVal = *val;
9733 } else {
9734 minTeamsVal = maxTeamsVal = 0;
9735 }
9736 } else if (castOrGetParentOfType<omp::ParallelOp>(capturedOp,
9737 /*immediateParent=*/true) ||
9739 /*immediateParent=*/true)) {
9740 minTeamsVal = maxTeamsVal = 1;
9741 } else {
9742 minTeamsVal = maxTeamsVal = -1;
9743 }
9744
9745 // Handle clauses impacting the number of threads.
9746
9747 auto setMaxValueFromClause = [](Value clauseValue, int32_t &result) {
9748 if (!clauseValue)
9749 return;
9750
9751 if (auto val = extractConstInteger(clauseValue))
9752 result = *val;
9753
9754 // Found an applicable clause, so it's not undefined. Mark as unknown
9755 // because it's not constant.
9756 if (result < 0)
9757 result = 0;
9758 };
9759
9760 // Extract 'thread_limit' clause from 'target' and 'teams' directives.
9761 int32_t targetThreadLimitVal = -1, teamsThreadLimitVal = -1;
9762 if (!targetOp.getThreadLimitVars().empty())
9763 setMaxValueFromClause(targetOp.getThreadLimit(0), targetThreadLimitVal);
9764 setMaxValueFromClause(threadLimit, teamsThreadLimitVal);
9765
9766 // Extract 'max_threads' clause from 'parallel' or set to 1 if it's SIMD.
9767 int32_t maxThreadsVal = -1;
9769 setMaxValueFromClause(numThreads, maxThreadsVal);
9770 else if (castOrGetParentOfType<omp::SimdOp>(capturedOp,
9771 /*immediateParent=*/true))
9772 maxThreadsVal = 1;
9773
9774 // For max values, < 0 means unset, == 0 means set but unknown. Select the
9775 // minimum value between 'max_threads' and 'thread_limit' clauses that were
9776 // set.
9777 int32_t combinedMaxThreadsVal = targetThreadLimitVal;
9778 if (combinedMaxThreadsVal < 0 ||
9779 (teamsThreadLimitVal >= 0 && teamsThreadLimitVal < combinedMaxThreadsVal))
9780 combinedMaxThreadsVal = teamsThreadLimitVal;
9781
9782 if (combinedMaxThreadsVal < 0 ||
9783 (maxThreadsVal >= 0 && maxThreadsVal < combinedMaxThreadsVal))
9784 combinedMaxThreadsVal = maxThreadsVal;
9785
9786 int32_t reductionDataSize = 0;
9787 if (isGPU && capturedOp) {
9788 if (auto teamsOp = castOrGetParentOfType<omp::TeamsOp>(capturedOp))
9789 reductionDataSize = getReductionDataSize(teamsOp);
9790 }
9791
9792 // Update kernel bounds structure for the `OpenMPIRBuilder` to use.
9793 // Use the kernel_type attribute set by the frontend instead of analyzing IR.
9794 omp::TargetExecMode execMode = targetOp.getKernelType();
9795 switch (execMode) {
9796 case omp::TargetExecMode::bare:
9797 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_BARE;
9798 break;
9799 case omp::TargetExecMode::generic:
9800 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_GENERIC;
9801 break;
9802 case omp::TargetExecMode::spmd:
9803 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_SPMD;
9804 break;
9805 case omp::TargetExecMode::spmd_no_loop:
9806 attrs.ExecFlags = llvm::omp::OMP_TGT_EXEC_MODE_SPMD_NO_LOOP;
9807 break;
9808 }
9809 attrs.MinTeams.front() = minTeamsVal;
9810 attrs.MaxTeams.front() = maxTeamsVal;
9811 attrs.MinThreads.front() = 1;
9812 attrs.MaxThreads.front() = combinedMaxThreadsVal;
9813 attrs.ReductionDataSize = reductionDataSize;
9814}
9815
9816/// Gather LLVM runtime values for all clauses evaluated in the host that are
9817/// passed to the kernel invocation.
9818///
9819/// This function must be called only when compiling for the host. Also, it will
9820/// only provide correct results if it's called after the body of \c targetOp
9821/// has been fully generated.
9822static void
9823initTargetRuntimeAttrs(llvm::IRBuilderBase &builder,
9824 LLVM::ModuleTranslation &moduleTranslation,
9825 omp::TargetOp targetOp, Operation *capturedOp,
9826 llvm::OpenMPIRBuilder::TargetKernelRuntimeAttrs &attrs) {
9827 omp::LoopNestOp loopOp = castOrGetParentOfType<omp::LoopNestOp>(capturedOp);
9828 unsigned numLoops = loopOp ? loopOp.getNumLoops() : 0;
9829
9830 Value numThreads, numTeamsLower, numTeamsUpper, teamsThreadLimit;
9831 llvm::SmallVector<Value> lowerBounds(numLoops), upperBounds(numLoops),
9832 steps(numLoops);
9833 extractHostEvalClauses(targetOp, numThreads, numTeamsLower, numTeamsUpper,
9834 teamsThreadLimit, &lowerBounds, &upperBounds, &steps);
9835
9836 // TODO: Handle constant 'if' clauses.
9837 if (!targetOp.getThreadLimitVars().empty()) {
9838 Value targetThreadLimit = targetOp.getThreadLimit(0);
9839 attrs.TargetThreadLimit.front() =
9840 moduleTranslation.lookupValue(targetThreadLimit);
9841 }
9842
9843 // The __kmpc_push_num_teams_51 function expects int32 as the arguments. So,
9844 // truncate or sign extend lower and upper num_teams bounds as well as
9845 // thread_limit to match int32 ABI requirements for the OpenMP runtime.
9846 if (numTeamsLower)
9847 attrs.MinTeams.front() = builder.CreateSExtOrTrunc(
9848 moduleTranslation.lookupValue(numTeamsLower), builder.getInt32Ty());
9849
9850 if (numTeamsUpper)
9851 attrs.MaxTeams.front() = builder.CreateSExtOrTrunc(
9852 moduleTranslation.lookupValue(numTeamsUpper), builder.getInt32Ty());
9853
9854 if (teamsThreadLimit)
9855 attrs.TeamsThreadLimit.front() = builder.CreateSExtOrTrunc(
9856 moduleTranslation.lookupValue(teamsThreadLimit), builder.getInt32Ty());
9857
9858 if (numThreads)
9859 attrs.MaxThreads.front() = moduleTranslation.lookupValue(numThreads);
9860
9861 if (targetOp.hasHostEvalTripCount()) {
9862 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
9863 attrs.LoopTripCount = nullptr;
9864
9865 // To calculate the trip count, we multiply together the trip counts of
9866 // every collapsed canonical loop. We don't need to create the loop nests
9867 // here, since we're only interested in the trip count.
9868 for (auto [loopLower, loopUpper, loopStep] :
9869 llvm::zip_equal(lowerBounds, upperBounds, steps)) {
9870 llvm::Value *lowerBound = moduleTranslation.lookupValue(loopLower);
9871 llvm::Value *upperBound = moduleTranslation.lookupValue(loopUpper);
9872 llvm::Value *step = moduleTranslation.lookupValue(loopStep);
9873
9874 if (!lowerBound || !upperBound || !step) {
9875 attrs.LoopTripCount = nullptr;
9876 break;
9877 }
9878
9879 llvm::OpenMPIRBuilder::LocationDescription loc(builder);
9880 llvm::Value *tripCount = ompBuilder->calculateCanonicalLoopTripCount(
9881 loc, lowerBound, upperBound, step, /*IsSigned=*/true,
9882 loopOp.getLoopInclusive());
9883
9884 if (!attrs.LoopTripCount) {
9885 attrs.LoopTripCount = tripCount;
9886 continue;
9887 }
9888
9889 // TODO: Enable UndefinedSanitizer to diagnose an overflow here.
9890 attrs.LoopTripCount = builder.CreateMul(attrs.LoopTripCount, tripCount,
9891 {}, /*HasNUW=*/true);
9892 }
9893 }
9894
9895 attrs.DeviceID = builder.getInt64(llvm::omp::OMP_DEVICEID_UNDEF);
9896 if (mlir::Value devId = targetOp.getDevice()) {
9897 attrs.DeviceID = moduleTranslation.lookupValue(devId);
9898 attrs.DeviceID =
9899 builder.CreateSExtOrTrunc(attrs.DeviceID, builder.getInt64Ty());
9900 }
9901}
9902
9903static llvm::omp::OMPDynGroupprivateFallbackType
9904getDynGroupprivateFallbackType(omp::FallbackModifierAttr fallbackAttr) {
9905 omp::FallbackModifier fb = fallbackAttr ? fallbackAttr.getValue()
9906 : omp::FallbackModifier::default_mem;
9907 switch (fb) {
9908 case omp::FallbackModifier::abort:
9909 return llvm::omp::OMPDynGroupprivateFallbackType::Abort;
9910 case omp::FallbackModifier::null:
9911 return llvm::omp::OMPDynGroupprivateFallbackType::Null;
9912 case omp::FallbackModifier::default_mem:
9913 return llvm::omp::OMPDynGroupprivateFallbackType::DefaultMem;
9914 }
9915
9916 llvm_unreachable("unexpected dyn_groupprivate fallback type");
9917}
9918
9919static LogicalResult
9920convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
9921 LLVM::ModuleTranslation &moduleTranslation) {
9922 auto targetOp = cast<omp::TargetOp>(opInst);
9923
9924 // The current debug location already has the DISubprogram for the outlined
9925 // function that will be created for the target op. We save it here so that
9926 // we can set it on the outlined function.
9927 llvm::DebugLoc outlinedFnLoc = builder.getCurrentDebugLocation();
9928 if (failed(checkImplementationStatus(opInst)))
9929 return failure();
9930
9931 // During the handling of target op, we will generate instructions in the
9932 // parent function like call to the oulined function or branch to a new
9933 // BasicBlock. We set the debug location here to parent function so that those
9934 // get the correct debug locations. For outlined functions, the normal MLIR op
9935 // conversion will automatically pick the correct location.
9936 llvm::BasicBlock *parentBB = builder.GetInsertBlock();
9937 assert(parentBB && "No insert block is set for the builder");
9938 llvm::Function *parentLLVMFn = parentBB->getParent();
9939 assert(parentLLVMFn && "Parent Function must be valid");
9940 if (llvm::DISubprogram *SP = parentLLVMFn->getSubprogram())
9941 builder.SetCurrentDebugLocation(llvm::DILocation::get(
9942 parentLLVMFn->getContext(), outlinedFnLoc.getLine(),
9943 outlinedFnLoc.getCol(), SP, outlinedFnLoc.getInlinedAt()));
9944
9945 // OMPIRBuilder emits runtime calls into the outlined function before bodyCB
9946 // below gets a chance to attach the subprogram to it, so it needs the
9947 // outlined function's location handed to it separately. Only pass it under
9948 // the same condition that decides whether the subprogram is attached at all:
9949 // a location may not be attached to an instruction in a function that has no
9950 // subprogram.
9951 llvm::DebugLoc outlinedFnDbgLoc;
9952 if (outlinedFnLoc && parentLLVMFn->getSubprogram())
9953 outlinedFnDbgLoc = outlinedFnLoc;
9954
9955 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
9956 bool isTargetDevice = ompBuilder->Config.isTargetDevice();
9957 bool isGPU = ompBuilder->Config.isGPU();
9958
9959 auto parentFn = opInst.getParentOfType<LLVM::LLVMFuncOp>();
9960 auto argIface = cast<omp::BlockArgOpenMPOpInterface>(opInst);
9961 auto &targetRegion = targetOp.getRegion();
9962 // Holds the private vars that have been mapped along with the block
9963 // argument that corresponds to the MapInfoOp corresponding to the private
9964 // var in question. So, for instance:
9965 //
9966 // %10 = omp.map.info var_ptr(%6#0 : !fir.ref<!fir.box<!fir.heap<i32>>>, ..)
9967 // omp.target map_entries(%10 -> %arg0) private(@box.privatizer %6#0-> %arg1)
9968 //
9969 // Then, %10 has been created so that the descriptor can be used by the
9970 // privatizer @box.privatizer on the device side. Here we'd record {%6#0,
9971 // %arg0} in the mappedPrivateVars map.
9972 llvm::DenseMap<Value, Value> mappedPrivateVars;
9973 DataLayout dl = DataLayout(opInst.getParentOfType<ModuleOp>());
9974 SmallVector<Value> mapVars = targetOp.getMapVars();
9975 SmallVector<Value> hdaVars = targetOp.getHasDeviceAddrVars();
9976 ArrayRef<BlockArgument> mapBlockArgs = argIface.getMapBlockArgs();
9977 ArrayRef<BlockArgument> hdaBlockArgs = argIface.getHasDeviceAddrBlockArgs();
9978 llvm::Function *llvmOutlinedFn = nullptr;
9979 TargetDirectiveEnumTy targetDirective =
9980 getTargetDirectiveEnumTyFromOp(&opInst);
9981
9982 // TODO: It can also be false if a compile-time constant `false` IF clause is
9983 // specified.
9984 bool isOffloadEntry =
9985 isTargetDevice || !ompBuilder->Config.TargetTriples.empty();
9986
9987 // Resolve in_reduction clauses on omp.target for the host. From the target
9988 // device's perspective an in_reduction list item behaves as a regular
9989 // map(tofrom) variable, so no special handling is needed there; only the
9990 // host redirects the mapped value to the per-task reduction-private storage
9991 // returned by __kmpc_task_reduction_get_th_data (emitted inside the
9992 // to-be-outlined target task body). This applies to both offloading and
9993 // non-offloading host modules.
9994 //
9995 // The target body has no dedicated in_reduction block argument: each
9996 // in_reduction variable is accessed through its map_entries block argument.
9997 // So each in_reduction variable must also be captured by a matching
9998 // map_entries entry (guaranteed by the verifier); without one the outlined
9999 // body would reference a value defined in the host function. Record, for each
10000 // in_reduction variable, the position of that map entry so the corresponding
10001 // map block argument can be redirected inside the body. The in_reduction
10002 // operand itself is used as the `orig` argument of the runtime lookup.
10003 SmallVector<llvm::Value *> inRedOrigPtrs;
10004 SmallVector<unsigned> inRedMapArgIdx;
10005 if (!targetOp.getInReductionVars().empty() && !isTargetDevice) {
10006 inRedOrigPtrs.reserve(targetOp.getInReductionVars().size());
10007 inRedMapArgIdx.reserve(targetOp.getInReductionVars().size());
10008 for (Value v : targetOp.getInReductionVars()) {
10009 // Select the map_entries entry that captures this in_reduction operand.
10010 // The verifier guarantees at least one match exists; more than one
10011 // matching entry is a lowering ambiguity (the redirect cannot pick which
10012 // map argument to rebind).
10013 std::optional<unsigned> matchIdx;
10014 for (auto [idx, mapV] : llvm::enumerate(targetOp.getMapVars())) {
10015 auto mapInfo = mapV.getDefiningOp<omp::MapInfoOp>();
10016 if (v != mapInfo.getVarPtr())
10017 continue;
10018 if (matchIdx)
10019 return targetOp.emitError()
10020 << "in_reduction variable on omp.target has multiple matching "
10021 "map_entries entries; the redirect target is ambiguous";
10022 matchIdx = idx;
10023 }
10024 // The verifier requires a capturing map entry for every in_reduction
10025 // operand, so a match must exist here.
10026 assert(matchIdx &&
10027 "TargetOp verifier guarantees a matching map_entries entry for "
10028 "each in_reduction variable");
10029 inRedMapArgIdx.push_back(*matchIdx);
10030 // The runtime `orig` pointer is the in_reduction operand itself, the
10031 // reduction variable the enclosing taskgroup registered.
10032 inRedOrigPtrs.push_back(moduleTranslation.lookupValue(v));
10033 }
10034 }
10035
10036 // For some private variables, the MapsForPrivatizedVariablesPass
10037 // creates MapInfoOp instances. Go through the private variables and
10038 // the mapped variables so that during codegeneration we are able
10039 // to quickly look up the corresponding map variable, if any for each
10040 // private variable.
10041 if (!targetOp.getPrivateVars().empty() && !targetOp.getMapVars().empty()) {
10042 OperandRange privateVars = targetOp.getPrivateVars();
10043 std::optional<ArrayAttr> privateSyms = targetOp.getPrivateSyms();
10044 std::optional<DenseI64ArrayAttr> privateMapIndices =
10045 targetOp.getPrivateMapsAttr();
10046
10047 for (auto [privVarIdx, privVarSymPair] :
10048 llvm::enumerate(llvm::zip_equal(privateVars, *privateSyms))) {
10049 auto privVar = std::get<0>(privVarSymPair);
10050 auto privSym = std::get<1>(privVarSymPair);
10051
10052 SymbolRefAttr privatizerName = llvm::cast<SymbolRefAttr>(privSym);
10053 omp::PrivateClauseOp privatizer =
10054 findPrivatizer(targetOp, privatizerName);
10055
10056 if (!privatizer.needsMap())
10057 continue;
10058
10059 mlir::Value mappedValue =
10060 targetOp.getMappedValueForPrivateVar(privVarIdx);
10061 assert(mappedValue && "Expected to find mapped value for a privatized "
10062 "variable that needs mapping");
10063
10064 // The MapInfoOp defining the map var isn't really needed later.
10065 // So, we don't store it in any datastructure. Instead, we just
10066 // do some sanity checks on it right now.
10067 auto mapInfoOp = mappedValue.getDefiningOp<omp::MapInfoOp>();
10068 [[maybe_unused]] Type varType = mapInfoOp.getVarPtrType();
10069
10070 // Check #1: Check that the type of the private variable matches
10071 // the type of the variable being mapped.
10072 if (!isa<LLVM::LLVMPointerType>(privVar.getType()))
10073 assert(
10074 varType == privVar.getType() &&
10075 "Type of private var doesn't match the type of the mapped value");
10076
10077 // Ok, only 1 sanity check for now.
10078 // Record the block argument corresponding to this mapvar.
10079 mappedPrivateVars.insert(
10080 {privVar,
10081 targetRegion.getArgument(argIface.getMapBlockArgsStart() +
10082 (*privateMapIndices)[privVarIdx])});
10083 }
10084 }
10085
10086 using InsertPointTy = llvm::OpenMPIRBuilder::InsertPointTy;
10087 auto bodyCB = [&](InsertPointTy allocaIP, InsertPointTy codeGenIP,
10088 ArrayRef<llvm::BasicBlock *> deallocBlocks)
10089 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
10090 llvm::IRBuilderBase::InsertPointGuard guard(builder);
10091 builder.SetCurrentDebugLocation(llvm::DebugLoc());
10092 // Forward target-cpu and target-features function attributes from the
10093 // original function to the new outlined function.
10094 llvm::Function *llvmParentFn =
10095 moduleTranslation.lookupFunction(parentFn.getName());
10096 llvmOutlinedFn = codeGenIP.getNodeParent()->getParent();
10097 assert(llvmParentFn && llvmOutlinedFn &&
10098 "Both parent and outlined functions must exist at this point");
10099
10100 if (outlinedFnLoc && llvmParentFn->getSubprogram())
10101 llvmOutlinedFn->setSubprogram(outlinedFnLoc->getScope()->getSubprogram());
10102
10103 if (auto attr = llvmParentFn->getFnAttribute("target-cpu");
10104 attr.isStringAttribute())
10105 llvmOutlinedFn->addFnAttr(attr);
10106
10107 if (auto attr = llvmParentFn->getFnAttribute("target-features");
10108 attr.isStringAttribute())
10109 llvmOutlinedFn->addFnAttr(attr);
10110
10111 for (auto [idx, arg] : llvm::enumerate(mapBlockArgs)) {
10112 // in_reduction list items on omp.target are accessed through their
10113 // map_entries block argument, which is redirected below to the per-task
10114 // reduction-private storage returned by the runtime. Skip the default
10115 // host-value mapping for those block arguments so the write-once
10116 // mapValue mapping is free to be set to the private pointer.
10117 if (llvm::is_contained(inRedMapArgIdx, idx))
10118 continue;
10119 auto mapInfoOp = cast<omp::MapInfoOp>(mapVars[idx].getDefiningOp());
10120 llvm::Value *mapOpValue =
10121 moduleTranslation.lookupValue(mapInfoOp.getVarPtr());
10122 moduleTranslation.mapValue(arg, mapOpValue);
10123 }
10124 for (auto [arg, mapOp] : llvm::zip_equal(hdaBlockArgs, hdaVars)) {
10125 auto mapInfoOp = cast<omp::MapInfoOp>(mapOp.getDefiningOp());
10126 llvm::Value *mapOpValue =
10127 moduleTranslation.lookupValue(mapInfoOp.getVarPtr());
10128 moduleTranslation.mapValue(arg, mapOpValue);
10129 }
10130
10131 // Do privatization after moduleTranslation has already recorded
10132 // mapped values.
10133 PrivateVarsInfo privateVarsInfo(targetOp);
10134
10136 allocatePrivateVars(targetOp, builder, moduleTranslation,
10137 privateVarsInfo, allocaIP, &mappedPrivateVars);
10138
10139 if (failed(handleError(afterAllocas, *targetOp)))
10140 return llvm::make_error<PreviouslyReportedError>();
10141
10142 builder.restoreIP(codeGenIP);
10143 if (handleError(initPrivateVars(builder, moduleTranslation, privateVarsInfo,
10144 &mappedPrivateVars),
10145 *targetOp)
10146 .failed())
10147 return llvm::make_error<PreviouslyReportedError>();
10148
10149 if (failed(completePrivateVars(
10150 targetOp, builder, moduleTranslation, privateVarsInfo.mlirVars,
10151 privateVarsInfo.llvmVars, privateVarsInfo.privatizers,
10152 targetOp.getPrivateNeedsBarrier(), &mappedPrivateVars)))
10153 return llvm::make_error<PreviouslyReportedError>();
10154
10155 // The target body accesses each in_reduction variable through its
10156 // map_entries block argument. Redirect that block argument to the per-task
10157 // private storage returned by __kmpc_task_reduction_get_th_data so the body
10158 // accumulates into the reduction-private copy rather than the mapped
10159 // original. The lookup must run inside the target task body so the gtid
10160 // corresponds to the executing thread. The descriptor argument is NULL: the
10161 // runtime walks enclosing taskgroups to locate the matching task_reduction
10162 // registration for `origPtr`. Mirrors the in_reduction handling on
10163 // omp.taskloop.context.
10164 if (!inRedOrigPtrs.empty()) {
10165 // Collect, per item, the type the private pointer must have (the map
10166 // block argument's type), and, through the callback, rebind the map block
10167 // argument that stands in for each in_reduction list item to the per-task
10168 // reduction-private storage the runtime returns.
10169 SmallVector<llvm::Type *> inRedResultPtrTys;
10170 inRedResultPtrTys.reserve(inRedMapArgIdx.size());
10171 for (unsigned mapArgIdx : inRedMapArgIdx)
10172 inRedResultPtrTys.push_back(
10173 moduleTranslation.convertType(mapBlockArgs[mapArgIdx].getType()));
10174
10175 llvm::OpenMPIRBuilder::LocationDescription bodyLoc(builder);
10176 llvm::OpenMPIRBuilder::InsertPointTy redIP =
10177 ompBuilder->createTargetInReduction(
10178 bodyLoc, inRedOrigPtrs, inRedResultPtrTys,
10179 [&](unsigned idx, llvm::Value *priv) {
10180 moduleTranslation.mapValue(mapBlockArgs[inRedMapArgIdx[idx]],
10181 priv);
10182 });
10183 builder.restoreIP(redIP);
10184 }
10185
10187 moduleTranslation, allocaIP.getNodeParent(), deallocBlocks);
10189 targetRegion, "omp.target", builder, moduleTranslation);
10190
10191 if (failed(handleError(exitBlock, *targetOp)))
10192 return llvm::make_error<PreviouslyReportedError>();
10193
10194 builder.SetInsertPoint(exitBlock.get()->getTerminator());
10195
10196 if (failed(cleanupPrivateVars(targetOp, builder, moduleTranslation,
10197 targetOp.getLoc(), privateVarsInfo)))
10198 return llvm::make_error<PreviouslyReportedError>();
10199
10200 return builder.saveIP();
10201 };
10202
10203 StringRef parentName = parentFn.getName();
10204
10205 llvm::TargetRegionEntryInfo entryInfo;
10206
10207 getTargetEntryUniqueInfo(entryInfo, targetOp,
10208 *getOpenMPBuilder(moduleTranslation),
10209 moduleTranslation.getFileSystem(), parentName);
10210
10211 MapInfoData mapData;
10212 collectMapDataFromMapOperands(mapData, mapVars, moduleTranslation, dl,
10213 builder, /*useDevPtrOperands=*/{},
10214 /*useDevAddrOperands=*/{}, hdaVars);
10215
10216 MapInfosTy combinedInfos;
10217 auto genMapInfoCB =
10218 [&](llvm::OpenMPIRBuilder::InsertPointTy codeGenIP) -> MapInfosTy & {
10219 builder.restoreIP(codeGenIP);
10220 genMapInfos(builder, moduleTranslation, dl, combinedInfos, mapData,
10221 targetDirective);
10222
10223 // Append a null entry for the implicit dyn_ptr argument so the argument
10224 // count sent to the runtime already includes it.
10225 auto *nullPtr = llvm::Constant::getNullValue(builder.getPtrTy());
10226 combinedInfos.BasePointers.push_back(nullPtr);
10227 combinedInfos.Pointers.push_back(nullPtr);
10228 combinedInfos.DevicePointers.push_back(
10229 llvm::OpenMPIRBuilder::DeviceInfoTy::None);
10230 combinedInfos.Sizes.push_back(builder.getInt64(0));
10231 combinedInfos.Types.push_back(
10232 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_TARGET_PARAM |
10233 llvm::omp::OpenMPOffloadMappingFlags::OMP_MAP_LITERAL);
10234 // TODO: set HasAttachPtr from Flang for pointee-storage entries.
10235 combinedInfos.HasAttachPtr.push_back(false);
10236 if (!combinedInfos.Names.empty())
10237 combinedInfos.Names.push_back(nullPtr);
10238 combinedInfos.Mappers.push_back(nullptr);
10239
10240 return combinedInfos;
10241 };
10242
10243 auto argAccessorCB = [&](llvm::Argument &arg, llvm::Value *input,
10244 llvm::Value *&retVal, InsertPointTy allocaIP,
10245 InsertPointTy codeGenIP,
10247 -> llvm::OpenMPIRBuilder::InsertPointOrErrorTy {
10248 llvm::IRBuilderBase::InsertPointGuard guard(builder);
10249 builder.SetCurrentDebugLocation(llvm::DebugLoc());
10250 // We just return the unaltered argument for the host function
10251 // for now, some alterations may be required in the future to
10252 // keep host fallback functions working identically to the device
10253 // version (e.g. pass ByCopy values should be treated as such on
10254 // host and device, currently not always the case)
10255 if (!isTargetDevice) {
10256 retVal = cast<llvm::Value>(&arg);
10257 return codeGenIP;
10258 }
10259
10260 return createDeviceArgumentAccessor(targetOp, mapData, arg, input, retVal,
10261 builder, *ompBuilder, moduleTranslation,
10262 allocaIP, codeGenIP, deallocIPs);
10263 };
10264
10265 llvm::OpenMPIRBuilder::TargetKernelRuntimeAttrs runtimeAttrs;
10266 llvm::OpenMPIRBuilder::TargetKernelDefaultAttrs defaultAttrs;
10267 Operation *targetCapturedOp =
10268 cast<omp::ComposableOpInterface>(*targetOp).findCapturedOp();
10269 initTargetDefaultAttrs(targetOp, targetCapturedOp, defaultAttrs,
10270 isTargetDevice, isGPU);
10271
10272 // Collect host-evaluated values needed to properly launch the kernel from the
10273 // host.
10274 if (!isTargetDevice)
10275 initTargetRuntimeAttrs(builder, moduleTranslation, targetOp,
10276 targetCapturedOp, runtimeAttrs);
10277
10278 // Pass host-evaluated values as parameters to the kernel / host fallback,
10279 // except if they are constants. In any case, map the MLIR block argument to
10280 // the corresponding LLVM values.
10282 SmallVector<Value> hostEvalVars = targetOp.getHostEvalVars();
10283 ArrayRef<BlockArgument> hostEvalBlockArgs = argIface.getHostEvalBlockArgs();
10284 for (auto [arg, var] : llvm::zip_equal(hostEvalBlockArgs, hostEvalVars)) {
10285 llvm::Value *value = moduleTranslation.lookupValue(var);
10286 moduleTranslation.mapValue(arg, value);
10287
10288 if (!llvm::isa<llvm::Constant>(value))
10289 kernelInput.push_back(value);
10290 }
10291
10292 for (size_t i = 0, e = mapData.OriginalValue.size(); i != e; ++i) {
10293 // 1) Declare target arguments are not passed to kernels as arguments.
10294 // 2) Attach maps are not passed in as arguments to kernels, except for
10295 // private attach maps used for corresponding-pointer initialization.
10296 // 3) Children of record objects are not passed in as arguments.
10297 // TODO: We currently do not handle cases where a member is explicitly
10298 // passed in as an argument, this will likley need to be handled in
10299 // the near future, rather than using IsAMember, it may be better to
10300 // test if the relevant BlockArg is used within the target region and
10301 // then use that as a basis for exclusion in the kernel inputs.
10302 using MapFlags = llvm::omp::OpenMPOffloadMappingFlags;
10303 bool isAttachMap = (mapData.Types[i] & MapFlags::OMP_MAP_ATTACH) ==
10304 MapFlags::OMP_MAP_ATTACH;
10305 bool isPrivateTargetParam =
10306 (mapData.Types[i] &
10307 (MapFlags::OMP_MAP_PRIVATE | MapFlags::OMP_MAP_TARGET_PARAM)) ==
10308 (MapFlags::OMP_MAP_PRIVATE | MapFlags::OMP_MAP_TARGET_PARAM);
10309
10310 if (!mapData.IsDeclareTarget[i] && !mapData.IsAMember[i] &&
10311 (!isAttachMap || (isAttachMap && isPrivateTargetParam)))
10312 kernelInput.push_back(mapData.OriginalValue[i]);
10313 }
10314
10316 llvm::OpenMPIRBuilder::InsertPointTy allocaIP =
10317 findAllocInsertPoints(builder, moduleTranslation, &deallocBlocks);
10318
10319 llvm::OpenMPIRBuilder::DependenciesInfo dds;
10320 if (failed(buildDependData(
10321 targetOp.getDependVars(), targetOp.getDependKinds(),
10322 targetOp.getDependIterated(), targetOp.getDependIteratedKinds(),
10323 builder, moduleTranslation, dds)))
10324 return failure();
10325
10326 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
10327
10328 llvm::OpenMPIRBuilder::TargetDataInfo info(
10329 /*RequiresDevicePointerInfo=*/false,
10330 /*SeparateBeginEndCalls=*/true);
10331
10332 auto customMapperCB =
10333 [&](unsigned int i) -> llvm::Expected<llvm::Function *> {
10334 if (!combinedInfos.Mappers[i])
10335 return nullptr;
10336 info.HasMapper = true;
10337 return getOrCreateUserDefinedMapperFunc(combinedInfos.Mappers[i], builder,
10338 moduleTranslation, targetDirective);
10339 };
10340
10341 llvm::Value *ifCond = nullptr;
10342 if (Value targetIfCond = targetOp.getIfExpr())
10343 ifCond = moduleTranslation.lookupValue(targetIfCond);
10344
10345 Value dynGroupPrivateSize = targetOp.getDynGroupprivateSize();
10346 llvm::Value *dynSizeVal = nullptr;
10347 if (dynGroupPrivateSize) {
10348 dynSizeVal = moduleTranslation.lookupValue(dynGroupPrivateSize);
10349 dynSizeVal = builder.CreateIntCast(dynSizeVal, builder.getInt32Ty(),
10350 /*isSigned=*/false);
10351 }
10352
10353 llvm::omp::OMPDynGroupprivateFallbackType fallbackType =
10354 getDynGroupprivateFallbackType(targetOp.getDynGroupprivateFallbackAttr());
10355
10356 // Pass the target region's source location to the runtime, taken from the
10357 // op's own location. Restricted to the host offload path that actually emits
10358 // the kernel launch, to avoid creating an unused identifier on the device.
10359 llvm::Value *rtLocOverride =
10360 (!isTargetDevice && isOffloadEntry)
10361 ? getSourceLocIdentFromOp(builder, *ompBuilder, targetOp)
10362 : nullptr;
10363
10364 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
10365 getOpenMPBuilder(moduleTranslation)
10366 ->createTarget(ompLoc, isOffloadEntry, allocaIP, builder.saveIP(),
10367 deallocBlocks, info, entryInfo, defaultAttrs,
10368 runtimeAttrs, ifCond, kernelInput, genMapInfoCB,
10369 bodyCB, argAccessorCB, customMapperCB, dds,
10370 targetOp.getNowait(), dynSizeVal, fallbackType,
10371 outlinedFnDbgLoc, rtLocOverride);
10372
10373 if (failed(handleError(afterIP, opInst)))
10374 return failure();
10375
10376 builder.restoreIP(*afterIP);
10377
10378 if (dds.DepArray)
10379 builder.CreateFree(dds.DepArray);
10380
10381 return success();
10382}
10383
10384static LogicalResult
10385convertDeclareTargetAttr(Operation *op, mlir::omp::DeclareTargetAttr attribute,
10386 llvm::OpenMPIRBuilder *ompBuilder,
10387 LLVM::ModuleTranslation &moduleTranslation) {
10388 // Amend omp.declare_target by deleting the IR of the outlined functions
10389 // created for target regions. They cannot be filtered out from MLIR earlier
10390 // because the omp.target operation inside must be translated to LLVM, but
10391 // the wrapper functions themselves must not remain at the end of the
10392 // process. We know that functions where omp.declare_target does not match
10393 // omp.is_target_device at this stage can only be wrapper functions because
10394 // those that aren't are removed earlier as an MLIR transformation pass.
10395 if (FunctionOpInterface funcOp = dyn_cast<FunctionOpInterface>(op)) {
10396 if (auto offloadMod = dyn_cast<omp::OffloadModuleInterface>(
10397 op->getParentOfType<ModuleOp>().getOperation())) {
10398 if (!offloadMod.getIsTargetDevice())
10399 return success();
10400
10401 omp::DeclareTargetDeviceType declareType = attribute.getDeviceType();
10402
10403 if (declareType == omp::DeclareTargetDeviceType::host) {
10404 llvm::Function *llvmFunc =
10405 moduleTranslation.lookupFunction(funcOp.getName());
10406 llvmFunc->dropAllReferences();
10407 llvmFunc->eraseFromParent();
10408
10409 // Invalidate the builder's current insertion point, as it now points to
10410 // a deleted block.
10411 ompBuilder->Builder.ClearInsertionPoint();
10412 ompBuilder->Builder.SetCurrentDebugLocation(llvm::DebugLoc());
10413 } else if (llvm::Function *llvmFunc =
10414 moduleTranslation.lookupFunction(funcOp.getName())) {
10415 // Device-side declare target functions are externally visible by
10416 // default so they can be referenced from other device translation
10417 // units. That also prevents the offload LTO from internalizing and
10418 // deleting them when they end up unused in the final device image.
10419 // Such dead functions can still reference internal LDS and trigger
10420 // spurious "local memory global used by non-kernel function" backend
10421 // warnings. Marking them hidden keeps the symbol usable within the
10422 // device image's linkage unit while letting LTO drop it when nothing
10423 // references it; symbols that must stay reachable (e.g. via an offload
10424 // entry that takes their address) are kept alive by that reference.
10425 if (!llvmFunc->isDeclaration() && llvmFunc->hasExternalLinkage() &&
10426 llvmFunc->getVisibility() == llvm::GlobalValue::DefaultVisibility)
10427 llvmFunc->setVisibility(llvm::GlobalValue::HiddenVisibility);
10428 }
10429 }
10430 return success();
10431 }
10432
10433 if (LLVM::GlobalOp gOp = dyn_cast<LLVM::GlobalOp>(op)) {
10434 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
10435 if (auto *gVal = llvmModule->getNamedValue(gOp.getSymName())) {
10436 auto *gVar = cast<llvm::GlobalVariable>(gVal);
10437 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
10438 bool isDeclaration = gOp.isDeclaration();
10439 bool isExternallyVisible =
10440 gOp.getVisibility() != mlir::SymbolTable::Visibility::Private;
10441 auto loc = op->getLoc()->findInstanceOf<FileLineColLoc>();
10442 llvm::StringRef mangledName = gOp.getSymName();
10443 mlir::omp::DeclareTargetCaptureClause captureClause =
10444 attribute.getCaptureClause();
10445 auto captureClauseKind = convertToCaptureClauseKind(captureClause);
10446 auto deviceClause = convertToDeviceClauseKind(attribute.getDeviceType());
10447 llvm::StringRef entryMangledName = mangledName;
10448 llvm::Constant *entryAddr = llvm::cast<llvm::Constant>(gVal);
10449 std::function<llvm::GlobalValue::LinkageTypes()> variableLinkage;
10450 llvm::SmallString<128> entryNameStorage;
10451 bool requiresUSM = ompBuilder->Config.hasRequiresUnifiedSharedMemory();
10452 bool isToOrEnter =
10453 captureClause == omp::DeclareTargetCaptureClause::to ||
10454 captureClause == omp::DeclareTargetCaptureClause::enter;
10455 bool isHostOnly =
10456 attribute.getDeviceType() == omp::DeclareTargetDeviceType::host;
10457
10458 // A to/enter declare-target variable needs a device-resident,
10459 // name-resolvable copy and a host offloading entry. A local-linkage
10460 // global provides neither, so we promote it to external.
10461 if (isToOrEnter && !isHostOnly && !requiresUSM &&
10462 gVar->hasLocalLinkage()) {
10463 gVar->setLinkage(llvm::GlobalValue::ExternalLinkage);
10464 isExternallyVisible = true;
10465
10466 // Clear the stale dso_local flag so it is referenced like a
10467 // module-scope declare target global.
10468 if (ompBuilder->Config.isTargetDevice())
10469 gVar->setDSOLocal(false);
10470 }
10471
10472 if (isToOrEnter &&
10473 deviceClause ==
10474 llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseAny &&
10475 !requiresUSM && !isDeclaration &&
10476 (gVal->hasLocalLinkage() || gVal->hasHiddenVisibility())) {
10477 // Keep the original symbol as-is for target code, but create a visible
10478 // alias for the offload entry so libomptarget can associate the host
10479 // global with the actual device global.
10480 entryNameStorage = (mangledName + llvm::Twine("_decl_tgt_entry")).str();
10481 entryMangledName = entryNameStorage;
10482 if (llvm::GlobalValue *existing =
10483 llvmModule->getNamedValue(entryMangledName)) {
10484 entryAddr = llvm::cast<llvm::Constant>(existing);
10485 } else {
10486 entryAddr = llvm::GlobalAlias::create(
10487 gVal->getValueType(), gVal->getAddressSpace(),
10488 llvm::GlobalValue::WeakAnyLinkage, entryMangledName, entryAddr,
10489 llvmModule);
10490 llvm::cast<llvm::GlobalAlias>(entryAddr)->setVisibility(
10491 llvm::GlobalValue::DefaultVisibility);
10492 }
10493 variableLinkage = [] { return llvm::GlobalValue::WeakAnyLinkage; };
10494 }
10495 // unused for MLIR at the moment, required in Clang for book
10496 // keeping
10497 std::vector<llvm::GlobalVariable *> generatedRefs;
10498
10499 std::vector<llvm::Triple> targetTriple;
10500 auto targetTripleAttr = dyn_cast_or_null<mlir::StringAttr>(
10501 op->getParentOfType<mlir::ModuleOp>()->getDiscardableAttr(
10502 LLVM::LLVMDialect::getTargetTripleAttrName()));
10503 if (targetTripleAttr)
10504 targetTriple.emplace_back(targetTripleAttr.data());
10505
10506 auto fileInfoCallBack = [&loc]() {
10507 std::string filename = "";
10508 std::uint64_t lineNo = 0;
10509
10510 if (loc) {
10511 filename = loc.getFilename().str();
10512 lineNo = loc.getLine();
10513 }
10514
10515 return std::pair<std::string, std::uint64_t>(llvm::StringRef(filename),
10516 lineNo);
10517 };
10518
10519 llvm::vfs::FileSystem &vfs = moduleTranslation.getFileSystem();
10520 ompBuilder->registerTargetGlobalVariable(
10521 captureClauseKind, deviceClause, isDeclaration, isExternallyVisible,
10522 ompBuilder->getTargetEntryUniqueInfo(fileInfoCallBack, vfs),
10523 entryMangledName, generatedRefs, /*OpenMPSimd*/ false, targetTriple,
10524 /*GlobalInitializer*/ nullptr, variableLinkage, gVal->getType(),
10525 entryAddr);
10526
10527 if (ompBuilder->Config.isTargetDevice() &&
10528 (captureClause == omp::DeclareTargetCaptureClause::link ||
10529 requiresUSM)) {
10530 // For USM and link we generate a global reference pointer in the
10531 // default address space (e.g address space 0), as opposed to the
10532 // globals original type and address space.
10533 llvm::Type *ptrTy = llvm::PointerType::get(llvmModule->getContext(), 0);
10534 llvm::Constant *refPtr = ompBuilder->getAddrOfDeclareTargetVar(
10535 captureClauseKind, deviceClause, isDeclaration, isExternallyVisible,
10536 ompBuilder->getTargetEntryUniqueInfo(fileInfoCallBack, vfs),
10537 mangledName, generatedRefs, /*OpenMPSimd*/ false, targetTriple,
10538 ptrTy, /*GlobalInitializer*/ nullptr,
10539 /*VariableLinkage*/ nullptr);
10540
10541 // For indirectly-accessed global pointers, we rely on "internal"
10542 // linkage to optimize out the unneeded full-variable storage later,
10543 // since we can't prevent the LLVM dialect from generating globals
10544 // without also breaking target lowering. However, We can only do
10545 // this for definiions, as global variable declarations must have
10546 // external or weak linkage.
10547 if (refPtr) {
10548 if (!gVar->isDeclaration())
10549 gVar->setLinkage(llvm::GlobalValue::InternalLinkage);
10550
10551 // Register the (original global, reference pointer) pair so that the
10552 // OpenMPIRBuilder can rewrite uses of the original global during
10553 // finalization.
10554 if (auto *newGV =
10555 dyn_cast<llvm::GlobalValue>(refPtr->stripPointerCasts()))
10556 ompBuilder->registerDeclareTargetGlobalReplacement(gVal, newGV);
10557 }
10558 }
10559
10560 // Mark 'device_type(host) enter(...)' variables as external in the device
10561 // since they're not supposed to have their own copy. This will cause
10562 // linker errors if accesses are attempted from the target device.
10563 if (ompBuilder->Config.isTargetDevice() && isHostOnly && isToOrEnter) {
10564 gVar->setLinkage(llvm::GlobalValue::ExternalLinkage);
10565 gVar->setInitializer(nullptr);
10566 }
10567 }
10568 }
10569
10570 return success();
10571}
10572
10573namespace {
10574
10575/// Implementation of the dialect interface that converts operations belonging
10576/// to the OpenMP dialect to LLVM IR.
10577class OpenMPDialectLLVMIRTranslationInterface
10578 : public LLVMTranslationDialectInterface {
10579public:
10580 using LLVMTranslationDialectInterface::LLVMTranslationDialectInterface;
10581
10582 /// Translates the given operation to LLVM IR using the provided IR builder
10583 /// and saving the state in `moduleTranslation`.
10584 LogicalResult
10585 convertOperation(Operation *op, llvm::IRBuilderBase &builder,
10586 LLVM::ModuleTranslation &moduleTranslation) const final;
10587
10588 /// Given an OpenMP MLIR attribute, create the corresponding LLVM-IR,
10589 /// runtime calls, or operation amendments
10590 LogicalResult
10591 amendOperation(Operation *op, ArrayRef<llvm::Instruction *> instructions,
10592 NamedAttribute attribute,
10593 LLVM::ModuleTranslation &moduleTranslation) const final;
10594
10595 /// Records the LLVM alloc pointer produced for an OMP ALLOCATE variable so
10596 /// that the paired omp.allocate_free op can generate the matching
10597 /// __kmpc_free call.
10598 void registerAllocatedPtr(Value var, llvm::Value *ptr) const {
10599 ompAllocatedPtrs[var] = ptr;
10600 }
10601
10602 /// Returns the LLVM alloc pointer previously registered for var, or
10603 /// nullptr if no allocation was recorded.
10604 llvm::Value *lookupAllocatedPtr(Value var) const {
10605 auto it = ompAllocatedPtrs.find(var);
10606 return it != ompAllocatedPtrs.end() ? it->second : nullptr;
10607 }
10608
10609private:
10610 /// Maps each MLIR variable value that appeared in an omp.allocate_dir op to
10611 /// the LLVM pointer returned by the corresponding __kmpc_alloc call. The
10612 /// paired omp.allocate_free op looks up these pointers to emit __kmpc_free.
10613 mutable DenseMap<Value, llvm::Value *> ompAllocatedPtrs;
10614};
10615
10616} // namespace
10617
10618LogicalResult OpenMPDialectLLVMIRTranslationInterface::amendOperation(
10619 Operation *op, ArrayRef<llvm::Instruction *> instructions,
10620 NamedAttribute attribute,
10621 LLVM::ModuleTranslation &moduleTranslation) const {
10622 return llvm::StringSwitch<llvm::function_ref<LogicalResult(Attribute)>>(
10623 attribute.getName())
10624 .Case("omp.is_target_device",
10625 [&](Attribute attr) {
10626 if (auto deviceAttr = dyn_cast<BoolAttr>(attr)) {
10627 llvm::OpenMPIRBuilderConfig &config =
10628 getOpenMPBuilder(moduleTranslation)->Config;
10629 config.setIsTargetDevice(deviceAttr.getValue());
10630 return success();
10631 }
10632 return failure();
10633 })
10634 .Case("omp.is_gpu",
10635 [&](Attribute attr) {
10636 if (auto gpuAttr = dyn_cast<BoolAttr>(attr)) {
10637 llvm::OpenMPIRBuilderConfig &config =
10638 getOpenMPBuilder(moduleTranslation)->Config;
10639 config.setIsGPU(gpuAttr.getValue());
10640 return success();
10641 }
10642 return failure();
10643 })
10644 .Case("omp.host_ir_filepath",
10645 [&](Attribute attr) {
10646 if (auto filepathAttr = dyn_cast<StringAttr>(attr)) {
10647 llvm::OpenMPIRBuilder *ompBuilder =
10648 getOpenMPBuilder(moduleTranslation);
10649 ompBuilder->loadOffloadInfoMetadata(
10650 moduleTranslation.getFileSystem(), filepathAttr.getValue());
10651 return success();
10652 }
10653 return failure();
10654 })
10655 .Case("omp.flags",
10656 [&](Attribute attr) {
10657 if (auto rtlAttr = dyn_cast<omp::FlagsAttr>(attr))
10658 return convertFlagsAttr(op, rtlAttr, moduleTranslation);
10659 return failure();
10660 })
10661 .Case("omp.version",
10662 [&](Attribute attr) {
10663 if (auto versionAttr = dyn_cast<omp::VersionAttr>(attr)) {
10664 llvm::OpenMPIRBuilder *ompBuilder =
10665 getOpenMPBuilder(moduleTranslation);
10666 ompBuilder->M.addModuleFlag(llvm::Module::Max, "openmp",
10667 versionAttr.getVersion());
10668 return success();
10669 }
10670 return failure();
10671 })
10672 .Case("omp.declare_target",
10673 [&](Attribute attr) {
10674 if (auto declareTargetAttr =
10675 dyn_cast<omp::DeclareTargetAttr>(attr)) {
10676 llvm::OpenMPIRBuilder *ompBuilder =
10677 getOpenMPBuilder(moduleTranslation);
10678 return convertDeclareTargetAttr(op, declareTargetAttr,
10679 ompBuilder, moduleTranslation);
10680 }
10681 return failure();
10682 })
10683 .Case("omp.requires",
10684 [&](Attribute attr) {
10685 if (auto requiresAttr = dyn_cast<omp::ClauseRequiresAttr>(attr)) {
10686 using Requires = omp::ClauseRequires;
10687 Requires flags = requiresAttr.getValue();
10688 llvm::OpenMPIRBuilderConfig &config =
10689 getOpenMPBuilder(moduleTranslation)->Config;
10690 config.setHasRequiresReverseOffload(
10691 bitEnumContainsAll(flags, Requires::reverse_offload));
10692 config.setHasRequiresUnifiedAddress(
10693 bitEnumContainsAll(flags, Requires::unified_address));
10694 config.setHasRequiresUnifiedSharedMemory(
10695 bitEnumContainsAll(flags, Requires::unified_shared_memory));
10696 config.setHasRequiresDynamicAllocators(
10697 bitEnumContainsAll(flags, Requires::dynamic_allocators));
10698 return success();
10699 }
10700 return failure();
10701 })
10702 .Case("omp.target_triples",
10703 [&](Attribute attr) {
10704 if (auto triplesAttr = dyn_cast<ArrayAttr>(attr)) {
10705 llvm::OpenMPIRBuilderConfig &config =
10706 getOpenMPBuilder(moduleTranslation)->Config;
10707 config.TargetTriples.clear();
10708 config.TargetTriples.reserve(triplesAttr.size());
10709 for (Attribute tripleAttr : triplesAttr) {
10710 if (auto tripleStrAttr = dyn_cast<StringAttr>(tripleAttr))
10711 config.TargetTriples.emplace_back(tripleStrAttr.getValue());
10712 else
10713 return failure();
10714 }
10715 return success();
10716 }
10717 return failure();
10718 })
10719 .Case("omp.integer_wrap_around",
10720 [&](Attribute attr) {
10721 if (auto wrapAttr = dyn_cast<omp::IntegerWrapAroundAttr>(attr)) {
10722 llvm::OpenMPIRBuilderConfig &config =
10723 getOpenMPBuilder(moduleTranslation)->Config;
10724 config.setNoSignedWrap(!wrapAttr.getIntegerWrapAround());
10725 return success();
10726 }
10727 return failure();
10728 })
10729 .Default([](Attribute) {
10730 // Fall through for omp attributes that do not require lowering.
10731 return success();
10732 })(attribute.getValue());
10733
10734 return failure();
10735}
10736
10737// Returns true if the operation is not inside a TargetOp, it is part of a
10738// function and that function is not declare target.
10739static bool isHostDeviceOp(Operation *op) {
10740 // Assumes no reverse offloading
10741 if (op->getParentOfType<omp::TargetOp>())
10742 return false;
10743
10744 if (auto parentFn = op->getParentOfType<LLVM::LLVMFuncOp>()) {
10745 if (auto declareTargetIface =
10746 llvm::dyn_cast<mlir::omp::DeclareTargetInterface>(
10747 parentFn.getOperation())) {
10748 omp::DeclareTargetAttr declareTargetAttr =
10749 declareTargetIface.getDeclareTarget();
10750 if (declareTargetAttr && declareTargetAttr.getDeviceType() !=
10751 mlir::omp::DeclareTargetDeviceType::host)
10752 return false;
10753 }
10754
10755 return true;
10756 }
10757
10758 return false;
10759}
10760
10761static llvm::Function *getOmpTargetAlloc(llvm::IRBuilderBase &builder,
10762 llvm::Module *llvmModule) {
10763 llvm::Type *i64Ty = builder.getInt64Ty();
10764 llvm::Type *i32Ty = builder.getInt32Ty();
10765 llvm::Type *returnType = builder.getPtrTy(0);
10766 llvm::FunctionType *fnType =
10767 llvm::FunctionType::get(returnType, {i64Ty, i32Ty}, false);
10768 llvm::Function *func = cast<llvm::Function>(
10769 llvmModule->getOrInsertFunction("omp_target_alloc", fnType).getCallee());
10770 return func;
10771}
10772
10773template <typename T>
10774static llvm::Value *
10775getAllocationSize(llvm::IRBuilderBase &builder,
10776 LLVM::ModuleTranslation &moduleTranslation, T op) {
10777 llvm::DataLayout dataLayout =
10778 moduleTranslation.getLLVMModule()->getDataLayout();
10779 llvm::Type *llvmHeapTy =
10780 moduleTranslation.convertType(op.getMemElemTypeAttr().getValue());
10781
10782 auto alignment = op.getMemAlignment();
10783 llvm::TypeSize typeSize = llvm::alignTo(
10784 dataLayout.getTypeStoreSize(llvmHeapTy),
10785 alignment ? *alignment : dataLayout.getABITypeAlign(llvmHeapTy).value());
10786
10787 llvm::Value *allocSize = builder.getInt64(typeSize.getFixedValue());
10788 return builder.CreateMul(
10789 allocSize,
10790 builder.CreateIntCast(moduleTranslation.lookupValue(op.getMemArraySize()),
10791 builder.getInt64Ty(),
10792 /*isSigned=*/false));
10793}
10794
10795template <>
10796llvm::Value *getAllocationSize(llvm::IRBuilderBase &builder,
10797 LLVM::ModuleTranslation &moduleTranslation,
10798 omp::TargetAllocMemOp op) {
10799 llvm::DataLayout dataLayout =
10800 moduleTranslation.getLLVMModule()->getDataLayout();
10801 llvm::Type *llvmHeapTy = moduleTranslation.convertType(op.getAllocatedType());
10802 llvm::TypeSize typeSize = dataLayout.getTypeAllocSize(llvmHeapTy);
10803 llvm::Value *allocSize = builder.getInt64(typeSize.getFixedValue());
10804 for (auto typeParam : op.getTypeparams()) {
10805 allocSize = builder.CreateMul(
10806 allocSize,
10807 builder.CreateIntCast(moduleTranslation.lookupValue(typeParam),
10808 builder.getInt64Ty(),
10809 /*isSigned=*/false));
10810 }
10811 return allocSize;
10812}
10813
10814static LogicalResult
10815convertTargetAllocMemOp(Operation &opInst, llvm::IRBuilderBase &builder,
10816 LLVM::ModuleTranslation &moduleTranslation) {
10817 auto allocMemOp = cast<omp::TargetAllocMemOp>(opInst);
10818 if (!allocMemOp)
10819 return failure();
10820
10821 // Get "omp_target_alloc" function
10822 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
10823 llvm::Function *ompTargetAllocFunc = getOmpTargetAlloc(builder, llvmModule);
10824 // Get the corresponding device value in llvm
10825 mlir::Value deviceNum = allocMemOp.getDevice();
10826 llvm::Value *llvmDeviceNum = moduleTranslation.lookupValue(deviceNum);
10827 // Get the allocation size.
10828 llvm::Value *allocSize =
10829 getAllocationSize(builder, moduleTranslation, allocMemOp);
10830 // Create call to "omp_target_alloc" with the args as translated llvm values.
10831 llvm::CallInst *call =
10832 builder.CreateCall(ompTargetAllocFunc, {allocSize, llvmDeviceNum});
10833 llvm::Value *resultI64 = builder.CreatePtrToInt(call, builder.getInt64Ty());
10834
10835 // Map the result
10836 moduleTranslation.mapValue(allocMemOp.getResult(), resultI64);
10837 return success();
10838}
10839
10840static LogicalResult
10841convertAllocSharedMemOp(omp::AllocSharedMemOp allocMemOp,
10842 llvm::IRBuilderBase &builder,
10843 LLVM::ModuleTranslation &moduleTranslation) {
10844 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
10845 llvm::Value *size = getAllocationSize(builder, moduleTranslation, allocMemOp);
10846 moduleTranslation.mapValue(allocMemOp.getResult(),
10847 ompBuilder->createOMPAllocShared(builder, size));
10848 return success();
10849}
10850
10851static LogicalResult
10852convertAllocateDirOp(Operation &opInst, llvm::IRBuilderBase &builder,
10853 LLVM::ModuleTranslation &moduleTranslation,
10854 const OpenMPDialectLLVMIRTranslationInterface &ompIface) {
10855 auto allocateDirOp = cast<omp::AllocateDirOp>(opInst);
10856 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
10857
10858 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
10859 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
10860 llvm::DataLayout dataLayout = llvmModule->getDataLayout();
10861 SmallVector<Value> vars = allocateDirOp.getVarList();
10862 std::optional<int64_t> alignAttr = allocateDirOp.getAlign();
10863
10864 llvm::Value *allocator;
10865 if (auto allocatorVar = allocateDirOp.getAllocator()) {
10866 allocator = moduleTranslation.lookupValue(allocatorVar);
10867 if (allocator->getType()->isIntegerTy())
10868 allocator = builder.CreateIntToPtr(allocator, builder.getPtrTy());
10869 else if (allocator->getType()->isPointerTy())
10870 allocator = builder.CreatePointerBitCastOrAddrSpaceCast(
10871 allocator, builder.getPtrTy());
10872 } else {
10873 allocator = llvm::ConstantPointerNull::get(builder.getPtrTy());
10874 }
10875
10876 for (Value var : vars) {
10877 Value baseVar = getBaseValueForTypeLookup(var);
10878 llvm::Type *typeToInspect =
10879 getAllocatedLlvmTypeForVariable(var, baseVar, moduleTranslation);
10880
10881 llvm::Value *size;
10882 if (std::optional<llvm::Value *> dynamicSize = getDynamicAllocatedSize(
10883 var, baseVar, moduleTranslation, builder, dataLayout)) {
10884 size = *dynamicSize;
10885 } else if (typeToInspect->isArrayTy()) {
10886 size = builder.getInt64(
10887 dataLayout.getTypeAllocSize(typeToInspect).getFixedValue());
10888 } else {
10889 size = builder.getInt64(
10890 dataLayout.getTypeAllocSize(typeToInspect).getFixedValue());
10891 }
10892
10893 uint64_t alignValue =
10894 alignAttr ? alignAttr.value()
10895 : dataLayout.getABITypeAlign(typeToInspect).value();
10896 llvm::Value *alignConst = builder.getInt64(alignValue);
10897 // Align the size: ((size + align - 1) / align) * align
10898 size = builder.CreateAdd(size, builder.getInt64(alignValue - 1), "", true);
10899 size = builder.CreateUDiv(size, alignConst);
10900 size = builder.CreateMul(size, alignConst, "", true);
10901
10902 std::string allocName =
10903 ompBuilder->createPlatformSpecificName({".void.addr"});
10904 llvm::CallInst *allocCall;
10905 if (alignAttr.has_value()) {
10906 allocCall = ompBuilder->createOMPAlignedAlloc(
10907 ompLoc, builder.getInt64(alignAttr.value()), size, allocator,
10908 allocName);
10909 } else {
10910 allocCall =
10911 ompBuilder->createOMPAlloc(ompLoc, size, allocator, allocName);
10912 }
10913 // Record the alloc pointer keyed by the MLIR variable value.
10914 ompIface.registerAllocatedPtr(var, allocCall);
10915
10916 if (llvm::Value *baseLlvm = moduleTranslation.lookupValue(baseVar)) {
10917 llvm::Value *boundPtr = builder.CreatePointerBitCastOrAddrSpaceCast(
10918 allocCall, baseLlvm->getType());
10919 moduleTranslation.remapAllValuesWith(baseLlvm, boundPtr);
10920 } else if (llvm::Value *varLlvm = moduleTranslation.lookupValue(var)) {
10921 llvm::Value *boundPtr = builder.CreatePointerBitCastOrAddrSpaceCast(
10922 allocCall, varLlvm->getType());
10923 moduleTranslation.remapAllValuesWith(varLlvm, boundPtr);
10924 }
10925 }
10926
10927 return success();
10928}
10929
10930static LogicalResult
10931convertAllocateFreeOp(Operation &opInst, llvm::IRBuilderBase &builder,
10932 LLVM::ModuleTranslation &moduleTranslation,
10933 const OpenMPDialectLLVMIRTranslationInterface &ompIface) {
10934 auto freeOp = cast<omp::AllocateFreeOp>(opInst);
10935 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
10936 llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
10937
10938 llvm::Value *allocator;
10939 if (auto allocatorVar = freeOp.getAllocator()) {
10940 allocator = moduleTranslation.lookupValue(allocatorVar);
10941 if (allocator->getType()->isIntegerTy())
10942 allocator = builder.CreateIntToPtr(allocator, builder.getPtrTy());
10943 else if (allocator->getType()->isPointerTy())
10944 allocator = builder.CreatePointerBitCastOrAddrSpaceCast(
10945 allocator, builder.getPtrTy());
10946 } else {
10947 allocator = llvm::ConstantPointerNull::get(builder.getPtrTy());
10948 }
10949
10950 // Emit __kmpc_free for each variable in reverse allocation order.
10951 SmallVector<Value> vars = freeOp.getVarList();
10952 for (Value var : llvm::reverse(vars)) {
10953 llvm::Value *allocPtr = ompIface.lookupAllocatedPtr(var);
10954 if (!allocPtr)
10955 return opInst.emitError("omp.allocate_free: no allocation recorded");
10956 ompBuilder->createOMPFree(ompLoc, allocPtr, allocator, "");
10957 }
10958
10959 return success();
10960}
10961
10962static llvm::Function *getOmpTargetFree(llvm::IRBuilderBase &builder,
10963 llvm::Module *llvmModule) {
10964 llvm::Type *ptrTy = builder.getPtrTy(0);
10965 llvm::Type *i32Ty = builder.getInt32Ty();
10966 llvm::Type *voidTy = builder.getVoidTy();
10967 llvm::FunctionType *fnType =
10968 llvm::FunctionType::get(voidTy, {ptrTy, i32Ty}, false);
10969 llvm::Function *func = dyn_cast<llvm::Function>(
10970 llvmModule->getOrInsertFunction("omp_target_free", fnType).getCallee());
10971 return func;
10972}
10973
10974static LogicalResult
10975convertTargetFreeMemOp(Operation &opInst, llvm::IRBuilderBase &builder,
10976 LLVM::ModuleTranslation &moduleTranslation) {
10977 auto freeMemOp = cast<omp::TargetFreeMemOp>(opInst);
10978 if (!freeMemOp)
10979 return failure();
10980
10981 // Get "omp_target_free" function
10982 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
10983 llvm::Function *ompTragetFreeFunc = getOmpTargetFree(builder, llvmModule);
10984 // Get the corresponding device value in llvm
10985 mlir::Value deviceNum = freeMemOp.getDevice();
10986 llvm::Value *llvmDeviceNum = moduleTranslation.lookupValue(deviceNum);
10987 // Get the corresponding heapref value in llvm
10988 mlir::Value heapref = freeMemOp.getHeapref();
10989 llvm::Value *llvmHeapref = moduleTranslation.lookupValue(heapref);
10990 // Convert heapref int to ptr and call "omp_target_free"
10991 llvm::Value *intToPtr =
10992 builder.CreateIntToPtr(llvmHeapref, builder.getPtrTy(0));
10993 builder.CreateCall(ompTragetFreeFunc, {intToPtr, llvmDeviceNum});
10994 return success();
10995}
10996
10997static LogicalResult
10998convertFreeSharedMemOp(omp::FreeSharedMemOp freeMemOp,
10999 llvm::IRBuilderBase &builder,
11000 LLVM::ModuleTranslation &moduleTranslation) {
11001 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
11002 llvm::Value *size = getAllocationSize(builder, moduleTranslation, freeMemOp);
11003 ompBuilder->createOMPFreeShared(
11004 builder, moduleTranslation.lookupValue(freeMemOp.getHeapref()), size);
11005 return success();
11006}
11007
11008/// Converts an OpenMP groupprivate operation into LLVM IR.
11009static LogicalResult
11010convertOmpGroupprivate(Operation &opInst, llvm::IRBuilderBase &builder,
11011 LLVM::ModuleTranslation &moduleTranslation) {
11012 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
11013 auto groupprivateOp = cast<omp::GroupprivateOp>(opInst);
11014
11015 if (failed(checkImplementationStatus(opInst)))
11016 return failure();
11017
11018 bool isTargetDevice = ompBuilder->Config.isTargetDevice();
11019
11020 // Determine whether group-private storage should be allocated based on
11021 // device_type. When not specified, default to 'any' (allocate on both).
11022 bool shouldAllocate = true;
11023 switch (groupprivateOp.getDeviceType().value_or(
11024 mlir::omp::DeclareTargetDeviceType::any)) {
11025 case mlir::omp::DeclareTargetDeviceType::host:
11026 shouldAllocate = !isTargetDevice;
11027 break;
11028 case mlir::omp::DeclareTargetDeviceType::nohost:
11029 shouldAllocate = isTargetDevice;
11030 break;
11031 case mlir::omp::DeclareTargetDeviceType::any:
11032 shouldAllocate = true;
11033 break;
11034 }
11035
11036 // Look up the global variable directly by symbol name.
11038 &opInst, groupprivateOp.getSymNameAttr());
11039 if (!global)
11040 return opInst.emitError()
11041 << "expected symbol '" << groupprivateOp.getSymName()
11042 << "' to reference an LLVM global variable";
11043
11044 llvm::GlobalValue *globalValue = moduleTranslation.lookupGlobal(global);
11045 llvm::Type *varType = moduleTranslation.convertType(global.getType());
11046 std::string varName = globalValue->getName().str();
11047
11048 llvm::Value *resultPtr;
11049 if (shouldAllocate && isTargetDevice) {
11050 llvm::Module *llvmModule = moduleTranslation.getLLVMModule();
11051 llvm::Triple targetTriple(llvmModule->getTargetTriple());
11052 unsigned sharedAddressSpace;
11053 if (targetTriple.isAMDGCN())
11054 sharedAddressSpace = llvm::AMDGPUAS::LOCAL_ADDRESS;
11055 else if (targetTriple.isNVPTX())
11056 sharedAddressSpace = llvm::NVPTXAS::ADDRESS_SPACE_SHARED;
11057 else
11058 return opInst.emitError() << "groupprivate is not supported for target: "
11059 << targetTriple.str();
11060 llvm::GlobalVariable *sharedVar = new llvm::GlobalVariable(
11061 *llvmModule, varType, /*isConstant=*/false,
11062 llvm::GlobalValue::InternalLinkage, llvm::PoisonValue::get(varType),
11063 varName, /*InsertBefore=*/nullptr, llvm::GlobalValue::NotThreadLocal,
11064 sharedAddressSpace,
11065 /*isExternallyInitialized=*/false);
11066 resultPtr = sharedVar;
11067 } else {
11068 if (shouldAllocate && !isTargetDevice)
11069 opInst.emitWarning("groupprivate directive is currently ignored on the "
11070 "host, using original global");
11071 resultPtr = globalValue;
11072 }
11073
11074 moduleTranslation.mapValue(opInst.getResult(0), resultPtr);
11075 return success();
11076}
11077
11078/// Given an OpenMP MLIR operation, create the corresponding LLVM IR (including
11079/// OpenMP runtime calls).
11080LogicalResult OpenMPDialectLLVMIRTranslationInterface::convertOperation(
11081 Operation *op, llvm::IRBuilderBase &builder,
11082 LLVM::ModuleTranslation &moduleTranslation) const {
11083 llvm::OpenMPIRBuilder *ompBuilder = getOpenMPBuilder(moduleTranslation);
11084
11085 if (ompBuilder->Config.isTargetDevice() &&
11086 !isa<omp::TargetOp, omp::MapInfoOp, omp::TerminatorOp, omp::YieldOp>(
11087 op) &&
11088 isHostDeviceOp(op))
11089 return op->emitOpError() << "unsupported host op found in device";
11090
11091 // For each loop, introduce one stack frame to hold loop information. Ensure
11092 // this is only done for the outermost loop wrapper to prevent introducing
11093 // multiple stack frames for a single loop. Initially set to null, the loop
11094 // information structure is initialized during translation of the nested
11095 // omp.loop_nest operation, making it available to translation of all loop
11096 // wrappers after their body has been successfully translated.
11097 bool isOutermostLoopWrapper =
11098 isa_and_present<omp::LoopWrapperInterface>(op) &&
11099 !dyn_cast_if_present<omp::LoopWrapperInterface>(op->getParentOp());
11100
11101 // The TASKLOOP construct is implemented with an outer taskloop.context
11102 // operation which is not a loop wrapper, containing an inner taskloop
11103 // operation which is a loop wrapper. The stack frame should be pushed when
11104 // translating the outer taskloop.context and popped when translating the
11105 // inner taskloop which is a loop wrapper. We need access to the loop
11106 // information in the outer taskloop context so we need to create it and pop
11107 // it around the taskloop context not the inner loop wrapper.
11108 if (isa<omp::TaskloopContextOp>(op))
11109 isOutermostLoopWrapper = true;
11110 else if (isa<omp::TaskloopWrapperOp>(op))
11111 isOutermostLoopWrapper = false;
11112
11113 if (isOutermostLoopWrapper)
11114 moduleTranslation.stackPush<OpenMPLoopInfoStackFrame>();
11115
11116 auto result =
11117 llvm::TypeSwitch<Operation *, LogicalResult>(op)
11118 .Case([&](omp::BarrierOp op) -> LogicalResult {
11120 return failure();
11121
11122 llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
11123 ompBuilder->createBarrier(builder, llvm::omp::OMPD_barrier);
11124 LogicalResult res = handleError(afterIP, *op);
11125 if (res.succeeded()) {
11126 // If the barrier generated a cancellation check, the insertion
11127 // point might now need to be changed to a new continuation block
11128 builder.restoreIP(*afterIP);
11129 }
11130 return res;
11131 })
11132 .Case([&](omp::TaskyieldOp op) {
11134 return failure();
11135
11136 ompBuilder->createTaskyield(builder);
11137 return success();
11138 })
11139 .Case([&](omp::FlushOp op) {
11141 return failure();
11142
11143 // No support in Openmp runtime function (__kmpc_flush) to accept
11144 // the argument list.
11145 // OpenMP standard states the following:
11146 // "An implementation may implement a flush with a list by ignoring
11147 // the list, and treating it the same as a flush without a list."
11148 //
11149 // The argument list is discarded so that, flush with a list is
11150 // treated same as a flush without a list.
11151 ompBuilder->createFlush(builder);
11152 return success();
11153 })
11154 .Case([&](omp::ErrorOp op) {
11156 return failure();
11157
11158 llvm::Value *message = nullptr;
11159 if (mlir::Value messageExpr = op.getMessageExpr())
11160 message = moduleTranslation.lookupValue(messageExpr);
11161 else if (std::optional<StringRef> msg = op.getMessage();
11162 msg && !msg->empty())
11163 message = builder.CreateGlobalString(*msg);
11164 ompBuilder->createError(
11165 llvm::OpenMPIRBuilder::LocationDescription(builder),
11166 op.getSeverity() == omp::ClauseSeverity::fatal, message);
11167 return success();
11168 })
11169 .Case([&](omp::ParallelOp op) {
11170 return convertOmpParallel(op, builder, moduleTranslation);
11171 })
11172 .Case([&](omp::DispatchOp) {
11173 return convertOmpDispatch(*op, builder, moduleTranslation);
11174 })
11175 .Case([&](omp::MaskedOp) {
11176 return convertOmpMasked(*op, builder, moduleTranslation);
11177 })
11178 .Case([&](omp::MasterOp) {
11179 return convertOmpMaster(*op, builder, moduleTranslation);
11180 })
11181 .Case([&](omp::CriticalOp) {
11182 return convertOmpCritical(*op, builder, moduleTranslation);
11183 })
11184 .Case([&](omp::OrderedRegionOp) {
11185 return convertOmpOrderedRegion(*op, builder, moduleTranslation);
11186 })
11187 .Case([&](omp::OrderedOp) {
11188 return convertOmpOrdered(*op, builder, moduleTranslation);
11189 })
11190 .Case([&](omp::WsloopOp) {
11191 return convertOmpWsloop(*op, builder, moduleTranslation);
11192 })
11193 .Case([&](omp::SimdOp) {
11194 return convertOmpSimd(*op, builder, moduleTranslation);
11195 })
11196 .Case([&](omp::AtomicReadOp) {
11197 return convertOmpAtomicRead(*op, builder, moduleTranslation);
11198 })
11199 .Case([&](omp::AtomicWriteOp) {
11200 return convertOmpAtomicWrite(*op, builder, moduleTranslation);
11201 })
11202 .Case([&](omp::AtomicUpdateOp op) {
11203 return convertOmpAtomicUpdate(op, builder, moduleTranslation);
11204 })
11205 .Case([&](omp::AtomicCaptureOp op) {
11206 return convertOmpAtomicCapture(op, builder, moduleTranslation);
11207 })
11208 .Case([&](omp::AtomicCompareOp op) {
11209 return convertOmpAtomicCompare(op, builder, moduleTranslation);
11210 })
11211 .Case([&](omp::CancelOp op) {
11212 return convertOmpCancel(op, builder, moduleTranslation);
11213 })
11214 .Case([&](omp::CancellationPointOp op) {
11215 return convertOmpCancellationPoint(op, builder, moduleTranslation);
11216 })
11217 .Case([&](omp::SectionsOp) {
11218 return convertOmpSections(*op, builder, moduleTranslation);
11219 })
11220 .Case([&](omp::ScopeOp op) {
11221 return convertOmpScope(op, builder, moduleTranslation);
11222 })
11223 .Case([&](omp::SingleOp op) {
11224 return convertOmpSingle(op, builder, moduleTranslation);
11225 })
11226 .Case([&](omp::TeamsOp op) {
11227 return convertOmpTeams(op, builder, moduleTranslation);
11228 })
11229 .Case([&](omp::TaskOp op) {
11230 return convertOmpTaskOp(op, builder, moduleTranslation);
11231 })
11232 .Case([&](omp::TaskloopWrapperOp op) {
11233 return convertOmpTaskloopWrapperOp(op, builder, moduleTranslation);
11234 })
11235 .Case([&](omp::TaskloopContextOp op) {
11236 return convertOmpTaskloopContextOp(op, builder, moduleTranslation);
11237 })
11238 .Case([&](omp::TaskgroupOp op) {
11239 return convertOmpTaskgroupOp(op, builder, moduleTranslation);
11240 })
11241 .Case([&](omp::TaskwaitOp op) {
11242 return convertOmpTaskwaitOp(op, builder, moduleTranslation);
11243 })
11244 .Case([&](omp::InteropInitOp op) {
11245 return convertOmpInteropInitOp(op, builder, moduleTranslation);
11246 })
11247 .Case([&](omp::InteropDestroyOp op) {
11248 return convertOmpInteropDestroyOp(op, builder, moduleTranslation);
11249 })
11250 .Case([&](omp::InteropUseOp op) {
11251 return convertOmpInteropUseOp(op, builder, moduleTranslation);
11252 })
11253 .Case<omp::YieldOp, omp::TerminatorOp, omp::DeclareMapperOp,
11254 omp::DeclareMapperInfoOp, omp::DeclareReductionOp,
11255 omp::CriticalDeclareOp>([](auto op) {
11256 // `yield` and `terminator` can be just omitted. The block structure
11257 // was created in the region that handles their parent operation.
11258 // `declare_reduction` will be used by reductions and is not
11259 // converted directly, skip it.
11260 // `declare_mapper` and `declare_mapper.info` are handled whenever
11261 // they are referred to through a `map` clause.
11262 // `critical.declare` is only used to declare names of critical
11263 // sections which will be used by `critical` ops and hence can be
11264 // ignored for lowering. The OpenMP IRBuilder will create unique
11265 // name for critical section names.
11266 return success();
11267 })
11268 .Case([&](omp::ThreadprivateOp) {
11269 return convertOmpThreadprivate(*op, builder, moduleTranslation);
11270 })
11271 .Case<omp::TargetDataOp, omp::TargetEnterDataOp,
11272 omp::TargetExitDataOp, omp::TargetUpdateOp>([&](auto op) {
11273 return convertOmpTargetData(op, builder, moduleTranslation);
11274 })
11275 .Case([&](omp::TargetOp) {
11276 return convertOmpTarget(*op, builder, moduleTranslation);
11277 })
11278 .Case([&](omp::DistributeOp) {
11279 return convertOmpDistribute(*op, builder, moduleTranslation);
11280 })
11281 .Case([&](omp::LoopNestOp) {
11282 return convertOmpLoopNest(*op, builder, moduleTranslation);
11283 })
11284 .Case<omp::MapInfoOp, omp::MapBoundsOp, omp::PrivateClauseOp,
11285 omp::AffinityEntryOp, omp::IteratorOp>([&](auto op) {
11286 // No-op, should be handled by relevant owning operations e.g.
11287 // TargetOp, TargetEnterDataOp, TargetExitDataOp, TargetDataOp
11288 // etc. and then discarded
11289 return success();
11290 })
11291 .Case([&](omp::NewCliOp op) {
11292 // Meta-operation: Doesn't do anything by itself, but used to
11293 // identify a loop.
11294 return success();
11295 })
11296 .Case([&](omp::CanonicalLoopOp op) {
11297 return convertOmpCanonicalLoopOp(op, builder, moduleTranslation);
11298 })
11299 .Case([&](omp::UnrollHeuristicOp op) {
11300 // FIXME: Handling omp.unroll_heuristic as an executable requires
11301 // that the generator (e.g. omp.canonical_loop) has been seen first.
11302 // For construct that require all codegen to occur inside a callback
11303 // (e.g. OpenMPIRBilder::createParallel), all codegen of that
11304 // contained region including their transformations must occur at
11305 // the omp.canonical_loop.
11306 return applyUnrollHeuristic(op, builder, moduleTranslation);
11307 })
11308 .Case([&](omp::UnrollFullOp op) {
11309 return applyUnrollFull(op, builder, moduleTranslation);
11310 })
11311 .Case([&](omp::UnrollPartialOp op) {
11312 return applyUnrollPartial(op, builder, moduleTranslation);
11313 })
11314 .Case([&](omp::TileOp op) {
11315 return applyTile(op, builder, moduleTranslation);
11316 })
11317 .Case([&](omp::FuseOp op) {
11318 return applyFuse(op, builder, moduleTranslation);
11319 })
11320 .Case([&](omp::TargetAllocMemOp) {
11321 return convertTargetAllocMemOp(*op, builder, moduleTranslation);
11322 })
11323 .Case([&](omp::TargetFreeMemOp) {
11324 return convertTargetFreeMemOp(*op, builder, moduleTranslation);
11325 })
11326 .Case([&](omp::AllocateDirOp) {
11327 return convertAllocateDirOp(*op, builder, moduleTranslation, *this);
11328 })
11329 .Case([&](omp::AllocateFreeOp) {
11330 return convertAllocateFreeOp(*op, builder, moduleTranslation,
11331 *this);
11332 })
11333 .Case([&](omp::AllocSharedMemOp op) {
11334 return convertAllocSharedMemOp(op, builder, moduleTranslation);
11335 })
11336 .Case([&](omp::FreeSharedMemOp op) {
11337 return convertFreeSharedMemOp(op, builder, moduleTranslation);
11338 })
11339 .Case([&](omp::GroupprivateOp) {
11340 return convertOmpGroupprivate(*op, builder, moduleTranslation);
11341 })
11342 .Case([&](omp::DeclareSimdOp) {
11343 // TODO: Implement.
11344 // DECLARE_SIMD can be ignored. There is warning emitted in
11345 // semantic checks about the current situation, but not having an
11346 // implementation should not prevent compilation.
11347 return success();
11348 })
11349 .Default([&](Operation *inst) {
11350 return inst->emitError()
11351 << "not yet implemented: " << inst->getName();
11352 });
11353
11354 if (isOutermostLoopWrapper)
11355 moduleTranslation.stackPop();
11356
11357 return result;
11358}
11359
11361 static llvm::cl::opt<bool, /*ExternalStorage=*/true> option(
11362 "openmp-disable-virtual-collapse", llvm::cl::Hidden,
11363 llvm::cl::desc("Disable virtual collapse of eligible host loops"),
11364 llvm::cl::location(disableVirtualCollapse));
11365}
11366
11368 registry.insert<omp::OpenMPDialect>();
11369 registry.addExtension(+[](MLIRContext *ctx, omp::OpenMPDialect *dialect) {
11370 dialect->addInterfaces<OpenMPDialectLLVMIRTranslationInterface>();
11371 });
11372}
11373
11375 DialectRegistry registry;
11377 context.appendDialectRegistry(registry);
11378}
for(Operation *op :ops)
return success()
if(failed(verifyVectorMemoryOp(getOperation(), memrefType, getVectorType()))) return failure()
static void copy(Location loc, Value dst, Value src, Value size, OpBuilder &builder)
Copies the given number of bytes from src to dst pointers.
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
ArrayAttr()
auto load
static mlir::LogicalResult buildDependData(OperandRange dependVars, std::optional< ArrayAttr > dependKinds, OperandRange dependIterated, std::optional< ArrayAttr > dependIteratedKinds, llvm::IRBuilderBase &builder, mlir::LLVM::ModuleTranslation &moduleTranslation, llvm::OpenMPIRBuilder::DependenciesInfo &taskDeps)
static LogicalResult convertOmpAtomicUpdate(omp::AtomicUpdateOp &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP atomic update operation using OpenMPIRBuilder.
static llvm::omp::OrderKind convertOrderKind(std::optional< omp::ClauseOrderKind > o)
Convert Order attribute to llvm::omp::OrderKind.
static void mapParentWithMembers(LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder &ompBuilder, DataLayout &dl, MapInfosTy &combinedInfo, MapInfoData &mapData, uint64_t mapDataIndex, llvm::omp::OpenMPOffloadMappingFlags memberOfFlag, TargetDirectiveEnumTy targetDirective)
static llvm::OpenMPIRBuilder::InsertPointTy findAllocInsertPoints(llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, llvm::SmallVectorImpl< llvm::BasicBlock * > *deallocBlocks=nullptr)
Find the insertion point for allocas given the current insertion point for normal operations in the b...
static void sortMapIndices(llvm::SmallVectorImpl< size_t > &indices, omp::MapInfoOp mapInfo, bool first=true)
static LogicalResult convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
owningDataPtrPtrReductionGens[i]
static LogicalResult convertOmpTaskloopContextOp(omp::TaskloopContextOp contextOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static Operation * getGlobalOpFromValue(Value value)
static llvm::OffloadEntriesInfoManager::OMPTargetGlobalVarEntryKind convertToCaptureClauseKind(mlir::omp::DeclareTargetCaptureClause captureClause)
static omp::MapInfoOp getFirstOrLastMappedMemberPtr(omp::MapInfoOp mapInfo, bool first)
static LogicalResult convertOmpDispatch(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Convert 'dispatch' operation into LLVM IR.
static OpTy castOrGetParentOfType(Operation *op, bool immediateParent=false)
If op is of the given type parameter, return it casted to that type. Otherwise, if its immediate pare...
static LogicalResult convertOmpOrderedRegion(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP 'ordered_region' operation into LLVM IR using OpenMPIRBuilder.
static LogicalResult convertFreeSharedMemOp(omp::FreeSharedMemOp freeMemOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static LogicalResult convertTargetFreeMemOp(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static LogicalResult convertOmpAtomicWrite(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an omp.atomic.write operation to LLVM IR.
static OwningAtomicReductionGen makeAtomicReductionGen(omp::DeclareReductionOp decl, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Create an OpenMPIRBuilder-compatible atomic reduction generator for the given reduction declaration.
static void invalidateOmpLoop(LLVM::ModuleTranslation &moduleTranslation, Value mlir)
Mark an OpenMP loop as having been consumed.
static OwningDataPtrPtrReductionGen makeRefDataPtrGen(omp::DeclareReductionOp decl, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, bool isByRef)
Create an OpenMPIRBuilder-compatible data_ptr_ptr reduction generator for the given reduction declara...
static llvm::Value * getRefPtrIfDeclareTarget(Value value, LLVM::ModuleTranslation &moduleTranslation)
static llvm::Function * emitTaskReductionCombFn(omp::DeclareReductionOp decl, StringRef baseName, LLVM::ModuleTranslation &moduleTranslation)
Build an outlined combiner helper for a task_reduction declare_reduction op. Signature: void(ptr lhs,...
static LogicalResult convertOmpWsloop(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP workshare loop into LLVM IR using OpenMPIRBuilder.
static LogicalResult applyUnrollHeuristic(omp::UnrollHeuristicOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Apply a #pragma omp unroll / "!$omp unroll" transformation using the OpenMPIRBuilder.
static LogicalResult convertOmpMaster(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP 'master' operation into LLVM IR using OpenMPIRBuilder.
static void getAsIntegers(ArrayAttr values, llvm::SmallVector< int64_t > &ints)
static void emitComplexAtomicCmpXchg(llvm::IRBuilderBase &builder, llvm::Value *llvmX, llvm::Type *complexTy, llvm::Value *eVal, llvm::Value *dVal, llvm::AtomicOrdering atomicOrdering, llvm::AtomicOrdering failOrdering, bool isWeak, llvm::Value *&oldComplex, llvm::Value *&cmpOk)
Emit an IEEE-754-correct cmpxchg for a complex (struct-typed) atomic compare with fcmp oeq....
static llvm::Value * findAssociatedValue(Value privateVar, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, llvm::DenseMap< Value, Value > *mappedPrivateVars=nullptr)
Return the llvm::Value * corresponding to the privateVar that is being privatized....
static ArrayRef< bool > getIsByRef(std::optional< ArrayRef< bool > > attr)
static llvm::Expected< llvm::Value * > lookupOrTranslatePureValue(Value value, LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder)
Look up the given value in the mapping, and if it's not there, translate its defining operation at th...
static LogicalResult allocReductionVars(T op, ArrayRef< BlockArgument > reductionArgs, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, const llvm::OpenMPIRBuilder::InsertPointTy &allocaIP, SmallVectorImpl< omp::DeclareReductionOp > &reductionDecls, SmallVectorImpl< llvm::Value * > &privateReductionVariables, DenseMap< Value, llvm::Value * > &reductionVariableMap, SmallVectorImpl< DeferredStore > &deferredStores, llvm::ArrayRef< bool > isByRefs)
Allocate space for privatized reduction variables.
static void emitTaskReductionModifierFini(bool isWorksharing, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Emits __kmpc_task_reduction_modifier_fini(loc, gtid, is_ws) at the current builder insertion point,...
static LogicalResult convertOmpInteropUseOp(omp::InteropUseOp useOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static LogicalResult convertOmpTaskwaitOp(omp::TaskwaitOp twOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static LogicalResult collectAndValidateTaskloopRedDecls(Operation *contextOp, std::optional< ArrayAttr > syms, StringRef opName, StringRef clauseName, SmallVectorImpl< omp::DeclareReductionOp > &out)
Look up and validate the declare_reduction ops referenced by a reduction-like clause on the omp....
static LogicalResult convertOmpLoopNest(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP loop nest into LLVM IR using OpenMPIRBuilder.
static mlir::LogicalResult fillIteratorLoop(mlir::omp::IteratorOp itersOp, llvm::IRBuilderBase &builder, mlir::LLVM::ModuleTranslation &moduleTranslation, IteratorInfo &iterInfo, llvm::StringRef loopName, IteratorStoreEntryTy genStoreEntry)
static void createAlteredByCaptureMap(MapInfoData &mapData, LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder)
static LogicalResult convertOmpTaskOp(omp::TaskOp taskOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP task construct into LLVM IR using OpenMPIRBuilder.
static void genMapInfos(llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, DataLayout &dl, MapInfosTy &combinedInfo, MapInfoData &mapData, TargetDirectiveEnumTy targetDirective)
static llvm::AtomicOrdering convertAtomicOrdering(std::optional< omp::ClauseMemoryOrderKind > ao)
Convert an Atomic Ordering attribute to llvm::AtomicOrdering.
static LogicalResult convertOmpInteropInitOp(omp::InteropInitOp initOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static void setInsertPointForPossiblyEmptyBlock(llvm::IRBuilderBase &builder, llvm::BasicBlock *block=nullptr)
static mlir::LogicalResult convertIteratorRegion(llvm::Value *linearIV, IteratorInfo &iterInfo, mlir::Region &iteratorRegion, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
llvm::function_ref< void(llvm::Value *linearIV, mlir::omp::YieldOp yield)> IteratorStoreEntryTy
static llvm::Function * emitTaskReductionInitFn(omp::DeclareReductionOp decl, StringRef baseName, LLVM::ModuleTranslation &moduleTranslation)
Build an outlined init helper for a task_reduction declare_reduction op. Signature: void(ptr priv,...
static LogicalResult convertOmpSections(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static llvm::Expected< llvm::BasicBlock * > allocatePrivateVars(T op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, PrivateVarsInfo &privateVarsInfo, llvm::OpenMPIRBuilder::InsertPointTy &allocaIP, llvm::DenseMap< Value, Value > *mappedPrivateVars=nullptr, std::optional< llvm::OpenMPIRBuilder::InsertPointTy > allocatorIP=std::nullopt)
Allocate and initialize delayed private variables. Returns the basic block which comes after all of t...
static LogicalResult applyUnrollPartial(omp::UnrollPartialOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Apply a #pragma omp unroll partial / !$omp unroll partial transformation using the OpenMPIRBuilder.
static void addNonContiguousUpdate(LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder, MapInfoData &mapData, size_t mapDataIdx, MapInfosTy &combinedInfo)
Describe a bounded update as a set of contiguous pieces. Bounds follow the dialect's normalized faste...
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 OpenMPTranslationState & getOpenMPState(LLVM::ModuleTranslation &moduleTranslation)
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 void processIndividualMap(LLVM::ModuleTranslation &moduleTranslation, 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 LogicalResult convertOmpInteropDestroyOp(omp::InteropDestroyOp destroyOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static void mapOmpLoop(LLVM::ModuleTranslation &moduleTranslation, Value mlir, llvm::CanonicalLoopInfo *llvm)
Map an MLIR OpenMP dialect CanonicalLoopInfo to its lowered LLVM-IR OpenMPIRBuilder CanonicalLoopInfo...
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::CanonicalLoopInfo * lookupOMPLoop(LLVM::ModuleTranslation &moduleTranslation, Value mlir)
Find the LLVM-IR loop that represents an MLIR loop.
static llvm::AtomicRMWInst::BinOp convertBinOpToAtomic(Operation &op)
Converts an LLVM dialect binary operation to the corresponding enum value for atomicrmw supported bin...
static LogicalResult convertOmpCancel(omp::CancelOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static int getMapDataMemberIdx(MapInfoData &mapData, omp::MapInfoOp memberOp)
allocatedType moduleTranslation static convertType(allocatedType) LogicalResult inlineOmpRegionCleanup(llvm::SmallVectorImpl< Region * > &cleanupRegions, llvm::ArrayRef< llvm::Value * > privateVariables, LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder, StringRef regionName, bool shouldLoadCleanupRegionArg=true)
handling of DeclareReductionOp's cleanup region
static LogicalResult applyFuse(omp::FuseOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Apply a #pragma omp fuse / !$omp fuse transformation using the OpenMPIRBuilder.
static llvm::Value * materializeRegionArgValue(llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, BlockArgument regionArg, llvm::Value *value)
static LogicalResult convertOmpScope(omp::ScopeOp &scopeOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP scope construct into LLVM IR.
static bool isPrivatizeableAttachMap(omp::ClauseMapFlags mapType)
static llvm::Value * getSizeInBytes(DataLayout &dl, const mlir::Type &type, Operation *clauseOp, llvm::Value *basePointer, llvm::Type *baseType, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static llvm::Error initPrivateVars(llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, PrivateVarsInfo &privateVarsInfo, llvm::DenseMap< Value, Value > *mappedPrivateVars=nullptr)
static LogicalResult convertAllocSharedMemOp(omp::AllocSharedMemOp allocMemOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static llvm::CanonicalLoopInfo * findCurrentLoopInfo(LLVM::ModuleTranslation &moduleTranslation)
Find the loop information structure for the loop nest being translated.
static OwningReductionGen makeReductionGen(omp::DeclareReductionOp decl, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Create an OpenMPIRBuilder-compatible reduction generator for the given reduction declaration.
static void storeAffinityEntry(llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder &ompBuilder, llvm::Value *affinityList, llvm::Value *index, llvm::Value *addr, llvm::Value *len)
static LogicalResult convertOmpParallel(omp::ParallelOp opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts the OpenMP parallel operation to LLVM IR.
static void pushCancelFinalizationCB(SmallVectorImpl< llvm::UncondBrInst * > &cancelTerminators, llvm::IRBuilderBase &llvmBuilder, llvm::OpenMPIRBuilder &ompBuilder, mlir::Operation *op, llvm::omp::Directive cancelDirective)
Shared implementation of a callback which adds a termiator for the new block created for the branch t...
static LogicalResult inlineConvertOmpRegions(Region &region, StringRef blockName, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, SmallVectorImpl< llvm::Value * > *continuationBlockArgs=nullptr)
Translates the blocks contained in the given region and appends them to at the current insertion poin...
static void getTargetEntryUniqueInfo(llvm::TargetRegionEntryInfo &targetInfo, omp::TargetOp targetOp, llvm::OpenMPIRBuilder &ompBuilder, llvm::vfs::FileSystem &vfs, llvm::StringRef parentName="")
static LogicalResult convertOmpThreadprivate(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP Threadprivate operation into LLVM IR using OpenMPIRBuilder.
static omp::PrivateClauseOp findPrivatizer(Operation *from, SymbolRefAttr symbolName)
Looks up from the operation from and returns the PrivateClauseOp with name symbolName.
static llvm::OpenMPIRBuilder * getOpenMPBuilder(LLVM::ModuleTranslation &moduleTranslation)
Returns the OpenMP IR builder associated with the LLVM IR module being constructed.
static LogicalResult applyUnrollFull(omp::UnrollFullOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Apply a #pragma omp unroll full / !$omp unroll full transformation using the OpenMPIRBuilder.
static LogicalResult convertOmpGroupprivate(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP groupprivate operation into LLVM IR.
static llvm::Expected< llvm::Function * > getOrCreateUserDefinedMapperFunc(Operation *op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, TargetDirectiveEnumTy targetDirective)
static llvm::Value * getSourceLocIdentFromOp(llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder &ompBuilder, Operation *op)
static uint64_t getTypeByteSize(mlir::Type type, const DataLayout &dl)
static llvm::SmallString< 64 > getDeclareTargetRefPtrSuffix(LLVM::GlobalOp globalOp, llvm::OpenMPIRBuilder &ompBuilder, llvm::vfs::FileSystem &vfs)
static llvm::OpenMPIRBuilder::CollapseIVMapping selectCollapseIVMapping(omp::LoopNestOp loopOp, bool isTargetDevice, ArrayRef< llvm::CanonicalLoopInfo * > loops)
Select Virtual only for two constant-count host worksharing loops.
static void extractHostEvalClauses(omp::TargetOp targetOp, Value &numThreads, Value &numTeamsLower, Value &numTeamsUpper, Value &threadLimit, llvm::SmallVectorImpl< Value > *lowerBounds=nullptr, llvm::SmallVectorImpl< Value > *upperBounds=nullptr, llvm::SmallVectorImpl< Value > *steps=nullptr)
Follow uses of host_eval-defined block arguments of the given omp.target operation and populate outpu...
static llvm::Expected< llvm::BasicBlock * > convertOmpOpRegions(Region &region, StringRef blockName, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, SmallVectorImpl< llvm::PHINode * > *continuationBlockPHIs=nullptr)
Converts the given region that appears within an OpenMP dialect operation to LLVM IR,...
static LogicalResult extractAtomicComparePattern(Block &block, llvm::function_ref< llvm::Value *(mlir::Value)> materializeValue, omp::AtomicCompareOp atomicCompareOp, AtomicComparePatternInfo &info)
Extract comparison predicate, expected value (e), desired value (d), and related flags from an atomic...
static LogicalResult convertOmpAtomicCompare(omp::AtomicCompareOp atomicCompareOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an omp.atomic.compare operation to LLVM IR.
static LogicalResult convertAllocateDirOp(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, const OpenMPDialectLLVMIRTranslationInterface &ompIface)
static bool constructIsCancellable(Operation *op)
Returns true if the construct contains omp.cancel or omp.cancellation_point.
static llvm::omp::OpenMPOffloadMappingFlags convertClauseMapFlags(omp::ClauseMapFlags mlirFlags)
static LogicalResult convertAllocatorVars(Operation &op, ValueRange allocatorVars, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, PrivateVarsInfo &privateVarsInfo)
static void buildDependDataLocator(std::optional< ArrayAttr > dependKinds, OperandRange dependVars, LLVM::ModuleTranslation &moduleTranslation, SmallVectorImpl< llvm::OpenMPIRBuilder::DependData > &dds)
static std::optional< llvm::omp::OMPAtomicCompareOp > convertFCmpPredicateToAtomicCompareOp(LLVM::FCmpPredicate predicate)
Helper to extract the OMPAtomicCompareOp from a floating-point comparison predicate....
static llvm::Value * emitTaskReductionInitCall(ArrayRef< omp::DeclareReductionOp > redDecls, ArrayRef< llvm::Value * > origPtrs, StringRef helperNamePrefix, llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder::InsertPointTy allocaIP, LLVM::ModuleTranslation &moduleTranslation, bool isModifier=false, bool isWorksharing=false)
Emit the per-taskgroup task_reduction descriptor array and the __kmpc_taskred_init runtime call....
static llvm::Expected< llvm::Value * > getPrivateDataAddress(llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, omp::PrivateClauseOp privatizer, llvm::Value *var)
Returns the address of the data in var, via the data_address region.
static void mapInitializationArgs(T loop, LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder, SmallVectorImpl< omp::DeclareReductionOp > &reductionDecls, DenseMap< Value, llvm::Value * > &reductionVariableMap, unsigned i)
Map input arguments to reduction initialization region.
static llvm::omp::ProcBindKind getProcBindKind(omp::ClauseProcBindKind kind)
Convert ProcBindKind from MLIR-generated enum to LLVM enum.
static void fillAffinityLocators(Operation::operand_range affinityVars, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, llvm::Value *affinityList)
static LogicalResult convertOmpTaskloopWrapperOp(omp::TaskloopWrapperOp loopWrapperOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
The correct entry point is convertOmpTaskloopContextOp. This gets called whilst lowering the body of ...
static void getOverlappedMembers(llvm::SmallVectorImpl< size_t > &overlapMapDataIdxs, omp::MapInfoOp parentOp)
static LogicalResult convertOmpSingle(omp::SingleOp &singleOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP single construct into LLVM IR using OpenMPIRBuilder.
static bool isDeclareTargetTo(Value value)
static uint64_t getArrayElementSizeInBits(LLVM::LLVMArrayType arrTy, DataLayout &dl)
static void popCancelFinalizationCB(const ArrayRef< llvm::UncondBrInst * > cancelTerminators, llvm::OpenMPIRBuilder &ompBuilder, llvm::BasicBlock *afterBB)
If we cancelled the construct, we should branch to the finalization block of that construct....
static void collectReductionDecls(T op, SmallVectorImpl< omp::DeclareReductionOp > &reductions)
Populates reductions with reduction declarations used in the given op.
static LogicalResult handleError(llvm::Error error, Operation &op)
static LogicalResult convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static llvm::OffloadEntriesInfoManager::OMPTargetDeviceClauseKind convertToDeviceClauseKind(mlir::omp::DeclareTargetDeviceType deviceClause)
static std::optional< llvm::omp::OMPAtomicCompareOp > convertICmpPredicateToAtomicCompareOp(LLVM::ICmpPredicate predicate)
Helper to extract the OMPAtomicCompareOp from an integer comparison predicate. Returns std::nullopt f...
static llvm::Error computeTaskloopBounds(omp::LoopNestOp loopOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, llvm::Value *&lbVal, llvm::Value *&ubVal, llvm::Value *&stepVal)
static LogicalResult checkImplementationStatus(Operation &op)
Check whether translation to LLVM IR for the given operation is currently supported.
static llvm::IRBuilderBase::InsertPoint createDeviceArgumentAccessor(omp::TargetOp targetOp, MapInfoData &mapData, llvm::Argument &arg, llvm::Value *input, llvm::Value *&retVal, llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder &ompBuilder, LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase::InsertPoint allocaIP, llvm::IRBuilderBase::InsertPoint codeGenIP, llvm::ArrayRef< llvm::IRBuilderBase::InsertPoint > deallocIPs)
static LogicalResult createReductionsAndCleanup(OP op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, llvm::OpenMPIRBuilder::InsertPointTy &allocaIP, SmallVectorImpl< omp::DeclareReductionOp > &reductionDecls, ArrayRef< llvm::Value * > privateReductionVariables, ArrayRef< bool > isByRef, bool isNowait=false, bool isTeamsReduction=false)
static LogicalResult convertOmpCancellationPoint(omp::CancellationPointOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static uint64_t getReductionDataSize(OpTy &op)
static bool opMightBeSafeForBarriers(mlir::Operation *op)
A compiler-generated barrier is unsafe when only part of the current team can reach it....
static LogicalResult convertOmpAtomicRead(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Convert omp.atomic.read operation to LLVM IR.
static llvm::omp::Directive convertCancellationConstructType(omp::ClauseCancellationConstructType directive)
static void initTargetDefaultAttrs(omp::TargetOp targetOp, Operation *capturedOp, llvm::OpenMPIRBuilder::TargetKernelDefaultAttrs &attrs, bool isTargetDevice, bool isGPU)
Populate default MinTeams, MaxTeams and MaxThreads to their default values as stated by the correspon...
static llvm::AtomicOrdering getAtomicCompareFailureOrdering(omp::AtomicCompareOp atomicCompareOp, llvm::AtomicOrdering atomicOrdering)
Compute the cmpxchg failure ordering for an atomic compare op: use the fail clause ordering when pres...
static llvm::Value * emitTaskReductionModifierInitCall(OpTy op, ArrayRef< omp::DeclareReductionOp > reductionDecls, ArrayRef< llvm::Value * > privateReductionVariables, llvm::IRBuilderBase &builder, llvm::OpenMPIRBuilder::InsertPointTy allocaIP, LLVM::ModuleTranslation &moduleTranslation, bool isWorksharing)
Register only the task-modified items. All items still participate in the enclosing construct's ordin...
static llvm::omp::RTLDependenceKindTy convertDependKind(mlir::omp::ClauseTaskDepend kind)
static void initTargetRuntimeAttrs(llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, omp::TargetOp targetOp, Operation *capturedOp, llvm::OpenMPIRBuilder::TargetKernelRuntimeAttrs &attrs)
Gather LLVM runtime values for all clauses evaluated in the host that are passed to the kernel invoca...
static LogicalResult emitPrivatizationBarrier(mlir::Operation *op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static ComplexComparePattern detectComplexCompareEq(Block &block)
Detect a decomposed complex equality comparison in an atomic compare region: re_x = llvm....
static LogicalResult convertOmpTeams(omp::TeamsOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
static Value getBaseValueForTypeLookup(Value value)
static bool isHostDeviceOp(Operation *op)
static LogicalResult convertDeclareTargetAttr(Operation *op, mlir::omp::DeclareTargetAttr attribute, llvm::OpenMPIRBuilder *ompBuilder, LLVM::ModuleTranslation &moduleTranslation)
static LogicalResult completePrivateVars(mlir::Operation *op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, SmallVectorImpl< llvm::Value * > &moldVars, ArrayRef< llvm::Value * > llvmPrivateVars, SmallVectorImpl< omp::PrivateClauseOp > &privateDecls, bool insertBarrier, llvm::DenseMap< Value, Value > *mappedPrivateVars=nullptr)
static bool isDeclareTargetLink(Value value)
static LogicalResult convertFlagsAttr(Operation *op, mlir::omp::FlagsAttr attribute, LLVM::ModuleTranslation &moduleTranslation)
Lowers the FlagsAttr which is applied to the module when offloading. This attribute contains OpenMP R...
static bool checkIfPointerMap(omp::MapInfoOp mapOp)
static llvm::Type * getAllocatedLlvmTypeForVariable(Value var, Value baseVar, LLVM::ModuleTranslation &moduleTranslation)
static LogicalResult applyTile(omp::TileOp op, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Apply a #pragma omp tile / !$omp tile transformation using the OpenMPIRBuilder.
static LogicalResult convertAllocateFreeOp(Operation &opInst, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation, const OpenMPDialectLLVMIRTranslationInterface &ompIface)
static StringRef getMapClauseName(omp::MapInfoOp mapOp)
static std::vector< llvm::Value * > calculateBoundsOffset(LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder, bool isArrayTy, OperandRange bounds, bool &isByteOffset)
This function calculates the array/pointer offset for map data provided with bounds operations,...
static llvm::Function * getOmpTargetFree(llvm::IRBuilderBase &builder, llvm::Module *llvmModule)
static LogicalResult convertOmpTaskgroupOp(omp::TaskgroupOp tgOp, llvm::IRBuilderBase &builder, LLVM::ModuleTranslation &moduleTranslation)
Converts an OpenMP taskgroup construct into LLVM IR using OpenMPIRBuilder.
static std::optional< llvm::Value * > getDynamicAllocatedSize(Value var, Value baseVar, LLVM::ModuleTranslation &moduleTranslation, llvm::IRBuilderBase &builder, const llvm::DataLayout &dataLayout)
static void collectMapDataFromMapOperands(MapInfoData &mapData, SmallVectorImpl< Value > &mapVars, LLVM::ModuleTranslation &moduleTranslation, DataLayout &dl, llvm::IRBuilderBase &builder, ArrayRef< Value > useDevPtrOperands={}, ArrayRef< Value > useDevAddrOperands={}, ArrayRef< Value > hasDevAddrOperands={})
static void extractAtomicControlFlags(omp::AtomicUpdateOp atomicUpdateOp, bool &isIgnoreDenormalMode, bool &isFineGrainedMemory, bool &isRemoteMemory)
static Operation * genLoop(CodegenEnv &env, OpBuilder &builder, LoopId curr, unsigned numCases, bool needsUniv, ArrayRef< TensorLevel > tidLvls)
Generates a for-loop or a while-loop, depending on whether it implements singleton iteration or co-it...
#define MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(CLASS_NAME)
Definition TypeID.h:331
Attributes are known-constant values of operations.
Definition Attributes.h:25
This class represents an argument of a Block.
Definition Value.h:308
Block represents an ordered list of Operations.
Definition Block.h:34
BlockArgument getArgument(unsigned i)
Definition Block.h:154
unsigned getNumArguments()
Definition Block.h:153
OpListType & getOperations()
Definition Block.h:162
Operation & front()
Definition Block.h:178
Operation & back()
Definition Block.h:177
Operation * getTerminator()
Get the terminator operation of this block.
Definition Block.cpp:249
iterator_range< iterator > without_terminator()
Return an iterator range over the operation within this block excluding the terminator operation at t...
Definition Block.h:222
iterator begin()
Definition Block.h:168
The main mechanism for performing data layout queries.
llvm::TypeSize getTypeSize(Type t) const
Returns the size of the given type in the current scope.
llvm::TypeSize getTypeSizeInBits(Type t) const
Returns the size in bits of the given type in the current scope.
The DialectRegistry maps a dialect namespace to a constructor for the matching dialect.
bool addExtension(TypeID extensionID, std::unique_ptr< DialectExtensionBase > extension)
Add the given extension to the registry.
An instance of this location represents a tuple of file, line number, and column number.
Definition Location.h:176
Base class for state that lives as long as the module translation.
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.
T & getOrCreateExtension(Args &&...args)
Returns the extension of type T, constructing it from args on first use.
llvm::Value * lookupValue(Value value) const
Finds an LLVM IR value corresponding to the given MLIR value.
SymbolTableCollection & symbolTable()
llvm::Type * convertType(Type type)
Converts the type from MLIR LLVM dialect to LLVM.
llvm::vfs::FileSystem & getFileSystem()
Returns the virtual filesystem to use for file operations.
llvm::GlobalValue * lookupGlobal(Operation *op)
Finds an LLVM IR global value that corresponds to the given MLIR operation defining a global value.
SaveStateStack< T, ModuleTranslation > SaveStack
RAII object calling stackPush/stackPop on construction/destruction.
void remapAllValuesWith(llvm::Value *oldValue, llvm::Value *newValue)
Remap old value with new value in the MLIR-to-LLVM value map so later translations use the replacemen...
LogicalResult convertOperation(Operation &op, llvm::IRBuilderBase &builder)
Converts the given MLIR operation into LLVM IR using this translator.
llvm::Function * lookupFunction(StringRef name) const
Finds an LLVM IR function by its name.
void mapBlock(Block *mlir, llvm::BasicBlock *llvm)
Stores the mapping between an MLIR block and LLVM IR basic block.
llvm::Module * getLLVMModule()
Returns the LLVM module in which the IR is being constructed.
void stackPop()
Pops the last element from the ModuleTranslation stack.
void forgetMapping(Region &region)
Removes the mapping for blocks contained in the region and values defined in these blocks.
void mapValue(Value mlir, llvm::Value *llvm)
Stores the mapping between an MLIR value and its LLVM IR counterpart.
llvm::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:71
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:181
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:740
Value getOperand(unsigned idx)
Definition Operation.h:377
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:434
unsigned getNumRegions()
Returns the number of regions held by this operation.
Definition Operation.h:728
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:373
OperandRange operand_range
Definition Operation.h:398
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:405
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:851
user_range getUsers()
Returns a range of all users.
Definition Operation.h:927
result_range getResults()
Definition Operation.h:442
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:431
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
Block & front()
Definition Region.h:65
BlockArgListType getArguments()
Definition Region.h:94
bool empty()
Definition Region.h:60
unsigned getNumArguments()
Definition Region.h:136
iterator begin()
Definition Region.h:55
Operation * getParentOp()
Return the parent operation this region is attached to.
Definition Region.h:198
BlockListType & getBlocks()
Definition Region.h:45
bool hasOneBlock()
Return true if this region has exactly one block.
Definition Region.h:68
Concrete CRTP base class for StateStack frames.
Definition StateStack.h:47
@ Private
The symbol is private and may only be referenced by SymbolRefAttrs local to the operations within the...
Definition SymbolTable.h:92
static Operation * lookupNearestSymbolFrom(Operation *from, StringAttr symbol)
Returns the operation registered with the given symbol name within the closest parent operation of,...
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:392
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
user_range getUsers() const
Definition Value.h:218
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
A utility result that is used to signal how to proceed with an ongoing walk:
Definition WalkResult.h:29
static WalkResult skip()
Definition WalkResult.h:48
static WalkResult advance()
Definition WalkResult.h:47
bool wasInterrupted() const
Returns true if the walk was interrupted.
Definition WalkResult.h:51
static WalkResult interrupt()
Definition WalkResult.h:46
The OpAsmOpInterface, see OpAsmInterface.td for more details.
Definition CallGraph.h:229
void connectPHINodes(Region &region, const ModuleTranslation &state)
For all blocks in the region that were converted to LLVM IR using the given ModuleTranslation,...
llvm::Constant * createMappingInformation(Location loc, llvm::OpenMPIRBuilder &builder, StringRef mapName={})
Create a constant string representing the mapping information extracted from the MLIR location inform...
llvm::Constant * createSourceLocStrFromLocation(Location loc, llvm::OpenMPIRBuilder &builder, StringRef name, uint32_t &strLen, bool ForOffloadMap=false)
Create a constant string location from the MLIR Location information.
int64_t getOpenMPVersionAttribute(ModuleOp module, int64_t fallback=-1)
Returns the value of the omp.version attribute, if present, or the fallback.
Definition Utils.cpp:56
bool opInSharedDeviceContext(Operation &op)
Check whether the given operation is located in a context where an allocation to be used by multiple ...
Definition Utils.cpp:113
bool allocaUsesRequireSharedMem(Value alloc)
Check whether the value representing an allocation, assumed to have been defined in a shared device c...
Definition Utils.cpp:98
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:853
auto getDims(VectorType vType)
Returns a range over the dims (size and scalability) of a VectorType.
Include the generated interface declarations.
detail::DenseArrayAttrImpl< int64_t > DenseI64ArrayAttr
SetVector< Block * > getBlocksSortedByDominance(Region &region)
Gets a list of blocks that is sorted according to dominance.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:311
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
bool isPure(Operation *op)
Returns true if the given operation is pure, i.e., is speculatable that does not touch memory.
void registerOpenMPDialectTranslation(DialectRegistry &registry)
Register the OpenMP dialect and the translation from it to the LLVM IR in the given registry;.
llvm::SetVector< T, Vector, Set, N > SetVector
Definition LLVM.h:128
SmallVector< Loops, 8 > tile(ArrayRef< scf::ForOp > forOps, ArrayRef< Value > sizes, ArrayRef< scf::ForOp > targets)
Performs tiling fo imperfectly nested loops (with interchange) by strip-mining the forOps by sizes an...
Definition Utils.cpp:1389
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:123
void registerOpenMPTranslationCLOptions()
Register OpenMP translation command-line options before parsing them.
Holds the extracted comparison pattern information from an atomic compare region.
llvm::omp::OMPAtomicCompareOp compareOp
Result of matching the decomposed complex equality pattern inside an atomic compare region.
Eliminates variable at the specified position using Fourier-Motzkin variable elimination.