124#include "llvm/ADT/ArrayRef.h"
125#include "llvm/ADT/DenseMap.h"
126#include "llvm/ADT/STLExtras.h"
127#include "llvm/ADT/StringExtras.h"
128#include "llvm/ADT/Twine.h"
129#include "llvm/Support/Debug.h"
136#define GEN_PASS_DEF_ACCCGTOGPU
137#include "mlir/Dialect/OpenACC/Transforms/Passes.h.inc"
141#define DEBUG_TYPE "acc-cg-to-gpu"
147enum class PrivateMemScope { Thread, Worker, Gang,
None };
150static std::string getDeviceRemarkQualifier(DeviceType deviceType) {
151 switch (deviceType) {
152 case DeviceType::None:
153 case DeviceType::Star:
154 case DeviceType::Default:
158 llvm::StringRef deviceName = stringifyDeviceType(deviceType);
159 name.reserve(deviceName.size());
160 for (
char c : deviceName)
161 name.push_back(llvm::toUpper(c));
162 return name +
" GPU";
169 FunctionOpInterface funcOp = op->
getParentOfType<FunctionOpInterface>();
174static GPUParallelDimAttr
175getAccRoutineParDim(RoutineOp routineOp,
MLIRContext *ctx,
177 if (routineOp.getGangDimValue() ||
178 routineOp.getGangDimValue(DeviceType::Nvidia)) {
179 int64_t gangDimValue = routineOp.getGangDimValue(DeviceType::Nvidia)
180 ? *routineOp.getGangDimValue(DeviceType::Nvidia)
181 : *routineOp.getGangDimValue();
183 return policy.
gangDim(ctx, gangLevel);
185 if (routineOp.hasGang() || routineOp.hasGang(DeviceType::Nvidia))
186 return policy.
gangDim(ctx, ParLevel::gang_dim1);
187 if (routineOp.hasWorker() || routineOp.hasWorker(DeviceType::Nvidia))
189 if (routineOp.hasVector() || routineOp.hasVector(DeviceType::Nvidia))
191 return policy.
seqDim(ctx);
195static RoutineOp getRoutineOpForAccRoutineFunction(FunctionOpInterface funcOp,
198 SpecializedRoutineAttr attr =
199 funcOp->getDiscardableAttrOfType<SpecializedRoutineAttr>(
201 return symTab.
lookup<RoutineOp>(attr.getRoutine().getLeafReference());
203 RoutineInfoAttr routineInfo =
204 funcOp->getDiscardableAttrOfType<RoutineInfoAttr>(
206 if (!routineInfo || routineInfo.getAccRoutines().empty())
208 return symTab.
lookup<RoutineOp>(
209 routineInfo.getAccRoutines().front().getLeafReference());
213static GPUParallelDimAttr
214getSpecializedRoutineDim(FunctionOpInterface funcOp,
216 SpecializedRoutineAttr specAttr =
217 funcOp->getDiscardableAttrOfType<SpecializedRoutineAttr>(
219 assert(specAttr &&
"expected specialized routine attribute");
220 return policy.
map(funcOp->getContext(), specAttr.getLevel().getValue());
224static GPUParallelDimAttr
225getAccRoutineCallParDim(CallOpInterface callOp,
227 std::optional<CallInterfaceCallable> callee = callOp.getCallableForCallee();
230 SymbolRefAttr calleeSymbolRef = dyn_cast<SymbolRefAttr>(*callee);
231 if (!calleeSymbolRef)
233 ModuleOp moduleOp = callOp->getParentOfType<ModuleOp>();
238 FunctionOpInterface funcOp =
239 symTab.
lookup<FunctionOpInterface>(calleeSymbolRef.getLeafReference());
244 return getSpecializedRoutineDim(funcOp, policy);
245 if (RoutineOp routineOp = getRoutineOpForAccRoutineFunction(funcOp, symTab))
246 return getAccRoutineParDim(routineOp, funcOp.getContext(), policy);
253 ComputeRegionOp computeRegion = op->
getParentOfType<ComputeRegionOp>();
254 assert(computeRegion &&
"missing enclosing acc.compute_region");
257 bool isInnermostParallelParent =
true;
259 bool hasNonSeqParDim =
false;
260 if (GPUParallelDimsAttr parDimsAttr =
getParDimsAttr(parentLoop)) {
261 for (GPUParallelDimAttr parDim : parDimsAttr.getArray()) {
264 hasNonSeqParDim =
true;
275 (hasNonSeqParDim || isInnermostParallelParent))
276 for (GPUParallelDimAttr parDim : computeRegion.getLaunchParDims())
278 isInnermostParallelParent =
false;
279 parentLoop = parentLoop->getParentOfType<scf::ParallelOp>();
282 if (GPUParallelDimsAttr parDimsAttr =
getParDimsAttr(computeRegion))
283 for (GPUParallelDimAttr parDim : parDimsAttr.getArray())
289static Value stripIndexCastsFromValue(
Value x) {
293 while (arith::IndexCastOp castOp = dyn_cast<arith::IndexCastOp>(op)) {
302static FailureOr<int64_t> extractIntConst(
Value x,
303 bool stripIndexCasts =
false) {
305 x = stripIndexCastsFromValue(x);
309 assert(constOp.getType().getIntOrFloatBitWidth() <= 64);
310 return constOp.value();
313 return constOp.value();
320 x = stripIndexCastsFromValue(x);
321 FailureOr<int64_t> conX = extractIntConst(x);
328static bool getPassThroughResults(
Operation *userOp,
Value trackedOperand,
330 if (ViewLikeOpInterface viewLikeOp = dyn_cast<ViewLikeOpInterface>(userOp)) {
331 if (viewLikeOp.getViewSource() == trackedOperand) {
332 passThroughResults.push_back(viewLikeOp.getViewDest());
341 if (acc::PartialEntityAccessOpInterface partialAccess =
342 dyn_cast<acc::PartialEntityAccessOpInterface>(userOp)) {
343 if (partialAccess.getBaseEntity() == trackedOperand) {
355 if (ViewLikeOpInterface viewLike = dyn_cast<ViewLikeOpInterface>(op)) {
356 if (isa<MemRefType>(viewLike.getViewSource().getType()) ||
357 isa<MemRefType>(viewLike.getViewDest().getType())) {
358 v = viewLike.getViewSource();
369static acc::PrivateLocalOp getPrivateLocalForMemref(
Value memref);
372static GPUParallelDimsAttr
373getPrivateParDims(acc::PrivateLocalOp privateLocal,
374 acc::ComputeRegionOp computeRegion);
378static bool storageHasThreadX(acc::PrivateLocalOp privateLocal,
379 acc::ComputeRegionOp computeRegion) {
380 GPUParallelDimsAttr dims = getPrivateParDims(privateLocal, computeRegion);
381 return !dims || llvm::any_of(dims.getArray(), [](GPUParallelDimAttr d) {
382 return d.isThreadX();
395 if (GPUParallelDimsAttr parDimsAttr = privatize.getParDimsAttr())
396 return llvm::any_of(parDimsAttr.getArray(),
397 [](GPUParallelDimAttr d) { return d.isThreadX(); });
403static bool isInsideConditional(
Block *block) {
407 parent && !isa<gpu::LaunchOp, FunctionOpInterface>(parent);
409 if (isa<RegionBranchOpInterface>(parent) &&
410 !isa<LoopLikeOpInterface, scf::ExecuteRegionOp>(parent))
417 gpu::BarrierOp::create(builder, loc);
422 gpu::BarrierOp::create(builder, loc,
ArrayAttr{},
424 gpu::BarrierScope::Subgroup);
432 assert(rowWidth > 0 && subgroupSize % rowWidth == 0 &&
433 "row must tile the subgroup");
435 int64_t rowsPerSubgroup = subgroupSize / rowWidth;
441 Value threadY32 = arith::IndexCastOp::create(builder, loc, i32Ty, threadY);
444 Value rowIndex = arith::AndIOp::create(builder, loc, threadY32, rowMask);
446 Value laneBase = arith::MulIOp::create(builder, loc, rowIndex, width);
450 Value mask = arith::ShLIOp::create(builder, loc, laneBits, laneBase);
451 NVVM::SyncWarpOp::create(builder, loc, mask);
457struct SharedStorage {
458 llvm::SmallPtrSet<Value, 8> roots;
459 bool unknown =
false;
461 bool empty()
const {
return roots.empty() && !unknown; }
462 void insert(
const SharedStorage &other) {
463 roots.insert(other.roots.begin(), other.roots.end());
464 unknown |= other.unknown;
469class ACCCGToGPULowering {
471 explicit ACCCGToGPULowering(acc::ComputeRegionOp computeRegion,
472 RewriterBase &rewriter,
473 acc::OpenACCSupport &accSupport,
474 const ACCCGToGPUOptions &options)
475 : rewriter(rewriter), computeRegion(computeRegion),
476 accSupport(accSupport), options(options),
478 options.maxWorkgroupSharedMemory,
484 gpu::LaunchOp getLaunch()
const {
return launch; }
486 bool hasFailed =
false;
487 bool insideAccumulateGridStride =
false;
488 Value reductionSharedBuf;
491 llvm::DenseMap<Value, Value> reductionAccumValue;
493 llvm::SmallVector<std::pair<Value, memref::LoadOp>> pendingCombineReloads;
497 void processParallelOp(scf::ParallelOp parallelOp);
499 template <
typename LoopOp>
500 void processSeqLoop(LoopOp loopOp);
502 void processPredicateRegion(acc::PredicateRegionOp interOp);
505 processPrivateLocal(acc::PrivateLocalOp privateLocal,
506 std::optional<int64_t> sharedMemCopies = std::nullopt);
508 Value processPrivatize(acc::PrivatizeOp privatize);
510 Value allocatePrivatizeHeapStorage(acc::PrivatizeOp privatize,
514 void processExecuteRegion(scf::ExecuteRegionOp op);
516 void processAccumulateOp(acc::ReductionAccumulateOp op);
518 void processAccumulateArrayOp(acc::ReductionAccumulateArrayOp op);
520 void processReductionOp(acc::ReductionInitOp op);
522 void processReductionCombineOp(acc::ReductionCombineOp op);
524 void processCombineRegionOp(acc::ReductionCombineRegionOp op);
526 void processGenericOp(Operation *op);
528 void processGenericOpWithRegions(Operation *op);
530 void processOp(Operation *op);
533 void constructAtomicAccumulation(Location loc, Value memref,
535 arith::AtomicRMWKind kind);
538 FailureOr<arith::AtomicRMWKind> getReductionKind(acc::ReductionOperator redOp,
539 Type type, Location loc);
543 std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
544 SmallVector<mlir::acc::GPUParallelDimAttr>>
545 computeActiveAndInactiveParDims(Operation *op,
Block *block);
549 emitPredicate(Location loc,
550 SmallVector<mlir::acc::GPUParallelDimAttr> &inactiveParDims);
554 std::optional<int64_t>
555 isEligibleForSharedMemory(acc::PrivateLocalOp privateLocal,
559 bool tryAllocateSharedMemory(int64_t bytes);
565 void recordThreadPrivate(acc::PrivateLocalOp privateLocal);
568 int64_t getElementSizeInBytes(Location loc, Type elementType)
const;
571 bool canUseStackAlloca(MemRefType baseTy, Location loc,
572 int64_t maxThreadPrivateStack)
const;
575 void createBarrier(Location loc, mlir::acc::GPUParallelDimsAttr parDimsAttr);
581 void createPerRowBarrier(Location loc);
585 void createBarrierAfterSeqLoop(Operation *loopOp);
588 void flushDeferredBarriersBefore(Operation *beforeOp);
591 bool mayWriteSharedMemory(Operation *loopOp);
600 bool needsBarrierAfterThreadLoop(scf::ParallelOp loop);
611 bool mayAccessSharedMemory(Operation *root,
bool reads,
bool writes,
612 bool workerLevel, Operation *localScope,
613 Operation *ignore =
nullptr,
614 SharedStorage *collect =
nullptr,
615 const SharedStorage *filter =
nullptr);
618 PrivateMemScope getPrivateMemScope(acc::PrivatizeOp privatizeOp);
621 PrivateMemScope getPrivateScopeForMemref(Value memref);
624 acc::PrivatizeOp getPrivatizeForMemref(Value memref);
630 void collectSharedPrivateStores(
631 Operation *root, llvm::SmallPtrSetImpl<Operation *> &gangPrivatizes,
632 llvm::SmallPtrSetImpl<Operation *> &workerPrivatizes);
636 bool isMatchingPrivateMemref(
637 Value memref, PrivateMemScope storeScope,
638 const llvm::SmallPtrSetImpl<Operation *> &storePrivatizes);
642 bool hasParallelPrivateReuse(
643 LoopLikeOpInterface seqLoopOp, Operation *skipOp,
644 PrivateMemScope storeScope,
645 const llvm::SmallPtrSetImpl<Operation *> &storePrivatizes);
649 PrivateMemScope needsPreStoreReuseBarrier(acc::PredicateRegionOp interOp);
653 bool needsInLoopReuseBarrier(Operation *loopOp);
656 void createGPUAllReduceOp(Location loc, Value input, Value memref,
657 arith::AtomicRMWKind kind,
658 mlir::acc::GPUParallelDimsAttr parDimsAttr,
660 bool isPerThreadPrivateTarget =
false);
663 void postprocessAccumulateOp(acc::ReductionAccumulateOp op);
666 void postprocessLoopReduction(scf::ParallelOp parLoop);
672 llvm::DenseMap<gpu::Processor, Value> &ids,
673 llvm::DenseMap<gpu::Processor, Value> &dims) {
674 ids[gpu::Processor::BlockX] = gpu::BlockIdOp::create(
675 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::x);
676 ids[gpu::Processor::BlockY] = gpu::BlockIdOp::create(
677 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::y);
678 ids[gpu::Processor::BlockZ] = gpu::BlockIdOp::create(
679 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::z);
680 ids[gpu::Processor::ThreadX] = gpu::ThreadIdOp::create(
681 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::x);
682 ids[gpu::Processor::ThreadY] = gpu::ThreadIdOp::create(
683 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::y);
684 ids[gpu::Processor::ThreadZ] = gpu::ThreadIdOp::create(
685 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::z);
686 dims[gpu::Processor::BlockX] = gpu::GridDimOp::create(
687 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::x);
688 dims[gpu::Processor::BlockY] = gpu::GridDimOp::create(
689 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::y);
690 dims[gpu::Processor::BlockZ] = gpu::GridDimOp::create(
691 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::z);
692 dims[gpu::Processor::ThreadX] = gpu::BlockDimOp::create(
693 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::x);
694 dims[gpu::Processor::ThreadY] = gpu::BlockDimOp::create(
695 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::y);
696 dims[gpu::Processor::ThreadZ] = gpu::BlockDimOp::create(
697 rewriter, loc, rewriter.getIndexType(), gpu::Dimension::z);
702 BlockArgument getOrAppendInsBlockArg(Value outside) {
703 if (std::optional<BlockArgument> blockArg =
704 computeRegion.getBlockArg(outside)) {
707 return computeRegion.appendInputArg(outside);
711 void preparePrivatizeExtentInsOperands() {
712 computeRegion.walk([&](acc::PrivateLocalOp privateLocal) {
713 acc::PrivatizeOp privatizeOp =
715 if (privatizeOp->getParentOfType<acc::ComputeRegionOp>() == computeRegion)
717 for (Value extent : privatizeOp.getDynamicSizes())
718 getOrAppendInsBlockArg(extent);
724 resolvePrivateLocalDynamicExtents(acc::PrivateLocalOp privateLocal) {
725 acc::PrivatizeOp privatizeOp =
getPrivatizeOp(privateLocal, computeRegion);
726 SmallVector<Value> extents;
727 for (Value extent : privatizeOp.getDynamicSizes()) {
728 if (std::optional<BlockArgument> blockArg =
729 computeRegion.getBlockArg(extent)) {
730 extents.push_back(mapping.lookupOrDefault(*blockArg));
733 extents.push_back(mapping.lookupOrDefault(extent));
738 RewriterBase &rewriter;
739 acc::ComputeRegionOp computeRegion;
741 acc::OpenACCSupport &accSupport;
742 const ACCCGToGPUOptions &options;
743 gpu::LaunchOp launch;
745 llvm::SmallVector<scf::ParallelOp> loopReductions;
746 llvm::DenseMap<gpu::Processor, Value> threadIdMap;
747 llvm::DenseMap<gpu::Processor, Value> dimensionMap;
749 bool hasThreadYReduction =
false;
751 bool hasThreadXReduction =
false;
753 bool hasThreadLevelArrayReduction =
false;
755 bool hasThreadLevelRoutineCall =
false;
757 bool hasThreadYBarrier =
false;
760 llvm::DenseMap<Type, Value> privatizeBroadcastCache;
762 int64_t staticBlockDimX = 1024;
763 int64_t staticBlockDimY = 1024;
764 int64_t staticBlockDimZ = 1024;
769 bool isSingleThreadWorkerLaunch()
const {
770 return staticBlockDimX == 1 && staticBlockDimZ == 1 &&
771 staticBlockDimY > 1 && staticBlockDimY <= options.subgroupSize;
778 bool isWorkerOnlyShuffleLaunch()
const {
779 return hasThreadYReduction && !hasThreadXReduction &&
780 !hasThreadLevelArrayReduction && staticBlockDimZ == 1 &&
781 staticBlockDimY > 1 && staticBlockDimY <= options.subgroupSize &&
782 options.subgroupSize % staticBlockDimX == 0;
785 acc::DefaultACCToGPUMappingPolicy defaultPolicy;
786 SharedMemoryBudget sharedMemBudget;
787 SmallVector<std::string> sharedMemPrivateVarNames;
788 SmallVector<std::string> threadPrivateVarNames;
789 llvm::SmallVector<Operation *, 4> deferredBarrierSeqLoops;
791 Value getThreadId(Location loc, gpu::Dimension dim) {
792 return gpu::ThreadIdOp::create(rewriter, loc, rewriter.getIndexType(), dim);
795 Value getBlockDim(Location loc, gpu::Dimension dim) {
796 return gpu::BlockDimOp::create(rewriter, loc, rewriter.getIndexType(), dim);
800 Value getGPUThreadIdFor(gpu::Processor proc) {
805 Value getGPUSizeFor(gpu::Processor proc) {
806 return getGPUSize(proc, getLaunch(), dimensionMap);
811 Type elementType)
const {
812 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
813 if (std::optional<acc::TypeSizeAndAlignment> sizeAndAlignment =
815 return sizeAndAlignment->first.getFixedValue();
818 llvm::raw_string_ostream os(msg);
819 os <<
"element size computation for unsupported type: " << elementType;
820 (void)accSupport.
emitNYI(loc, os.str());
824bool ACCCGToGPULowering::canUseStackAlloca(
825 MemRefType baseTy, Location loc, int64_t maxThreadPrivateStack)
const {
826 for (int64_t dim : baseTy.getShape())
827 if (dim == ShapedType::kDynamic)
829 int64_t elementSize = getElementSizeInBytes(loc, baseTy.getElementType());
830 int64_t numElements = 1;
831 for (int64_t dim : baseTy.getShape()) {
832 if (numElements > maxThreadPrivateStack / std::max<int64_t>(dim, 1))
836 return elementSize * numElements < maxThreadPrivateStack;
844static bool reductionHasBlockContext(acc::ReductionAccumulateArrayOp accArr) {
845 auto hasBlock = [](mlir::acc::GPUParallelDimsAttr parDims) {
846 return parDims && llvm::any_of(parDims.getArray(),
847 [](
auto pd) { return pd.isAnyBlock(); });
849 if (hasBlock(accArr.getParDimsAttr()))
851 for (scf::ParallelOp loop = accArr->getParentOfType<scf::ParallelOp>(); loop;
852 loop = loop->getParentOfType<scf::ParallelOp>()) {
862static acc::ReductionAccumulateArrayOp perThreadArrayReductionAccum(Value v) {
863 SmallVector<Value> worklist{v};
865 while (!worklist.empty()) {
866 Value cur = worklist.pop_back_val();
867 if (!seen.insert(cur).second)
869 for (Operation *user : cur.
getUsers()) {
870 if (acc::ReductionAccumulateArrayOp accArr =
871 dyn_cast<acc::ReductionAccumulateArrayOp>(user)) {
872 bool hasThread =
false;
873 for (
auto pd : accArr.getParDims().getArray())
874 hasThread |= pd.isAnyThread();
875 if (hasThread && reductionHasBlockContext(accArr))
879 SmallVector<Value> through;
880 if (getPassThroughResults(user, cur, through))
881 worklist.append(through.begin(), through.end());
882 else if (isa<ViewLikeOpInterface>(user))
883 worklist.append(user->result_begin(), user->result_end());
892static void initPerThreadArrayAccum(OpBuilder &
b, Location loc, Value alloca,
894 arith::AtomicRMWKind kind) {
895 assert(baseTy.getRank() > 0 && baseTy.hasStaticShape() &&
896 "per-thread array reduction accumulator must be static ranked");
902 auto buildLoopNest = [&](
auto &&self,
unsigned dim) ->
void {
903 if (dim == baseTy.getRank()) {
904 memref::StoreOp::create(
b, loc, ident, alloca,
indices);
909 auto forOp = scf::ForOp::create(
b, loc, lb, ub, step);
910 OpBuilder::InsertionGuard g(
b);
911 b.setInsertionPoint(forOp.getBody()->getTerminator());
912 indices.push_back(forOp.getInductionVar());
919std::optional<int64_t>
920ACCCGToGPULowering::isEligibleForSharedMemory(acc::PrivateLocalOp privateLocal,
925 if (perThreadArrayReductionAccum(privateLocal.getResult()) &&
926 storageHasThreadX(privateLocal, computeRegion))
928 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
930 privateLocal, computeRegion, module, defaultPolicy, &accSupport);
931 if (
failed(isCandidate)) {
935 if (!isCandidate.value())
937 std::optional<int64_t> upperBound =
939 module, defaultPolicy);
940 assert(upperBound &&
"candidate private_local must have an upper bound");
941 int64_t elementSize =
942 getElementSizeInBytes(privateLocal.getLoc(), baseTy.getElementType());
943 int64_t numElements = 1;
944 for (int64_t dim : baseTy.getShape())
946 return *upperBound / (elementSize * numElements);
949bool ACCCGToGPULowering::tryAllocateSharedMemory(int64_t bytes) {
953void ACCCGToGPULowering::recordThreadPrivate(acc::PrivateLocalOp privateLocal) {
954 std::string varName = accSupport.
getVariableName(privateLocal.getResult());
959 if (!llvm::is_contained(threadPrivateVarNames, varName))
960 threadPrivateVarNames.push_back(varName);
963FailureOr<arith::AtomicRMWKind>
964ACCCGToGPULowering::getReductionKind(acc::ReductionOperator redOp, Type type,
966 if (std::optional<arith::AtomicRMWKind> kind =
971 llvm::raw_string_ostream os(msg);
972 os <<
"reduction operator (" << redOp <<
") for type " << type;
973 (void)accSupport.
emitNYI(loc, os.str());
977LogicalResult ACCCGToGPULowering::rewrite() {
983 computeRegion->walk([&](acc::ReductionAccumulateOp op) {
984 for (
auto parDim : op.getParDimsAttr().getArray()) {
985 hasThreadXReduction |= parDim.isThreadX();
986 hasThreadYReduction |= parDim.isThreadY();
989 computeRegion->walk([&](acc::ReductionAccumulateArrayOp op) {
990 for (
auto parDim : op.getParDimsAttr().getArray()) {
991 if (!parDim.isAnyBlock())
992 hasThreadLevelArrayReduction =
true;
1000 computeRegion->walk([&](CallOpInterface callOp) -> WalkResult {
1001 if (mlir::acc::GPUParallelDimAttr parDim =
1002 getAccRoutineCallParDim(callOp, defaultPolicy)) {
1003 if (parDim.isThreadX() || parDim.isThreadY()) {
1004 hasThreadLevelRoutineCall =
true;
1011 Location loc = computeRegion->getLoc();
1014 auto launchArgument = [&](gpu::Processor processor) -> Value {
1015 mlir::acc::GPUParallelDimAttr parDim = mlir::acc::GPUParallelDimAttr::get(
1016 computeRegion->getContext(), processor);
1017 std::optional<Value> maybeLaunchArg =
1018 computeRegion.getKnownLaunchArg(parDim);
1019 LLVM_DEBUG(llvm::dbgs() <<
"ACCCGToGPU: launch-arg: "
1020 <<
" parDim: " << parDim <<
" gpu: " << processor
1022 << maybeLaunchArg.value_or(constantOne) <<
"\n");
1026 maybeLaunchArg.value_or(constantOne));
1028 LLVM_DEBUG(llvm::dbgs() <<
"ACCCGToGPU: creating gpu launch op: \n");
1032 auto mapLaunchArguments = [&](gpu::Processor processor, Value launchArg) {
1033 mlir::acc::GPUParallelDimAttr parDim = mlir::acc::GPUParallelDimAttr::get(
1034 computeRegion->getContext(), processor);
1035 std::optional<Value> kernelArg = computeRegion.getLaunchArg(parDim);
1037 mapping.
map(computeRegion.gpuParWidth(processor), launchArg);
1040 llvm::StringRef blockDimXName =
"blockDim.x";
1041 llvm::StringRef blockDimYName =
"blockDim.y";
1042 std::string deviceLabel = getDeviceRemarkQualifier(
options.deviceType);
1044 if (!computeRegion->getParentOfType<gpu::GPUFuncOp>()) {
1045 Value blockDimX = launchArgument(gpu::Processor::ThreadX);
1048 staticBlockDimX = bdxVal.getSExtValue();
1049 Value blockDimY = launchArgument(gpu::Processor::ThreadY);
1052 staticBlockDimY = bdyVal.getSExtValue();
1053 Value blockDimZ = launchArgument(gpu::Processor::ThreadZ);
1056 staticBlockDimZ = bdzVal.getSExtValue();
1057 Value gridDimX = launchArgument(gpu::Processor::BlockX);
1058 Value gridDimY = launchArgument(gpu::Processor::BlockY);
1059 Value gridDimZ = launchArgument(gpu::Processor::BlockZ);
1065 auto getName = [&](Value val) -> std::string {
1068 width && *width < 0)
1071 return name.empty() ?
"(*)" : name;
1073 bool isEffectivelySerial =
1074 sameEffectiveValue(blockDimX, 1) &&
1075 sameEffectiveValue(blockDimY, 1) &&
1076 sameEffectiveValue(blockDimZ, 1) && sameEffectiveValue(gridDimX, 1) &&
1077 sameEffectiveValue(gridDimY, 1) && sameEffectiveValue(gridDimZ, 1);
1078 return (llvm::Twine(
"Generating ") +
1079 llvm::Twine(isEffectivelySerial ?
"serial " :
"") + deviceLabel +
1080 " code with gridDim=" + getName(gridDimX) +
"x" +
1081 getName(gridDimY) +
"x" + getName(gridDimZ) +
1082 " blockDim=" + getName(blockDimX) +
"x" + getName(blockDimY) +
1083 "x" + getName(blockDimZ))
1088 if (mlir::Value streamValue = computeRegion.getStream()) {
1089 LLVM_DEBUG(llvm::dbgs()
1090 <<
"\nDEBUG: Creating async gpu.launch with stream: "
1091 << streamValue <<
"\n");
1092 launch = gpu::LaunchOp::create(
1093 rewriter, loc, gridDimX, gridDimY, gridDimZ, blockDimX, blockDimY,
1099 launch.getAsyncDependenciesMutable().append(streamValue);
1101 LLVM_DEBUG(llvm::dbgs()
1102 <<
"\nDEBUG: No stream, creating sync gpu.launch\n");
1103 launch = gpu::LaunchOp::create(rewriter, loc, gridDimX, gridDimY,
1104 gridDimZ, blockDimX, blockDimY, blockDimZ);
1109 if (
auto kernelFuncName = computeRegion.getKernelFuncNameAttr())
1110 launch.setFunctionAttr(kernelFuncName);
1111 if (
auto kernelModuleName = computeRegion.getKernelModuleNameAttr())
1112 launch.setModuleAttr(kernelModuleName);
1115 gpu::TerminatorOp::create(rewriter, loc);
1117 mapLaunchArguments(gpu::Processor::BlockX,
1118 gpu::GridDimOp::create(rewriter, loc,
1120 gpu::Dimension::x));
1121 mapLaunchArguments(gpu::Processor::BlockY,
1122 gpu::GridDimOp::create(rewriter, loc,
1124 gpu::Dimension::y));
1125 mapLaunchArguments(gpu::Processor::BlockZ,
1126 gpu::GridDimOp::create(rewriter, loc,
1128 gpu::Dimension::z));
1129 mapLaunchArguments(gpu::Processor::ThreadX,
1130 gpu::BlockDimOp::create(rewriter, loc,
1132 gpu::Dimension::x));
1133 mapLaunchArguments(gpu::Processor::ThreadY,
1134 gpu::BlockDimOp::create(rewriter, loc,
1136 gpu::Dimension::y));
1137 mapLaunchArguments(gpu::Processor::ThreadZ,
1138 gpu::BlockDimOp::create(rewriter, loc,
1140 gpu::Dimension::z));
1145 OpBuilder::InsertionGuard guard(rewriter);
1148 mapLaunchArguments(gpu::Processor::BlockX,
1149 dimensionMap[gpu::Processor::BlockX]);
1150 mapLaunchArguments(gpu::Processor::BlockY,
1151 dimensionMap[gpu::Processor::BlockY]);
1152 mapLaunchArguments(gpu::Processor::BlockZ,
1153 dimensionMap[gpu::Processor::BlockZ]);
1154 mapLaunchArguments(gpu::Processor::ThreadX,
1155 dimensionMap[gpu::Processor::ThreadX]);
1156 mapLaunchArguments(gpu::Processor::ThreadY,
1157 dimensionMap[gpu::Processor::ThreadY]);
1158 mapLaunchArguments(gpu::Processor::ThreadZ,
1159 dimensionMap[gpu::Processor::ThreadZ]);
1164 preparePrivatizeExtentInsOperands();
1165 Block *body = computeRegion.getBody();
1166 unsigned numLaunchArgs = computeRegion.getLaunchArgs().size();
1167 ValueRange inputArgs = computeRegion.getInputArgs();
1171 assert(computeRegion.getRegion().hasOneBlock() &&
1172 "compute region only supports one block region for now");
1174 for (
auto &op : computeRegion.getRegion().getBlocks().front().getOperations())
1177 for (
auto &parLoop : loopReductions)
1178 postprocessLoopReduction(parLoop);
1182 if (!pendingCombineReloads.empty() && launch) {
1183 DominanceInfo domInfo(launch);
1184 for (
auto &[slot, loadOp] : pendingCombineReloads) {
1185 llvm::DenseMap<Value, Value>::iterator it =
1186 reductionAccumValue.find(slot);
1187 if (it == reductionAccumValue.end())
1189 if (!domInfo.dominates(it->second, loadOp.getOperation()))
1196 const int64_t subgroupSize =
options.subgroupSize;
1197 const int64_t subgroupAlignMask = subgroupSize - 1;
1205 bool needsThreadXAlign =
false;
1206 bool needsThreadYAlign =
false;
1207 bool alignThreadXReduction =
1213 auto classifyAllReduce = [&](gpu::AllReduceOp allReduce) {
1214 bool hasThreadX =
false;
1215 bool hasThreadY =
false;
1217 hasThreadX |= parDim.isThreadX();
1218 hasThreadY |= parDim.isThreadY();
1220 if (hasThreadX && alignThreadXReduction)
1221 needsThreadXAlign =
true;
1222 else if (hasThreadY)
1223 needsThreadYAlign =
true;
1226 launch.walk(classifyAllReduce);
1228 launch.walk([&](func::CallOp callOp) {
1229 if (gpu::GPUFuncOp callee =
1230 callOp->getParentOfType<ModuleOp>().lookupSymbol<gpu::GPUFuncOp>(
1231 callOp.getCallee()))
1232 callee.walk(classifyAllReduce);
1235 bool isShuffleEnabled = needsThreadXAlign || needsThreadYAlign;
1237 std::optional<int64_t> constBlockDimX =
1239 std::optional<int64_t> constBlockDimY =
1241 std::optional<int64_t> constBlockDimZ =
1249 bool skipAlign = constBlockDimX && constBlockDimY && constBlockDimZ &&
1250 *constBlockDimX > 1 && *constBlockDimX < subgroupSize &&
1251 *constBlockDimY == 1 && *constBlockDimZ == 1;
1256 if (isSingleThreadWorkerLaunch())
1258 if (needsThreadYAlign && !needsThreadXAlign && !hasThreadYBarrier &&
1259 isWorkerOnlyShuffleLaunch())
1262 if ((isShuffleEnabled || hasThreadYBarrier) && !skipAlign) {
1265 Value curBlockDimX = launch.getBlockSizeX();
1266 Value curBlockDimY = launch.getBlockSizeY();
1267 Value curBlockDimZ = launch.getBlockSizeZ();
1271 auto getName = [&](Value val) -> std::string {
1273 return name.empty() ?
"(*)" : name;
1275 std::string blockDimXValStr = getName(curBlockDimX);
1276 std::string blockDimYValStr = getName(curBlockDimY);
1277 llvm::StringRef kind =
1278 isShuffleEnabled ?
"Shuffle reduction" :
"ThreadY barrier";
1279 return (llvm::Twine(kind) +
1280 " is generated while adjusting the number of threads into "
1282 llvm::Twine(subgroupSize) +
".\n\t" + blockDimXName +
": `" +
1283 blockDimXValStr +
"` to `((" + blockDimXValStr +
" + " +
1284 llvm::Twine(subgroupAlignMask) +
") / " +
1285 llvm::Twine(subgroupSize) +
") * " + llvm::Twine(subgroupSize) +
1286 "`\n" +
"\t" + blockDimYName +
": `" + blockDimYValStr +
1287 "` to `max(1, (new-" + blockDimXName +
" * " + blockDimYValStr +
1288 ") / new-" + blockDimXName +
")`")
1297 Value newBlockDimX, newBlockDimY, newBlockDimZ;
1298 if (constBlockDimX && constBlockDimY && constBlockDimZ) {
1299 int64_t bdx = *constBlockDimX;
1300 int64_t bdy = *constBlockDimY;
1301 int64_t bdz = *constBlockDimZ;
1302 int64_t alignedBdx =
1303 ((bdx + subgroupAlignMask) / subgroupSize) * subgroupSize;
1304 int64_t numXYThreads = bdx * bdy;
1305 int64_t numThreads = numXYThreads * bdz;
1306 int64_t newBdy = std::max<int64_t>(1, numXYThreads / alignedBdx);
1308 std::max<int64_t>(1, numThreads / (alignedBdx * newBdy));
1315 Value numXYThreads =
1316 arith::MulIOp::create(rewriter, loc, curBlockDimX, curBlockDimY);
1318 arith::MulIOp::create(rewriter, loc, numXYThreads, curBlockDimZ);
1322 Value cstSubgroupSize =
1325 arith::AddIOp::create(rewriter, loc, curBlockDimX, cstMask);
1326 Value subgroupsRequired =
1327 arith::DivUIOp::create(rewriter, loc, padded, cstSubgroupSize);
1328 newBlockDimX = arith::MulIOp::create(rewriter, loc, subgroupsRequired,
1332 arith::DivUIOp::create(rewriter, loc, numXYThreads, newBlockDimX);
1334 newBlockDimY = arith::MaxUIOp::create(rewriter, loc, cst1, quotient);
1336 Value newNumXYThreads =
1337 arith::MulIOp::create(rewriter, loc, newBlockDimX, newBlockDimY);
1339 arith::DivUIOp::create(rewriter, loc, numThreads, newNumXYThreads);
1340 newBlockDimZ = arith::MaxUIOp::create(rewriter, loc, cst1, quotient);
1343 launch.getBlockSizeXMutable().assign(newBlockDimX);
1344 launch.getBlockSizeYMutable().assign(newBlockDimY);
1345 launch.getBlockSizeZMutable().assign(newBlockDimZ);
1352 if (!threadPrivateVarNames.empty()) {
1354 return (llvm::Twine(
"Local memory or registers used for ") +
1355 llvm::join(threadPrivateVarNames,
","))
1360 if (!sharedMemPrivateVarNames.empty()) {
1362 return (llvm::Twine(
"GPU shared memory used for ") +
1363 llvm::join(sharedMemPrivateVarNames,
","))
1368 rewriter.
eraseOp(computeRegion);
1384static bool isRedundantChainAccumulate(acc::ReductionAccumulateOp op) {
1385 Value memref = op.getMemref();
1386 memref::LoadOp loadOp = op.getValue().
getDefiningOp<memref::LoadOp>();
1387 if (!loadOp || loadOp.getMemRef() != memref)
1389 for (Operation *user : memref.
getUsers()) {
1390 acc::ReductionCombineOp combineOp = dyn_cast<acc::ReductionCombineOp>(user);
1391 if (!combineOp || combineOp.getDestMemref() != memref)
1393 SmallVector<mlir::acc::GPUParallelDimAttr> parDims =
1395 if (llvm::any_of(parDims, [](mlir::acc::GPUParallelDimAttr d) {
1396 return d.isAnyBlock();
1404static GPUParallelDimsAttr
1405getPrivateParDims(acc::PrivateLocalOp privateLocal,
1406 acc::ComputeRegionOp computeRegion) {
1409 if (acc::PrivatizeOp privatize =
getPrivatizeOp(privateLocal, computeRegion))
1410 return privatize.getParDimsAttr();
1415static bool isThreadYPrivate(acc::PrivateLocalOp privateLocal,
bool allowBlock,
1416 acc::ComputeRegionOp computeRegion) {
1419 GPUParallelDimsAttr parDims = getPrivateParDims(privateLocal, computeRegion);
1422 return llvm::any_of(parDims.getArray(),
1423 [](
auto dim) { return dim.isThreadY(); }) &&
1424 llvm::all_of(parDims.getArray(), [=](
auto dim) {
1425 return dim.isThreadY() || (allowBlock && dim.isAnyBlock());
1429struct ThreadYBroadeningInfo {
1430 bool hasActiveWorkerCombine =
false;
1431 bool hasExplicitInactiveCombine =
false;
1432 bool hasBroadeningConflict =
false;
1433 Operation *diagnosticOp =
nullptr;
1435 void merge(
const ThreadYBroadeningInfo &other) {
1436 hasActiveWorkerCombine |= other.hasActiveWorkerCombine;
1437 hasExplicitInactiveCombine |= other.hasExplicitInactiveCombine;
1438 hasBroadeningConflict |= other.hasBroadeningConflict;
1440 diagnosticOp = other.diagnosticOp;
1445static bool hasUnsafeEffectsWhenBroadening(Operation *op) {
1446 if (
auto effectOp = dyn_cast<MemoryEffectOpInterface>(op)) {
1447 SmallVector<MemoryEffects::EffectInstance> effects;
1448 effectOp.getEffects(effects);
1449 return llvm::any_of(effects, [](
const auto &effect) {
1450 return !isa<MemoryEffects::Read>(effect.getEffect());
1453 return !op->
hasTrait<OpTrait::HasRecursiveMemoryEffects>();
1458static bool isFedByInnerBlockCombine(acc::PrivateLocalOp accumulator,
1459 Operation *selfCombine) {
1462 for (Operation *user : accumulator.getResult().getUsers()) {
1463 if (user == selfCombine)
1465 auto combineOp = dyn_cast<acc::ReductionCombineOp>(user);
1467 unwrapMemRefConversion(combineOp.getDestMemref()).getDefiningOp() !=
1468 accumulator.getOperation())
1470 SmallVector<mlir::acc::GPUParallelDimAttr> parDims =
1472 if (llvm::any_of(parDims, [](mlir::acc::GPUParallelDimAttr d) {
1473 return d.isAnyBlock();
1481static void classifyThreadYCombine(ThreadYBroadeningInfo &info,
1482 Operation *combineOp, Value src, Value dest,
1483 ArrayRef<GPUParallelDimAttr> parDims,
1484 acc::ComputeRegionOp computeRegion) {
1485 bool hasThreadY = llvm::any_of(
1486 parDims, [](GPUParallelDimAttr parDim) {
return parDim.isThreadY(); });
1487 bool hasBlock = llvm::any_of(
1488 parDims, [](GPUParallelDimAttr parDim) {
return parDim.isAnyBlock(); });
1489 acc::PrivateLocalOp srcPrivate =
1490 unwrapMemRefConversion(src).getDefiningOp<acc::PrivateLocalOp>();
1491 acc::PrivateLocalOp destPrivate =
1492 unwrapMemRefConversion(dest).getDefiningOp<acc::PrivateLocalOp>();
1493 bool hasPrivateDest = isa<acc::ReductionCombineOp>(combineOp) && srcPrivate &&
1497 if (hasThreadY && hasBlock &&
1498 isThreadYPrivate(srcPrivate, hasPrivateDest, computeRegion)) {
1499 info.hasActiveWorkerCombine =
true;
1506 if (hasThreadY && hasBlock &&
1507 isThreadYPrivate(srcPrivate,
true, computeRegion) &&
1508 isFedByInnerBlockCombine(srcPrivate, combineOp)) {
1509 info.hasActiveWorkerCombine =
true;
1513 info.hasExplicitInactiveCombine =
true;
1514 if (!info.diagnosticOp)
1515 info.diagnosticOp = combineOp;
1521static ThreadYBroadeningInfo
1522analyzeThreadYBroadening(
Block &predicateBlock,
1523 acc::ComputeRegionOp computeRegion) {
1524 ThreadYBroadeningInfo info;
1525 for (Operation &nestedOp : predicateBlock) {
1526 if (acc::PredicateRegionOp nestedPredicate =
1527 dyn_cast<acc::PredicateRegionOp>(nestedOp)) {
1528 ThreadYBroadeningInfo nestedInfo = analyzeThreadYBroadening(
1529 nestedPredicate.getRegion().front(), computeRegion);
1530 info.hasActiveWorkerCombine |= nestedInfo.hasActiveWorkerCombine;
1533 if (acc::ReductionCombineOp combineOp =
1534 dyn_cast<acc::ReductionCombineOp>(nestedOp)) {
1535 classifyThreadYCombine(
1536 info, combineOp, combineOp.getSrcMemref(), combineOp.getDestMemref(),
1540 if (acc::ReductionCombineRegionOp combineRegionOp =
1541 dyn_cast<acc::ReductionCombineRegionOp>(nestedOp)) {
1542 classifyThreadYCombine(info, combineRegionOp, combineRegionOp.getSrcVar(),
1543 combineRegionOp.getDestVar(),
1548 if (nestedOp.getNumRegions() != 0) {
1549 if (hasUnsafeEffectsWhenBroadening(&nestedOp)) {
1550 info.hasBroadeningConflict =
true;
1551 if (!info.diagnosticOp)
1552 info.diagnosticOp = &nestedOp;
1554 for (Region ®ion : nestedOp.getRegions())
1555 for (
Block &nestedBlock : region)
1556 info.merge(analyzeThreadYBroadening(nestedBlock, computeRegion));
1559 if (hasUnsafeEffectsWhenBroadening(&nestedOp)) {
1560 info.hasBroadeningConflict =
true;
1561 if (!info.diagnosticOp)
1562 info.diagnosticOp = &nestedOp;
1568std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
1569 SmallVector<mlir::acc::GPUParallelDimAttr>>
1570ACCCGToGPULowering::computeActiveAndInactiveParDims(Operation *op,
1572 MLIRContext *ctx = computeRegion->getContext();
1574 mlir::acc::GPUParallelDimAttr routineParDim;
1576 FunctionOpInterface funcOp =
1577 computeRegion->getParentOfType<FunctionOpInterface>();
1578 routineParDim = getSpecializedRoutineDim(funcOp, defaultPolicy);
1585 if (isa<acc::PrivateLocalOp, acc::PrivatizeOp, acc::PredicateRegionOp>(op)) {
1586 if (mlir::acc::ActiveParDimsAttr precomputedActiveParDims =
1588 mlir::acc::GPUParallelDimAttr lowestParDim =
1589 mlir::acc::GPUParallelDimAttr::threadXDim(ctx);
1591 SmallVector<mlir::acc::GPUParallelDimAttr> launchParDims;
1592 if (routineParDim) {
1593 for (mlir::acc::GPUParallelDimAttr parDim = routineParDim;
1594 parDim.getOrder() >= lowestParDim.getOrder();
1595 parDim = parDim.getOneLower()) {
1599 launchParDims = computeRegion.getLaunchParDims();
1602 SmallVector<mlir::acc::GPUParallelDimAttr> activeParDims(
1603 precomputedActiveParDims.getArray());
1604 SmallVector<mlir::acc::GPUParallelDimAttr> inactiveParDims;
1605 for (mlir::acc::GPUParallelDimAttr launchParDim : launchParDims) {
1606 if (launchParDim.getOrder() < lowestParDim.getOrder())
1608 if (!llvm::is_contained(activeParDims, launchParDim))
1609 inactiveParDims.push_back(launchParDim);
1611 return std::pair{activeParDims, inactiveParDims};
1615 SmallVector<mlir::acc::GPUParallelDimAttr> ancestorParDims =
1616 getAncestorParDims(op);
1621 bool noStructuralAncestorParDims =
1622 llvm::none_of(ancestorParDims, [](
auto pd) {
return !pd.isSeq(); });
1624 if (routineParDim) {
1625 if (routineParDim.isThreadX()) {
1627 mlir::acc::GPUParallelDimAttr::threadYDim(ctx));
1630 mlir::acc::GPUParallelDimAttr::blockXDim(ctx));
1634 if (acc::PrivateLocalOp privateLocalOp = dyn_cast<acc::PrivateLocalOp>(op)) {
1635 for (Operation *user : privateLocalOp.getResult().getUsers()) {
1636 if (acc::ReductionAccumulateOp accumulateOp =
1637 dyn_cast<acc::ReductionAccumulateOp>(user)) {
1638 if (accumulateOp.getMemref() == privateLocalOp.getResult()) {
1639 for (mlir::acc::GPUParallelDimAttr parDim :
1640 accumulateOp.getParDims().getArray()) {
1650 if (acc::ReductionCombineOp combineOp =
1651 dyn_cast<acc::ReductionCombineOp>(user)) {
1652 if (combineOp.getSrcMemref() == privateLocalOp.
getResult()) {
1653 for (mlir::acc::GPUParallelDimAttr parDim :
1659 if (
auto combineRegionOp =
1660 dyn_cast<acc::ReductionCombineRegionOp>(user)) {
1661 if (combineRegionOp.getSrcVar() == privateLocalOp.getResult()) {
1662 for (mlir::acc::GPUParallelDimAttr parDim :
1674 acc::PrivateType privTy =
1675 cast<acc::PrivateType>(privateLocalOp.getPrivatized().getType());
1677 privTy.getBaseTy(), computeRegion->getParentOfType<ModuleOp>());
1678 if (!baseTy.hasStaticShape()) {
1679 GPUParallelDimsAttr ownParDims =
1680 getPrivateParDims(privateLocalOp, computeRegion);
1682 for (GPUParallelDimAttr parDim : ownParDims.getArray())
1687 bool hasBlock =
false;
1688 for (mlir::acc::GPUParallelDimAttr parDim : ancestorParDims)
1689 if (parDim.isAnyBlock())
1692 mlir::acc::GPUParallelDimAttr lowestParDim =
1693 mlir::acc::GPUParallelDimAttr::threadXDim(ctx);
1695 ThreadYBroadeningInfo threadYInfo =
1696 analyzeThreadYBroadening(*block, computeRegion);
1698 auto applyCombineParDims =
1699 [&](ArrayRef<mlir::acc::GPUParallelDimAttr> combineParDims) {
1700 for (mlir::acc::GPUParallelDimAttr parDim : combineParDims)
1704 block->
walk([&](Operation *op) -> WalkResult {
1708 auto addPrivateStoreParDims = [&](Value
target) {
1709 if (
auto privateLocalOp = getPrivateLocalForMemref(
target)) {
1710 GPUParallelDimsAttr parDimsAttr =
1711 getPrivateParDims(privateLocalOp, computeRegion);
1713 for (
auto parDim : parDimsAttr.getArray())
1717 if (
auto memEffects = dyn_cast<MemoryEffectOpInterface>(op)) {
1718 SmallVector<MemoryEffects::EffectInstance> effects;
1719 memEffects.getEffects(effects);
1721 if (isa<MemoryEffects::Write>(effect.getEffect()) &&
1723 addPrivateStoreParDims(effect.getValue());
1728 if (CallOpInterface callOp = dyn_cast<CallOpInterface>(op)) {
1729 if (mlir::acc::GPUParallelDimAttr parDim =
1730 getAccRoutineCallParDim(callOp, defaultPolicy)) {
1731 if (parDim.isBlockZ())
1732 lowestParDim = parDim;
1734 lowestParDim = parDim.getOneHigher();
1740 if (acc::ReductionCombineOp reductionCombineOp =
1741 dyn_cast<acc::ReductionCombineOp>(op)) {
1742 if (
failed(applyCombineParDims(
1746 if (acc::ReductionCombineRegionOp combineRegionOp =
1747 dyn_cast<acc::ReductionCombineRegionOp>(op)) {
1748 if (
failed(applyCombineParDims(
1755 if (acc::ReductionAccumulateArrayOp accArrayOp =
1756 dyn_cast<acc::ReductionAccumulateArrayOp>(op)) {
1757 for (mlir::acc::GPUParallelDimAttr parDim :
1758 accArrayOp.getParDims().getArray()) {
1764 mlir::acc::GPUParallelDimAttr threadY =
1765 mlir::acc::GPUParallelDimAttr::threadYDim(ctx);
1766 bool baselineThreadYActive = llvm::is_contained(ancestorParDims, threadY);
1767 if (threadYInfo.hasActiveWorkerCombine && !baselineThreadYActive) {
1768 if (threadYInfo.hasExplicitInactiveCombine ||
1769 threadYInfo.hasBroadeningConflict) {
1770 Operation *diagnosticOp =
1771 threadYInfo.diagnosticOp ? threadYInfo.diagnosticOp : op;
1774 "operations in the same predicate region require incompatible "
1775 "ThreadY predication");
1784 SmallVector<mlir::acc::GPUParallelDimAttr> launchParDims;
1785 if (routineParDim) {
1786 for (mlir::acc::GPUParallelDimAttr parDim = routineParDim;
1787 parDim.getOrder() >= lowestParDim.getOrder();
1788 parDim = parDim.getOneLower()) {
1792 launchParDims = computeRegion.getLaunchParDims();
1796 SmallVector<mlir::acc::GPUParallelDimAttr> activeParDims, inactiveParDims;
1797 for (mlir::acc::GPUParallelDimAttr launchParDim : launchParDims) {
1798 if (launchParDim.getOrder() < lowestParDim.getOrder())
1800 if (llvm::find(ancestorParDims, launchParDim) != ancestorParDims.end() ||
1801 (launchParDim.isAnyBlock() &&
1802 (noStructuralAncestorParDims || hasBlock))) {
1803 activeParDims.push_back(launchParDim);
1805 inactiveParDims.push_back(launchParDim);
1809 return std::pair{activeParDims, inactiveParDims};
1812Value ACCCGToGPULowering::emitPredicate(
1813 Location loc, SmallVector<mlir::acc::GPUParallelDimAttr> &inactiveParDims) {
1815 for (mlir::acc::GPUParallelDimAttr inactiveParDim : inactiveParDims) {
1816 Value threadId = getGPUThreadIdFor(inactiveParDim.getProcessor());
1818 Value zero = arith::ConstantOp::create(rewriter, loc, zeroAttr);
1819 Value cmp = arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::eq,
1822 predicate = arith::AndIOp::create(rewriter, loc, cmp, predicate);
1829void ACCCGToGPULowering::createBarrier(
1830 Location loc, mlir::acc::GPUParallelDimsAttr parDimsAttr) {
1831 bool hasAnyBlock =
false, hasThreadY =
false, hasThreadX =
false;
1832 for (
auto parDim : parDimsAttr.getArray()) {
1833 if (parDim.isAnyBlock())
1835 if (parDim.isThreadY())
1837 if (parDim.isThreadX())
1841 if (hasAnyBlock || hasThreadY)
1842 emitGPUBarrierWorkgroup(rewriter, loc);
1843 else if (hasThreadX)
1844 createPerRowBarrier(loc);
1847void ACCCGToGPULowering::createPerRowBarrier(Location loc) {
1850 if (isSingleThreadWorkerLaunch())
1856 if (isWorkerOnlyShuffleLaunch() && staticBlockDimX <
options.subgroupSize) {
1857 emitGPUBarrierRow(rewriter, loc, staticBlockDimX,
options.subgroupSize);
1861 hasThreadYBarrier =
true;
1863 if (staticBlockDimX <=
options.subgroupSize) {
1864 emitGPUBarrierSubgroup(rewriter, loc);
1868 if (
options.deviceType != mlir::acc::DeviceType::Nvidia) {
1871 "per-row barrier to support worker parallelism on non-NVIDIA device");
1887 Value blockDimX = gpu::BlockDimOp::create(
1888 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::x);
1889 Value blockDimY = gpu::BlockDimOp::create(
1890 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::y);
1892 Value isSingleWorker = arith::CmpIOp::create(
1893 rewriter, loc, arith::CmpIPredicate::eq, blockDimY, cst1);
1895 auto outerIf = scf::IfOp::create(rewriter, loc, isSingleWorker,
1900 emitGPUBarrierWorkgroup(rewriter, loc);
1904 Value cstSubgroupSize =
1906 Value isSubgroupSized = arith::CmpIOp::create(
1907 rewriter, loc, arith::CmpIPredicate::ule, blockDimX, cstSubgroupSize);
1909 auto innerIf = scf::IfOp::create(rewriter, loc, isSubgroupSized,
1915 emitGPUBarrierSubgroup(rewriter, loc);
1922 Value threadYId = gpu::ThreadIdOp::create(
1923 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::y);
1924 Value barrierId = arith::AddIOp::create(rewriter, loc, threadYId, cst1);
1927 arith::IndexCastOp::create(rewriter, loc, i32Ty, barrierId);
1928 Value numberOfThreads32 =
1929 arith::IndexCastOp::create(rewriter, loc, i32Ty, blockDimX);
1933 assert(
options.deviceType == mlir::acc::DeviceType::Nvidia);
1934 NVVM::BarrierOp::create(rewriter, loc, barrierId32, numberOfThreads32);
1943static bool hasSubsequentLoopSibling(Operation *loopOp) {
1944 for (Operation *next = loopOp->getNextNode(); next;
1945 next =
next->getNextNode()) {
1946 if (isa<scf::ParallelOp, scf::ForOp>(next))
1948 bool nested =
false;
1949 next->walk([&](Operation *op) {
1950 if (isa<scf::ParallelOp, scf::ForOp>(op)) {
1963static LoopLikeOpInterface findFirstSequentialLoop(Operation *op) {
1964 auto isAllSequentialParDims = [](scf::ParallelOp par) ->
bool {
1966 if (!pd || pd.getArray().empty())
1968 return llvm::all_of(pd.getArray(), [](mlir::acc::GPUParallelDimAttr d) {
1975 if (isa<scf::ForOp>(p))
1976 return cast<LoopLikeOpInterface>(p);
1977 if (scf::ParallelOp parOp = dyn_cast<scf::ParallelOp>(p)) {
1978 if (isAllSequentialParDims(parOp))
1979 return cast<LoopLikeOpInterface>(p);
1995static bool isLoopBodyClosureOp(Operation *op) {
1996 return isa<scf::ReduceOp, scf::YieldOp, acc::YieldOp>(op);
2001static bool isDeferredBarrierFlushPoint(Operation *op) {
2002 if (isLoopBodyClosureOp(op))
2006 if (isa<scf::ForOp>(op))
2008 if (scf::ParallelOp parallelOp = dyn_cast<scf::ParallelOp>(op)) {
2010 if (mlir::acc::GPUParallelDimsAttr parDims =
2012 if (parDims.getArray().size() == 1 &&
2013 parDims.getArray().front().isSeq()) {
2025static bool hasTrailingSideEffectSiblings(Operation *loopOp) {
2026 for (Operation *next = loopOp->getNextNode(); next;
2027 next =
next->getNextNode()) {
2028 return !isLoopBodyClosureOp(next);
2052void ACCCGToGPULowering::createBarrierAfterSeqLoop(Operation *loopOp) {
2054 auto getParentParallel = [&](Operation *op) -> scf::ParallelOp {
2056 if (parent && computeRegion->isAncestor(parent))
2060 scf::ParallelOp wsLoop = getParentParallel(loopOp);
2068 if (mayWriteSharedMemory(loopOp) && hasSubsequentLoopSibling(loopOp))
2069 emitGPUBarrierWorkgroup(rewriter, loopOp->
getLoc());
2075 wsParDims.getArray().size() == 1 && wsParDims.getArray().front().isSeq();
2082 bool hasThreadSubLoop =
false;
2083 loopOp->
walk([&](scf::ParallelOp innerPar) -> WalkResult {
2084 if (innerPar.getOperation() == loopOp)
2087 for (
auto d : dims.getArray()) {
2088 if (d.isThreadX() || d.isThreadY()) {
2089 hasThreadSubLoop =
true;
2095 if (!hasThreadSubLoop)
2097 scf::ParallelOp threadLoop = getParentParallel(wsLoop);
2100 scf::ParallelOp blockLoop = getParentParallel(threadLoop);
2103 mlir::acc::GPUParallelDimsAttr parDimsAttr =
2105 if (parDimsAttr.hasOnlyBlockLevel())
2106 createBarrier(loopOp->
getLoc(), parDimsAttr);
2112 scf::ParallelOp seqLoop = getParentParallel(wsLoop);
2121 if (mayWriteSharedMemory(loopOp) && hasSubsequentLoopSibling(wsLoop))
2122 emitGPUBarrierWorkgroup(rewriter, loopOp->
getLoc());
2125 if (scf::ParallelOp outerParLoop = getParentParallel(seqLoop)) {
2126 mlir::acc::GPUParallelDimsAttr parDimsAttr =
2128 if (parDimsAttr.hasOnlyBlockLevel()) {
2129 createBarrier(loopOp->
getLoc(), parDimsAttr);
2130 }
else if (parDimsAttr.hasOnlyThreadYLevel()) {
2131 createPerRowBarrier(loopOp->
getLoc());
2132 }
else if (parDimsAttr && parDimsAttr.isSeq()) {
2136 for (scf::ParallelOp gangLoop = getParentParallel(outerParLoop); gangLoop;
2137 gangLoop = getParentParallel(gangLoop)) {
2138 mlir::acc::GPUParallelDimsAttr gangDims =
2140 if (gangDims.hasOnlyBlockLevel()) {
2141 createBarrier(loopOp->
getLoc(), gangDims);
2144 if (!gangDims.isSeq())
2158 mlir::acc::GPUParallelDimsAttr parDimsAttr =
2160 if (parDimsAttr.hasOnlyBlockLevel() && mayWriteSharedMemory(loopOp)) {
2161 createBarrier(loopOp->
getLoc(), parDimsAttr);
2165bool ACCCGToGPULowering::mayWriteSharedMemory(Operation *loopOp) {
2167 loopOp->
walk([&](memref::StoreOp storeOp) {
2170 llvm::SmallVector<Value, 8> worklist{storeOp.getMemref()};
2171 llvm::SmallPtrSet<Value, 8> seen;
2172 while (!worklist.empty()) {
2173 Value v = worklist.pop_back_val();
2174 if (!seen.insert(v).second)
2179 if (acc::PrivateLocalOp privateLocal =
2180 dyn_cast<acc::PrivateLocalOp>(def)) {
2181 acc::PrivatizeOp privatizeOp =
2189 if (mlir::acc::GPUParallelDimsAttr parDims =
2190 privatizeOp.getParDimsAttr()) {
2191 bool hasBlock =
false, hasThread =
false;
2192 for (mlir::acc::GPUParallelDimAttr d : parDims.getArray()) {
2195 if (d.isThreadX() || d.isThreadY())
2198 if (hasBlock && !hasThread) {
2215static bool isAllocatedInside(Value memref, Operation *scope) {
2220 if (
auto effectOp = dyn_cast<MemoryEffectOpInterface>(def))
2221 if (effectOp.getEffectOnValue<MemoryEffects::Allocate>(v))
2225 if (ViewLikeOpInterface viewLike = dyn_cast<ViewLikeOpInterface>(def))
2226 v = viewLike.getViewSource();
2235static bool isRedundantSequentialLoop(Operation *op) {
2236 if (isa<scf::ForOp, scf::WhileOp>(op))
2238 scf::ParallelOp par = dyn_cast<scf::ParallelOp>(op);
2242 return dims && !dims.getArray().empty() &&
2243 llvm::all_of(dims.getArray(), [](mlir::acc::GPUParallelDimAttr d) {
2255static Value getStorageRoot(Value memref) {
2258 auto onlyNonMemoryEffects = [](Operation *op) {
2259 MemoryEffectOpInterface effectOp = dyn_cast<MemoryEffectOpInterface>(op);
2262 SmallVector<MemoryEffects::EffectInstance> effects;
2263 effectOp.getEffects(effects);
2265 return e.
getResource() != SideEffects::DefaultResource::get();
2272 Operation *owner = cast<BlockArgument>(v).getOwner()->getParentOp();
2273 if (isa<scf::ForOp, scf::WhileOp>(owner))
2278 if (
auto computeOp = dyn_cast<acc::ComputeRegionOp>(owner))
2279 if (Value operand = computeOp.getOperand(cast<BlockArgument>(v)))
2280 if (Value outerRoot = getStorageRoot(operand))
2284 if (
auto effectOp = dyn_cast<MemoryEffectOpInterface>(def))
2285 if (effectOp.getEffectOnValue<MemoryEffects::Allocate>(v))
2290 if (ViewLikeOpInterface viewLike = dyn_cast<ViewLikeOpInterface>(def))
2291 v = viewLike.getViewSource();
2297bool ACCCGToGPULowering::mayAccessSharedMemory(Operation *root,
bool reads,
2298 bool writes,
bool workerLevel,
2299 Operation *localScope,
2301 SharedStorage *collect,
2302 const SharedStorage *filter) {
2307 auto sharedAccess = [&](Value
target) {
2308 Value storage =
target ? getStorageRoot(
target) : Value();
2309 if (filter && storage && !filter->unknown &&
2310 !filter->roots.contains(storage))
2316 collect->roots.insert(storage);
2318 collect->unknown =
true;
2321 root->
walk([&](Operation *op) {
2322 if (op->
hasTrait<OpTrait::IsTerminator>())
2327 if (isa<acc::ReductionAccumulateOp, acc::ReductionCombineOp,
2328 acc::ReductionCombineRegionOp>(op))
2330 MemoryEffectOpInterface effectOp = dyn_cast<MemoryEffectOpInterface>(op);
2334 if (op->
hasTrait<OpTrait::HasRecursiveMemoryEffects>())
2336 return sharedAccess(Value());
2338 SmallVector<MemoryEffects::EffectInstance> effects;
2339 effectOp.getEffects(effects);
2342 (writes && isa<MemoryEffects::Write>(effect.getEffect())) ||
2343 (reads && isa<MemoryEffects::Read>(effect.getEffect()));
2346 Value
target = effect.getValue();
2348 PrivateMemScope scope = getPrivateScopeForMemref(
target);
2349 if (scope == PrivateMemScope::Thread ||
2350 (workerLevel && scope == PrivateMemScope::Worker))
2352 if (localScope && isAllocatedInside(
target, localScope))
2355 if (sharedAccess(
target).wasInterrupted())
2363bool ACCCGToGPULowering::needsBarrierAfterThreadLoop(scf::ParallelOp loop) {
2365 if (!loopDims || loopDims.getArray().size() != 1)
2367 mlir::acc::GPUParallelDimAttr loopDim = loopDims.getArray().front();
2368 if (!loopDim.isThreadX() && !loopDim.isThreadY())
2370 bool workerLevel = loopDim.isThreadY();
2375 for (Operation *parent = loop->getParentOp();
2376 parent && parent != computeRegion.getOperation();
2377 parent = parent->getParentOp()) {
2378 scf::ParallelOp parentLoop = dyn_cast<scf::ParallelOp>(parent);
2381 mlir::acc::GPUParallelDimsAttr parentDims =
2385 for (mlir::acc::GPUParallelDimAttr dim : parentDims.getArray())
2386 if (dim.isThreadY())
2393 bool hasOwnTile =
false;
2394 loop->walk([&](acc::GPUSharedMemoryOp) {
2403 bool inSequentialNest =
false;
2404 for (Operation *parent = loop->getParentOp();
2405 parent && parent != computeRegion.getOperation();
2406 parent = parent->getParentOp()) {
2407 if (isa<scf::ForOp, scf::WhileOp>(parent)) {
2408 inSequentialNest =
true;
2412 if (!inSequentialNest)
2421 SharedStorage written, touched;
2422 mayAccessSharedMemory(loop,
false,
true, workerLevel,
2423 loop,
nullptr, &written);
2424 mayAccessSharedMemory(loop,
true,
false, workerLevel,
2425 loop,
nullptr, &touched);
2426 touched.insert(written);
2427 if (touched.empty())
2429 bool loopWrites = !written.empty();
2432 auto conflictsWithLoop = [&](Operation *op, Operation *localScope,
2433 Operation *ignore) {
2434 if (loopWrites && mayAccessSharedMemory(
2435 op,
true,
false, workerLevel,
2436 localScope, ignore,
nullptr, &written))
2438 return mayAccessSharedMemory(op,
false,
true,
2439 workerLevel, localScope, ignore,
2447 for (Operation *cur = loop; cur && cur != computeRegion.getOperation();
2448 cur = cur->getParentOp()) {
2449 for (Operation *next = cur->getNextNode(); next; next =
next->getNextNode())
2450 if (conflictsWithLoop(next,
nullptr,
nullptr))
2452 Operation *parent = cur->getParentOp();
2453 if (!parent || parent == computeRegion.getOperation())
2455 if (!isRedundantSequentialLoop(parent))
2458 if (isa<scf::WhileOp>(parent))
2463 for (Operation *prev = &cur->getBlock()->front(); prev;
2464 prev = prev->getNextNode()) {
2466 if (conflictsWithLoop(prev,
nullptr,
nullptr))
2475 if (loopWrites && mayAccessSharedMemory(
2476 loop,
true,
false, workerLevel,
2478 nullptr,
nullptr, &written))
2481 if (cur != loop && conflictsWithLoop(cur, cur, loop))
2490ACCCGToGPULowering::getPrivateMemScope(acc::PrivatizeOp privatizeOp) {
2491 bool hasBlock =
false;
2492 bool hasThreadX =
false;
2493 bool hasThreadY =
false;
2494 if (mlir::acc::GPUParallelDimsAttr parDims = privatizeOp.getParDimsAttr()) {
2495 for (mlir::acc::GPUParallelDimAttr d : parDims.getArray()) {
2504 for (mlir::acc::GPUParallelDimAttr d : computeRegion.getLaunchParDims())
2508 return PrivateMemScope::Gang;
2509 return PrivateMemScope::Thread;
2512 return PrivateMemScope::Thread;
2513 if (hasBlock && hasThreadY)
2514 return PrivateMemScope::Worker;
2516 return PrivateMemScope::Gang;
2517 return PrivateMemScope::Thread;
2521static acc::PrivateLocalOp getPrivateLocalForMemref(Value memref) {
2522 llvm::SmallVector<Value, 8> worklist{memref};
2523 llvm::SmallPtrSet<Value, 8> seen;
2524 while (!worklist.empty()) {
2525 Value v = worklist.pop_back_val();
2526 if (!seen.insert(v).second)
2531 if (acc::PrivateLocalOp privateLocal = dyn_cast<acc::PrivateLocalOp>(def))
2532 return privateLocal;
2538PrivateMemScope ACCCGToGPULowering::getPrivateScopeForMemref(Value memref) {
2539 if (
auto privateLocal = getPrivateLocalForMemref(memref))
2540 return getPrivateMemScope(
getPrivatizeOp(privateLocal, computeRegion));
2541 return PrivateMemScope::None;
2544acc::PrivatizeOp ACCCGToGPULowering::getPrivatizeForMemref(Value memref) {
2545 if (
auto privateLocal = getPrivateLocalForMemref(memref))
2547 return acc::PrivatizeOp();
2550void ACCCGToGPULowering::collectSharedPrivateStores(
2551 Operation *root, llvm::SmallPtrSetImpl<Operation *> &gangPrivatizes,
2552 llvm::SmallPtrSetImpl<Operation *> &workerPrivatizes) {
2553 root->
walk([&](memref::StoreOp storeOp) {
2554 PrivateMemScope scope = getPrivateScopeForMemref(storeOp.getMemref());
2555 if (scope != PrivateMemScope::Gang && scope != PrivateMemScope::Worker)
2557 acc::PrivatizeOp privatize = getPrivatizeForMemref(storeOp.getMemref());
2560 if (scope == PrivateMemScope::Gang)
2561 gangPrivatizes.insert(privatize.getOperation());
2563 workerPrivatizes.insert(privatize.getOperation());
2567bool ACCCGToGPULowering::isMatchingPrivateMemref(
2568 Value memref, PrivateMemScope storeScope,
2569 const llvm::SmallPtrSetImpl<Operation *> &storePrivatizes) {
2570 if (getPrivateScopeForMemref(memref) != storeScope)
2572 acc::PrivatizeOp usePrivatize = getPrivatizeForMemref(memref);
2573 return usePrivatize && storePrivatizes.contains(usePrivatize.getOperation());
2576bool ACCCGToGPULowering::hasParallelPrivateReuse(
2577 LoopLikeOpInterface seqLoopOp, Operation *skipOp,
2578 PrivateMemScope storeScope,
2579 const llvm::SmallPtrSetImpl<Operation *> &storePrivatizes) {
2580 if (storePrivatizes.empty())
2583 bool hasParallelPrivateUse =
false;
2584 seqLoopOp.getOperation()->walk([&](Operation *op) {
2590 if (memref::LoadOp loadOp = dyn_cast<memref::LoadOp>(op))
2591 memref = loadOp.getMemref();
2592 else if (memref::StoreOp storeOp = dyn_cast<memref::StoreOp>(op))
2593 memref = storeOp.getMemref();
2597 if (!isMatchingPrivateMemref(memref, storeScope, storePrivatizes))
2600 bool insideNestedParallel =
false;
2601 for (Operation *p = op->
getParentOp(); p && p != seqLoopOp.getOperation();
2603 if (scf::ParallelOp par = dyn_cast<scf::ParallelOp>(p)) {
2604 if (mlir::acc::GPUParallelDimsAttr pd =
2606 if (llvm::any_of(pd.getArray(), [](mlir::acc::GPUParallelDimAttr d) {
2609 insideNestedParallel =
true;
2615 if (!insideNestedParallel)
2618 hasParallelPrivateUse =
true;
2622 return hasParallelPrivateUse;
2626ACCCGToGPULowering::needsPreStoreReuseBarrier(acc::PredicateRegionOp interOp) {
2630 LoopLikeOpInterface seqLoopOp = findFirstSequentialLoop(interOp);
2632 return PrivateMemScope::None;
2636 llvm::SmallPtrSet<Operation *, 4> gangPrivatizes, workerPrivatizes;
2637 collectSharedPrivateStores(interOp, gangPrivatizes, workerPrivatizes);
2643 if (hasParallelPrivateReuse(seqLoopOp, interOp, PrivateMemScope::Gang,
2645 return PrivateMemScope::Gang;
2646 if (hasParallelPrivateReuse(seqLoopOp, interOp, PrivateMemScope::Worker,
2648 return PrivateMemScope::Worker;
2649 return PrivateMemScope::None;
2652bool ACCCGToGPULowering::needsInLoopReuseBarrier(Operation *loopOp) {
2657 if (scf::ParallelOp par = dyn_cast<scf::ParallelOp>(loopOp))
2664 if (!hasThreadLevelRoutineCall)
2666 bool callsThreadLevelRoutine =
false;
2667 loopOp->
walk([&](CallOpInterface callOp) -> WalkResult {
2668 if (mlir::acc::GPUParallelDimAttr parDim =
2669 getAccRoutineCallParDim(callOp, defaultPolicy)) {
2670 if (parDim.isThreadX() || parDim.isThreadY()) {
2671 callsThreadLevelRoutine =
true;
2677 if (!callsThreadLevelRoutine)
2683 llvm::SmallPtrSet<Operation *, 4> storedPrivatizes, workerPrivatizes;
2684 collectSharedPrivateStores(loopOp, storedPrivatizes, workerPrivatizes);
2685 if (storedPrivatizes.empty())
2691 bool reusesGangPrivate =
false;
2692 loopOp->
walk([&](Operation *op) -> WalkResult {
2693 if (memref::LoadOp loadOp = dyn_cast<memref::LoadOp>(op))
2694 reusesGangPrivate = isMatchingPrivateMemref(
2695 loadOp.getMemref(), PrivateMemScope::Gang, storedPrivatizes);
2696 else if (CallOpInterface callOp = dyn_cast<CallOpInterface>(op))
2697 reusesGangPrivate = llvm::any_of(callOp.getArgOperands(), [&](Value arg) {
2698 return isMatchingPrivateMemref(arg, PrivateMemScope::Gang,
2703 if (!reusesGangPrivate)
2710 p && p != computeRegion.getOperation(); p = p->
getParentOp()) {
2711 scf::ParallelOp par = dyn_cast<scf::ParallelOp>(p);
2715 for (mlir::acc::GPUParallelDimAttr d : parDims.getArray())
2716 if (d.isThreadX() || d.isThreadY())
2722void ACCCGToGPULowering::processPredicateRegion(
2723 acc::PredicateRegionOp interOp) {
2724 LLVM_DEBUG(llvm::dbgs() <<
"processing predicate region: ";
2725 interOp->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
2726 Location loc = interOp->getLoc();
2728 std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
2729 SmallVector<mlir::acc::GPUParallelDimAttr>>
2730 parDimsPair = computeActiveAndInactiveParDims(
2731 interOp, &interOp.getRegion().front());
2742 if (hasThreadYReduction && !isSingleThreadWorkerLaunch()) {
2743 MLIRContext *ctx = computeRegion->getContext();
2744 mlir::acc::GPUParallelDimAttr threadXParDim =
2745 mlir::acc::GPUParallelDimAttr::threadXDim(ctx);
2746 bool hasThreadXInActive =
2747 llvm::any_of(parDimsPair.first, [](mlir::acc::GPUParallelDimAttr pd) {
2748 return pd.isThreadX();
2750 bool hasThreadXInInactive =
2751 llvm::any_of(parDimsPair.second, [](mlir::acc::GPUParallelDimAttr pd) {
2752 return pd.isThreadX();
2758 bool regionHasThreadLevelRoutineCall =
false;
2759 if (hasThreadLevelRoutineCall) {
2760 interOp.getRegion().walk([&](CallOpInterface callOp) {
2761 if (mlir::acc::GPUParallelDimAttr parDim =
2762 getAccRoutineCallParDim(callOp, defaultPolicy)) {
2763 if (parDim.isThreadX() || parDim.isThreadY()) {
2764 regionHasThreadLevelRoutineCall =
true;
2772 if (!hasThreadXInActive && !hasThreadXInInactive &&
2773 !regionHasThreadLevelRoutineCall) {
2774 parDimsPair.second.push_back(threadXParDim);
2778 Value predicate = emitPredicate(loc, parDimsPair.second);
2781 if (!predicate && llvm::all_of(computeRegion.getLaunchParDims(),
2782 [](mlir::acc::GPUParallelDimAttr pd) {
2783 return pd.isAnyBlock();
2787 LLVM_DEBUG(llvm::dbgs() <<
"predicate: " << predicate <<
"\n");
2788 bool isInsideThreadXLoop =
false;
2789 bool isInsideThreadYLoop =
false;
2790 for (
auto parDim : parDimsPair.first) {
2791 if (parDim.isThreadX())
2792 isInsideThreadXLoop =
true;
2793 if (parDim.isThreadY())
2794 isInsideThreadYLoop =
true;
2800 auto emitReconvergenceBarrier = [&]() {
2801 if (isInsideThreadXLoop) {
2803 }
else if (isInsideThreadYLoop) {
2808 bool predicatesThreadX = llvm::any_of(
2810 [](mlir::acc::GPUParallelDimAttr pd) { return pd.isThreadX(); });
2811 if (predicatesThreadX)
2812 createPerRowBarrier(loc);
2815 }
else if (!parDimsPair.first.empty()) {
2817 createBarrier(loc, mlir::acc::GPUParallelDimsAttr::get(
2818 interOp->getContext(), parDimsPair.first));
2821 createBarrier(loc, mlir::acc::GPUParallelDimsAttr::get(
2822 interOp->getContext(), parDimsPair.second));
2834 PrivateMemScope scope = needsPreStoreReuseBarrier(interOp);
2835 if (scope == PrivateMemScope::Gang)
2836 emitGPUBarrierWorkgroup(rewriter, loc);
2837 else if (scope == PrivateMemScope::Worker)
2838 createPerRowBarrier(loc);
2840 auto ifOp = scf::IfOp::create(rewriter, loc, predicate,
2842 Region &thenRegion = ifOp.getThenRegion();
2846 for (
auto &bodyOp : interOp.getRegion().front().getOperations()) {
2850 if (memref::StoreOp storeOp = dyn_cast<memref::StoreOp>(&bodyOp)) {
2851 std::optional<arith::AtomicRMWKind> blockReduceKind;
2852 bool failedReductionKind =
false;
2853 Value storeVal = storeOp.getValueToStore();
2860 Block *epilogueBlock = interOp->getBlock();
2861 auto findBlockAccLoad =
2863 Value val) -> std::optional<arith::AtomicRMWKind> {
2864 Operation *def = val.getDefiningOp();
2865 if (!def || def->
getBlock() != epilogueBlock)
2866 return std::nullopt;
2867 if (memref::LoadOp loadOp = dyn_cast<memref::LoadOp>(def)) {
2868 for (
auto *user : loadOp.getMemRef().getUsers()) {
2869 if (acc::ReductionAccumulateOp accOp =
2870 dyn_cast<acc::ReductionAccumulateOp>(user)) {
2871 if (llvm::any_of(accOp.getParDims().getArray(),
2872 [](mlir::acc::GPUParallelDimAttr pd) {
2873 return pd.isAnyBlock();
2875 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
2876 accOp.getReductionOperator(),
2877 accOp.getValue().getType(), accOp.getLoc());
2879 failedReductionKind =
true;
2880 return std::nullopt;
2886 return std::nullopt;
2889 if (
auto kind = self(self, operand))
2891 return std::nullopt;
2893 blockReduceKind = findBlockAccLoad(findBlockAccLoad, storeVal);
2895 if (failedReductionKind)
2897 if (blockReduceKind) {
2900 bool threadIsActive = llvm::any_of(
2901 parDimsPair.first, [](mlir::acc::GPUParallelDimAttr pd) {
2902 return !pd.isAnyBlock();
2904 if (!threadIsActive &&
2905 !isa_and_nonnull<memref::AllocaOp>(
2906 unwrapMemRefConversion(memref).getDefiningOp())) {
2912 MemRefType memrefTy = cast<MemRefType>(memref.
getType());
2914 SmallVector<Value> initIndices;
2915 for (Value idx : storeOp.getIndices())
2918 OpBuilder::InsertionGuard guard(rewriter);
2919 Block &launchBody = launch.getBody().front();
2920 Operation *insertBefore =
nullptr;
2922 launchBody.
walk([&](scf::ParallelOp parOp) -> WalkResult {
2923 for (Operation *parent = parOp->getParentOp(); parent;
2925 if (parent == launch.getOperation())
2927 if (isa<scf::ParallelOp>(parent))
2930 insertBefore = parOp.getOperation();
2936 std::function<bool(Value)> fromLaunch = [&](Value v) ->
bool {
2937 if (
auto arg = dyn_cast<BlockArgument>(v))
2938 return launch->isAncestor(arg.getOwner()->getParentOp());
2940 if (!launch->isAncestor(def) ||
2941 def->
hasTrait<OpTrait::ConstantLike>())
2947 if (!fromLaunch(memref) &&
2948 llvm::none_of(initIndices, fromLaunch)) {
2951 Value token = launch.getAsyncToken();
2952 auto initLaunch = gpu::LaunchOp::create(
2953 rewriter, loc, one, one, one, one, one, one,
2955 token ? token.
getType() : Type(),
2956 launch.getAsyncDependencies());
2958 &initLaunch.getBody().front());
2960 gpu::TerminatorOp::create(rewriter, loc));
2961 }
else if (insertBefore)
2971 DominanceInfo domInfo(launch);
2972 IRMapping initMapping;
2973 std::function<Value(Value)> materialize =
2974 [&](Value val) -> Value {
2975 Operation *defOp = val.getDefiningOp();
2986 materialize(operand);
2987 Operation *cloned = rewriter.
clone(*defOp, initMapping);
2988 for (
auto [orig, clonedRes] :
2990 initMapping.
map(orig, clonedRes);
2992 return initMapping.
lookup(val);
2994 Value initMemref = materialize(memref);
2995 for (
auto &idx : initIndices)
2996 idx = materialize(idx);
2998 rewriter, loc, memrefTy.getElementType(), *blockReduceKind,
3000 Value blockId = gpu::BlockIdOp::create(
3001 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::x);
3002 Value threadId = gpu::ThreadIdOp::create(
3003 rewriter, loc, rewriter.
getIndexType(), gpu::Dimension::x);
3005 Value isBlock0 = arith::CmpIOp::create(
3006 rewriter, loc, arith::CmpIPredicate::eq, blockId, zero);
3007 Value isThread0 = arith::CmpIOp::create(
3008 rewriter, loc, arith::CmpIPredicate::eq, threadId, zero);
3009 Value isFirstThread =
3010 arith::AndIOp::create(rewriter, loc, isBlock0, isThread0);
3011 auto initIf = scf::IfOp::create(rewriter, loc, isFirstThread,
3014 initIf.getThenRegion().back().getTerminator());
3015 memref::StoreOp::create(rewriter, loc, identityVal, initMemref,
3018 gpu::BarrierOp::create(rewriter, loc);
3020 SmallVector<Value> atomicIndices;
3021 for (Value idx : storeOp.getIndices())
3023 constructAtomicAccumulation(loc, memref, atomicIndices, input,
3032 emitReconvergenceBarrier();
3035 for (
auto &bodyOp : interOp.getRegion().front().getOperations())
3058Value ACCCGToGPULowering::allocatePrivatizeHeapStorage(
3059 acc::PrivatizeOp privatize, MemRefType baseTy,
3061 Location loc = privatize.getLoc();
3063 memref::AllocOp::create(rewriter, loc, baseTy, mappedDynamicSizes);
3064 OpBuilder::InsertionGuard guard(rewriter);
3068 memref::DeallocOp::create(rewriter, loc, mem);
3070 mapping.
map(privatize.getResult(), mem);
3073 if (!privatize->getParentOfType<acc::ComputeRegionOp>())
3078Value ACCCGToGPULowering::processPrivatize(acc::PrivatizeOp privatize) {
3079 LLVM_DEBUG(llvm::dbgs() <<
"processing privatize: ";
3080 privatize->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3081 Value tracked = privatize.getResult();
3082 if (acc::ComputeRegionOp insUser =
3083 dyn_cast<acc::ComputeRegionOp>(getOnlyUser(tracked))) {
3084 assert(privatize->hasOneUse() &&
3085 "expected acc.privatize op to have one use");
3086 tracked = insUser.getBody()->getArgument(
3087 privatize->use_begin()->getOperandNumber());
3089 Operation *privatizeUser = getOnlyUser(tracked);
3090 assert(privatizeUser &&
"expected PrivateLocalOp user for privatize");
3091 acc::PrivateLocalOp privateLocalUser =
3092 dyn_cast<acc::PrivateLocalOp>(privatizeUser);
3094 std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
3095 SmallVector<mlir::acc::GPUParallelDimAttr>>
3096 parDimsPair = computeActiveAndInactiveParDims(privatizeUser,
nullptr);
3098 if (!privatize.getParDimsAttr()) {
3099 privatize.setParDimsAttr(mlir::acc::GPUParallelDimsAttr::get(
3103 Location loc = privatize->getLoc();
3104 acc::PrivateType privTy = cast<acc::PrivateType>(privatize.getType());
3105 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
3108 gpu::GPUFuncOp gpuFuncOp = computeRegion->getParentOfType<gpu::GPUFuncOp>();
3113 privatize->getParentOfType<acc::ComputeRegionOp>() != computeRegion) {
3114 return privatize.getResult();
3117 bool threadXActive =
3118 llvm::any_of(parDimsPair.first, [](mlir::acc::GPUParallelDimAttr parDim) {
3119 return parDim.isThreadX();
3121 if (threadXActive &&
3122 canUseStackAlloca(baseTy, loc,
options.maxThreadPrivateStack)) {
3123 auto alloca = memref::AllocaOp::create(rewriter, loc, baseTy);
3124 recordThreadPrivate(privateLocalUser);
3125 mapping.
map(privatize.getResult(), alloca.getResult());
3126 return alloca.getResult();
3130 return privatize.getResult();
3138 bool threadYIsActive =
3139 llvm::any_of(parDimsPair.first, [](mlir::acc::GPUParallelDimAttr parDim) {
3140 return parDim.isThreadY();
3153 bool needsWorkgroupBarrier =
false;
3155 FunctionOpInterface funcOp =
3156 computeRegion->getParentOfType<FunctionOpInterface>();
3157 mlir::acc::GPUParallelDimAttr routineParDim =
3158 getSpecializedRoutineDim(funcOp, defaultPolicy);
3159 if (routineParDim.isThreadX()) {
3162 threadYIsActive =
true;
3163 }
else if (routineParDim.isThreadY()) {
3166 needsWorkgroupBarrier =
true;
3167 }
else if (routineParDim.isAnyBlock()) {
3168 needsWorkgroupBarrier =
true;
3172 llvm::SmallVector<Value> mappedDynamicSizes;
3173 for (
auto dynamicSize : privatize.getDynamicSizes()) {
3175 mappedDynamicSizes.push_back(mappedDynamicSize);
3178 computeRegion.isEffectivelySerial()) {
3179 if (mappedDynamicSizes.empty()) {
3182 memref::AllocaOp::create(rewriter, privatize->getLoc(), baseTy);
3183 recordThreadPrivate(privateLocalUser);
3184 mapping.
map(privatize.getResult(), alloca.getResult());
3185 return alloca.getResult();
3188 return allocatePrivatizeHeapStorage(privatize, baseTy, mappedDynamicSizes);
3191 if (threadXActive) {
3196 return allocatePrivatizeHeapStorage(privatize, baseTy, mappedDynamicSizes);
3201 SmallVector<mlir::acc::GPUParallelDimAttr> predicateDims;
3202 for (
auto parDim : parDimsPair.second) {
3204 if (threadYIsActive && parDim.isThreadY())
3206 predicateDims.push_back(parDim);
3208 Value predicate = emitPredicate(loc, predicateDims);
3210 predicate = arith::ConstantOp::create(
3213 auto ifOp = scf::IfOp::create(rewriter, loc, predicate,
3215 Region &thenRegion = ifOp.getThenRegion();
3218 auto mem = memref::AllocOp::create(rewriter, privatize->getLoc(), baseTy,
3219 mappedDynamicSizes);
3221 gpu::AddressSpaceAttr sharedMemoryAddressSpace = gpu::AddressSpaceAttr::get(
3222 computeRegion->getContext(), gpu::GPUDialect::getWorkgroupAddressSpace());
3226 constexpr int64_t kMaxThreadY = 32;
3227 MemRefType sharedMemTy =
3229 ? MemRefType::get({kMaxThreadY}, baseTy, MemRefLayoutAttrInterface{},
3230 sharedMemoryAddressSpace)
3231 : MemRefType::
get({}, baseTy, MemRefLayoutAttrInterface{},
3232 sharedMemoryAddressSpace);
3235 bool reuseBroadcast = !gpuFuncOp.isKernel();
3237 llvm::DenseMap<Type, Value>::iterator cachedSlot =
3238 reuseBroadcast ? privatizeBroadcastCache.find(sharedMemTy)
3239 : privatizeBroadcastCache.end();
3240 if (reuseBroadcast && cachedSlot != privatizeBroadcastCache.end()) {
3241 alloca = cachedSlot->second;
3244 mlir::acc::GPUParallelDimAttr dim =
3245 needsWorkgroupBarrier
3246 ? mlir::acc::GPUParallelDimAttr::threadYDim(rewriter.
getContext())
3247 : mlir::
acc::GPUParallelDimAttr::threadXDim(rewriter.
getContext());
3249 loc, mlir::acc::GPUParallelDimsAttr::get(rewriter.
getContext(), {dim}));
3251 alloca = gpuFuncOp.addWorkgroupAttribution(sharedMemTy,
3256 unsigned index = gpuFuncOp.getNumWorkgroupAttributions() - 1;
3257 gpuFuncOp.setWorkgroupAttributionAttr(
index,
3258 LLVM::LLVMDialect::getAlignAttrName(),
3261 privatizeBroadcastCache[sharedMemTy] = alloca;
3264 if (threadYIsActive) {
3265 Value threadYId = getThreadId(loc, gpu::Dimension::y);
3266 memref::StoreOp::create(rewriter, privatize->getLoc(), mem, alloca,
3269 memref::StoreOp::create(rewriter, privatize->getLoc(), mem, alloca);
3275 if (needsWorkgroupBarrier) {
3276 mlir::acc::GPUParallelDimsAttr threadYDimsAttr =
3277 mlir::acc::GPUParallelDimsAttr::get(
3279 {mlir::acc::GPUParallelDimAttr::threadYDim(rewriter.getContext())});
3280 createBarrier(loc, threadYDimsAttr);
3283 mlir::acc::GPUParallelDimsAttr threadXDimsAttr =
3284 mlir::acc::GPUParallelDimsAttr::get(
3286 {mlir::acc::GPUParallelDimAttr::threadXDim(rewriter.getContext())});
3287 createBarrier(loc, threadXDimsAttr);
3291 if (threadYIsActive) {
3292 Value threadYId = getThreadId(loc, gpu::Dimension::y);
3293 load = memref::LoadOp::create(rewriter, privatize->getLoc(), alloca,
3296 load = memref::LoadOp::create(rewriter, privatize->getLoc(), alloca,
3300 mapping.
map(privatize.getResult(),
load);
3304 if (!privatize->getParentOfType<acc::ComputeRegionOp>())
3308 if (needsWorkgroupBarrier) {
3309 mlir::acc::GPUParallelDimsAttr workerDimsAttr =
3310 mlir::acc::GPUParallelDimsAttr::get(
3312 {mlir::acc::GPUParallelDimAttr::threadYDim(rewriter.getContext())});
3313 createBarrier(loc, workerDimsAttr);
3315 mlir::acc::GPUParallelDimsAttr vectorDimsAttr =
3316 mlir::acc::GPUParallelDimsAttr::get(
3318 {mlir::acc::GPUParallelDimAttr::threadXDim(rewriter.getContext())});
3319 createBarrier(loc, vectorDimsAttr);
3321 auto ifOp2 = scf::IfOp::create(rewriter, loc, predicate,
3323 Region &thenRegion2 = ifOp2.getThenRegion();
3326 memref::DeallocOp::create(rewriter, privatize->getLoc(),
load);
3337void ACCCGToGPULowering::processPrivateLocal(
3338 acc::PrivateLocalOp privateLocal, std::optional<int64_t> sharedMemCopies) {
3339 LLVM_DEBUG(llvm::dbgs() <<
"processing private local: ";
3340 privateLocal->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3341 Location loc = privateLocal.getLoc();
3342 acc::PrivateType privTy =
3343 cast<acc::PrivateType>(privateLocal.getPrivatized().getType());
3344 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
3346 MemRefType byteMemrefTy =
3347 MemRefType::get({ShapedType::kDynamic}, rewriter.
getI8Type());
3349 acc::PrivatizeOp privatizeOp =
getPrivatizeOp(privateLocal, computeRegion);
3351 if (privatizeOp->getParentOfType<acc::ComputeRegionOp>() == computeRegion) {
3355 privateLocal.getType());
3356 mapping.
map(privateLocal.getResult(),
result);
3364 acc::ReductionAccumulateArrayOp arrayAccum =
3365 perThreadArrayReductionAccum(privateLocal.getResult());
3367 (arrayAccum && storageHasThreadX(privateLocal, computeRegion))) &&
3368 canUseStackAlloca(baseTy, loc,
options.maxThreadPrivateStack)) {
3369 Value alloca = memref::AllocaOp::create(rewriter, loc, baseTy);
3370 recordThreadPrivate(privateLocal);
3372 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
3373 arrayAccum.getReductionOperator(), baseTy.getElementType(), loc);
3376 initPerThreadArrayAccum(rewriter, loc, alloca, baseTy, *kind);
3379 privateLocal.getType());
3380 mapping.
map(privateLocal.getResult(), mem);
3386 std::optional<int64_t>
copies =
3387 sharedMemCopies ? sharedMemCopies
3388 : isEligibleForSharedMemory(privateLocal, baseTy);
3390 int64_t numCopies = *
copies;
3391 int64_t elementSize = getElementSizeInBytes(loc, baseTy.getElementType());
3392 int64_t numElements = 1;
3393 for (int64_t dim : baseTy.getShape())
3395 int64_t upperBound = elementSize * numElements * numCopies;
3397 if (tryAllocateSharedMemory(upperBound)) {
3398 std::string varName =
3400 sharedMemPrivateVarNames.push_back(varName.empty() ?
"(*)" : varName);
3402 gpu::AddressSpaceAttr workgroupAS = gpu::AddressSpaceAttr::get(
3403 computeRegion->getContext(),
3404 gpu::GPUDialect::getWorkgroupAddressSpace());
3405 MemRefType sharedMemTy =
3406 MemRefType::get(baseTy.getShape(), baseTy.getElementType(),
3407 MemRefLayoutAttrInterface{}, workgroupAS);
3408 Value sharedMem = acc::GPUSharedMemoryOp::create(
3416 privateLocal.getType());
3418 mapping.
map(privateLocal.getResult(),
result);
3423 OpBuilder::InsertionGuard guard(rewriter);
3425 inputMem = processPrivatize(privatizeOp);
3429 assert(inputMem &&
"expected input mem to be mapped");
3431 privateLocal.getType());
3432 mapping.
map(privateLocal.getResult(),
result);
3438 SmallVector<int64_t> viewShape;
3440 SmallVector<Value> viewDynSizes;
3442 SmallVector<OpFoldResult> subviewOffset;
3445 SmallVector<OpFoldResult> subviewSizes;
3447 SmallVector<int64_t> subviewStrides;
3449 SmallVector<int64_t> subviewShape;
3451 std::pair<SmallVector<mlir::acc::GPUParallelDimAttr>,
3452 SmallVector<mlir::acc::GPUParallelDimAttr>>
3453 parDimsPair = computeActiveAndInactiveParDims(privateLocal,
nullptr);
3454 acc::ReductionAccumulateArrayOp arrayAccum =
3455 perThreadArrayReductionAccum(privateLocal.getResult());
3456 for (mlir::acc::GPUParallelDimAttr parDim : parDimsPair.first) {
3457 if ((parDim.isThreadX() ||
3458 (arrayAccum && storageHasThreadX(privateLocal, computeRegion))) &&
3459 canUseStackAlloca(baseTy, loc,
options.maxThreadPrivateStack)) {
3460 Value alloca = memref::AllocaOp::create(rewriter, loc, baseTy);
3461 recordThreadPrivate(privateLocal);
3463 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
3464 arrayAccum.getReductionOperator(), baseTy.getElementType(), loc);
3467 initPerThreadArrayAccum(rewriter, loc, alloca, baseTy, *kind);
3470 privateLocal.getType());
3471 mapping.
map(privateLocal.getResult(), mem);
3475 if (parDimsPair.first.empty()) {
3479 mlir::acc::GPUParallelDimAttr::blockXDim(privateLocal.getContext()));
3481 for (mlir::acc::GPUParallelDimAttr parDim : parDimsPair.first) {
3482 gpu::Processor gpuProc = parDim.getProcessor();
3483 Value gpuSize = getGPUSizeFor(gpuProc);
3484 viewDynSizes.push_back(gpuSize);
3485 viewShape.push_back(ShapedType::kDynamic);
3486 subviewOffset.push_back(getGPUThreadIdFor(gpuProc));
3490 SmallVector<Value> innerDynSizes =
3491 resolvePrivateLocalDynamicExtents(privateLocal);
3493 unsigned dynIdx = 0;
3494 for (
auto innerDim : baseTy.getShape()) {
3496 viewShape.push_back(innerDim);
3497 subviewShape.push_back(innerDim);
3498 if (innerDim == ShapedType::kDynamic) {
3499 assert(dynIdx < innerDynSizes.size() &&
3500 "not enough dynamic sizes for inner dimensions");
3501 viewDynSizes.push_back(innerDynSizes[dynIdx]);
3502 subviewSizes.push_back(innerDynSizes[dynIdx]);
3505 subviewSizes.push_back(rewriter.
getIndexAttr(innerDim));
3511 for (
auto innerDimIt = baseTy.getShape().rbegin();
3512 innerDimIt != baseTy.getShape().rend(); ++innerDimIt) {
3513 int64_t innerDim = *innerDimIt;
3514 subviewStrides.insert(subviewStrides.begin(), stride);
3515 if (innerDim == ShapedType::kDynamic)
3516 stride = ShapedType::kDynamic;
3517 if (stride != ShapedType::kDynamic)
3524 MemRefType viewType = MemRefType::get(viewShape, baseTy.getElementType());
3525 auto view = memref::ViewOp::create(rewriter, loc, viewType, memBuffer,
3526 c0.getResult(), viewDynSizes);
3529 StridedLayoutAttr stridedLayout = StridedLayoutAttr::get(
3530 computeRegion->getContext(), ShapedType::kDynamic, subviewStrides);
3531 MemRefType subviewType =
3532 MemRefType::get(subviewShape, baseTy.getElementType(), stridedLayout);
3533 SmallVector<OpFoldResult> ones(viewType.getRank(), rewriter.
getIndexAttr(1));
3534 Value subview = memref::SubViewOp::create(rewriter, loc, subviewType, view,
3535 subviewOffset, subviewSizes, ones);
3540 memref::ExtractStridedMetadataOp::create(rewriter, loc, subview);
3542 rewriter, loc, getElementSizeInBytes(loc, baseTy.getElementType()));
3544 arith::MulIOp::create(rewriter, loc, metadata.getOffset(), elementBytes);
3545 Value privateView = memref::ViewOp::create(rewriter, loc, baseTy, memBuffer,
3546 byteOffset, innerDynSizes);
3548 privateLocal.getType());
3549 mapping.
map(privateLocal.getResult(),
result);
3553template <
typename LoopOp>
3554void ACCCGToGPULowering::processSeqLoop(LoopOp loopOp) {
3558 LLVM_DEBUG(llvm::dbgs() <<
"processing seq loop: ";
3559 loopOp->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3560 llvm::SmallPtrSet<Operation *, 4> preProcessedPrivateLocals;
3561 ModuleOp module = computeRegion->getParentOfType<ModuleOp>();
3562 for (
auto &bodyOp : loopOp.getBody()->getOperations()) {
3563 if (acc::PrivateLocalOp privateLocal =
3564 dyn_cast<acc::PrivateLocalOp>(&bodyOp)) {
3565 acc::PrivateType privTy =
3566 cast<acc::PrivateType>(privateLocal.getPrivatized().getType());
3568 if (
auto copies = isEligibleForSharedMemory(privateLocal, baseTy)) {
3569 processPrivateLocal(privateLocal,
copies);
3570 preProcessedPrivateLocals.insert(privateLocal.getOperation());
3578 &newLoop.getRegion(), newLoop.getRegion().begin(),
3579 loopOp.getBody()->getArgumentTypes(),
3580 SmallVector<Location>(loopOp.getBody()->getArgumentTypes().size(),
3586 assert(blockArgs.size() &&
"expected block arguments for loop");
3587 mapping.
map(blockArgs, newLoop.getBody()->getArguments());
3593 if (needsInLoopReuseBarrier(loopOp.getOperation()) &&
3595 emitGPUBarrierWorkgroup(rewriter, loopOp->getLoc());
3597 for (
auto &bodyOp : loopOp.getBody()->getOperations()) {
3598 if (preProcessedPrivateLocals.contains(&bodyOp))
3603 mapping.
map(loopOp.getResults(), newLoop.getResults());
3608 if (hasTrailingSideEffectSiblings(loopOp.getOperation()))
3609 deferredBarrierSeqLoops.push_back(loopOp.getOperation());
3611 createBarrierAfterSeqLoop(loopOp.getOperation());
3614void ACCCGToGPULowering::flushDeferredBarriersBefore(Operation *beforeOp) {
3616 SmallVector<Operation *, 4> toFlush;
3617 for (Operation *loopOp : deferredBarrierSeqLoops)
3618 if (loopOp->getBlock() == block && loopOp->isBeforeInBlock(beforeOp))
3619 toFlush.push_back(loopOp);
3620 if (toFlush.empty())
3624 for (Operation *loopOp : toFlush)
3625 createBarrierAfterSeqLoop(loopOp);
3626 deferredBarrierSeqLoops.erase(
3627 std::remove_if(deferredBarrierSeqLoops.begin(),
3628 deferredBarrierSeqLoops.end(),
3629 [&](Operation *loopOp) {
3630 return loopOp->getBlock() == block &&
3631 loopOp->isBeforeInBlock(beforeOp);
3633 deferredBarrierSeqLoops.end());
3638void ACCCGToGPULowering::processParallelOp(scf::ParallelOp parallelOp) {
3639 LLVM_DEBUG(llvm::dbgs() <<
"processing par loop: ";
3640 parallelOp->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
3642 "requires parallel dimensions attribute");
3643 mlir::acc::GPUParallelDimsAttr pDimsAttr =
3647 assert(pDimsAttr.getArray().size() == 1 &&
3648 "expected a single par dim in acc-cg-to-gpu");
3649 assert(parallelOp.getInductionVars().size() == 1 &&
3650 "expected a single induction variable in acc-cg-to-gpu");
3652 mlir::acc::GPUParallelDimAttr parDim = pDimsAttr.getArray().front();
3654 bool savedGridStrideFlag = insideAccumulateGridStride;
3655 Value savedReductionBuf = reductionSharedBuf;
3656 if (parDim.isThreadX()) {
3658 parallelOp.getBody()->walk([&](acc::ReductionAccumulateOp accOp) {
3659 bool hasBlockDim =
false;
3660 bool hasThreadDim =
false;
3661 for (
auto d : accOp.getParDims().getArray()) {
3664 if (d.isThreadX() || d.isThreadY())
3665 hasThreadDim =
true;
3667 if (hasThreadDim && !hasBlockDim) {
3674 insideAccumulateGridStride =
true;
3678 auto processLoopBody = [&]() {
3680 for (
auto &bodyOp : parallelOp.getBody()->getOperations()) {
3681 if (bodyOp.hasTrait<OpTrait::IsTerminator>()) {
3683 flushDeferredBarriersBefore(&bodyOp);
3691 if (parDim.isSeq()) {
3692 LLVM_DEBUG(llvm::dbgs() <<
"loop: parDim: " << parDim <<
" as gpu seq\n");
3698 bool needsAtomicReduction =
false;
3699 bool hasAccumulateSibling =
false;
3700 if (scf::ParallelOp parentPar =
3701 parallelOp->getParentOfType<scf::ParallelOp>()) {
3702 if (mlir::acc::GPUParallelDimsAttr parentDims =
3704 parentDims && llvm::any_of(parentDims.getArray(),
3705 [](
auto d) { return d.isThreadX(); })) {
3706 for (
auto &op : parentPar.getBody()->getOperations()) {
3707 if (acc::ReductionAccumulateOp acc =
3708 dyn_cast<acc::ReductionAccumulateOp>(op)) {
3709 bool hasBlockDim =
false;
3710 bool hasThreadDim =
false;
3711 for (
auto d : acc.getParDims().getArray()) {
3714 if (d.isThreadX() || d.isThreadY())
3715 hasThreadDim =
true;
3717 if (hasThreadDim && !hasBlockDim)
3718 hasAccumulateSibling =
true;
3723 if (insideAccumulateGridStride || hasAccumulateSibling) {
3724 for (
auto launchArg : computeRegion.getLaunchArgs()) {
3725 if (acc::ParWidthOp pw = launchArg.getDefiningOp<acc::ParWidthOp>()) {
3726 if (pw.getParDim().isThreadX()) {
3728 needsAtomicReduction = (*cval >=
options.subgroupSize);
3730 needsAtomicReduction =
true;
3736 if (needsAtomicReduction && !reductionSharedBuf) {
3738 parallelOp.getBody()->walk([&](acc::ReductionAccumulateOp accOp) {
3739 Type t = accOp.getValue().getType();
3740 if (isa<FloatType, IntegerType>(t))
3745 needsAtomicReduction =
false;
3747 if (needsAtomicReduction && !reductionSharedBuf) {
3748 Location seqLoc = parallelOp->getLoc();
3749 gpu::AddressSpaceAttr workgroupAS = gpu::AddressSpaceAttr::get(
3750 computeRegion->getContext(),
3751 gpu::GPUDialect::getWorkgroupAddressSpace());
3753 parallelOp.getBody()->
walk([&](acc::ReductionAccumulateOp accOp) {
3754 Type t = accOp.getValue().getType();
3755 if (isa<FloatType, IntegerType>(t))
3759 assert(elemTy &&
"expected scalar reduction element type");
3761 MemRefType bufTy = MemRefType::get({
options.subgroupSize}, elemTy,
3762 AffineMap{}, workgroupAS);
3763 reductionSharedBuf = acc::GPUSharedMemoryOp::create(
3767 Value tidY = getThreadId(seqLoc, gpu::Dimension::y);
3769 if (isa<FloatType>(elemTy)) {
3770 identity = arith::ConstantOp::create(
3771 rewriter, seqLoc, elemTy, rewriter.
getFloatAttr(elemTy, 0.0));
3775 memref::StoreOp::create(rewriter, seqLoc, identity, reductionSharedBuf,
3777 createPerRowBarrier(seqLoc);
3779 processSeqLoop(parallelOp);
3780 loopReductions.push_back(parallelOp);
3782 LLVM_DEBUG(llvm::dbgs()
3783 <<
"processing loop: parDim: " << parDim <<
" as gpu par\n");
3787 Value gpuThreadId = getGPUThreadIdFor(parDim.getProcessor());
3788 mapping.
map(parallelOp.getInductionVars()[0], gpuThreadId);
3796 if (needsBarrierAfterThreadLoop(parallelOp))
3797 createBarrier(parallelOp->getLoc(), pDimsAttr);
3802 llvm::for_each(parallelOp.getResults(), [&](Value v) {
3803 Type valTy = v.getType();
3804 TypedAttr zeroAttr = rewriter.getZeroAttr(valTy);
3805 auto zero = arith::ConstantOp::create(rewriter, parallelOp->getLoc(),
3807 mapping.map(v, zero);
3809 loopReductions.push_back(parallelOp);
3811 insideAccumulateGridStride = savedGridStrideFlag;
3812 if (!insideAccumulateGridStride && !savedReductionBuf)
3813 reductionSharedBuf = Value();
3817static gpu::AllReduceOperation
3818getAllReduceOperation(arith::AtomicRMWKind kind) {
3820 case arith::AtomicRMWKind::addf:
3821 case arith::AtomicRMWKind::addi:
3822 return gpu::AllReduceOperation::ADD;
3823 case arith::AtomicRMWKind::mulf:
3824 case arith::AtomicRMWKind::muli:
3825 return gpu::AllReduceOperation::MUL;
3826 case arith::AtomicRMWKind::minu:
3827 return gpu::AllReduceOperation::MINUI;
3828 case arith::AtomicRMWKind::mins:
3829 return gpu::AllReduceOperation::MINSI;
3830 case arith::AtomicRMWKind::minnumf:
3831 return gpu::AllReduceOperation::MINNUMF;
3832 case arith::AtomicRMWKind::maxu:
3833 return gpu::AllReduceOperation::MAXUI;
3834 case arith::AtomicRMWKind::maxs:
3835 return gpu::AllReduceOperation::MAXSI;
3836 case arith::AtomicRMWKind::maxnumf:
3837 return gpu::AllReduceOperation::MAXNUMF;
3838 case arith::AtomicRMWKind::ori:
3839 return gpu::AllReduceOperation::OR;
3840 case arith::AtomicRMWKind::andi:
3841 return gpu::AllReduceOperation::AND;
3842 case arith::AtomicRMWKind::xori:
3843 return gpu::AllReduceOperation::XOR;
3844 case arith::AtomicRMWKind::minimumf:
3845 return gpu::AllReduceOperation::MINIMUMF;
3846 case arith::AtomicRMWKind::maximumf:
3847 return gpu::AllReduceOperation::MAXIMUMF;
3848 case arith::AtomicRMWKind::assign:
3851 llvm_unreachable(
"unsupported atomic kind");
3854void ACCCGToGPULowering::constructAtomicAccumulation(
3856 arith::AtomicRMWKind kind) {
3858 "cannot lower atomic accumulation on an stack variable");
3870 MemRefType memrefTy = cast<MemRefType>(memref.
getType());
3871 unsigned rank = memrefTy.getRank();
3872 assert(
indices.size() == rank &&
"expected one index per memref dimension");
3873 SmallVector<OpFoldResult> offsets(
indices.begin(),
indices.end());
3874 SmallVector<OpFoldResult> sizes(rank, rewriter.
getIndexAttr(1));
3875 SmallVector<OpFoldResult> strides(rank, rewriter.
getIndexAttr(1));
3876 target = memref::SubViewOp::create(rewriter, loc, memref, offsets, sizes,
3880 auto atomicUpdateOp =
3881 acc::AtomicUpdateOp::create(rewriter, loc,
target, Value());
3882 Region ®ion = atomicUpdateOp->getRegion(0);
3886 Value reductionExpr =
3888 acc::YieldOp::create(rewriter, loc, reductionExpr);
3892void ACCCGToGPULowering::createGPUAllReduceOp(
3893 Location loc, Value input, Value memref, arith::AtomicRMWKind kind,
3895 bool isPerThreadPrivateTarget) {
3896 gpu::AllReduceOperationAttr attr = gpu::AllReduceOperationAttr::get(
3897 computeRegion->getContext(), getAllReduceOperation(kind));
3898 auto emitAllReduce = [&](Value operand) -> Value {
3900 gpu::AllReduceOp::create(rewriter, loc, operand, attr,
true);
3906 if (
auto complexType = dyn_cast<ComplexType>(input.
getType())) {
3911 if (kind != arith::AtomicRMWKind::addf) {
3913 llvm::raw_string_ostream os(msg);
3914 os <<
"reduction: " << arith::stringifyAtomicRMWKind(kind) <<
" of "
3915 << complexType <<
" across threads";
3916 (void)accSupport.
emitNYI(loc, os.str());
3920 Type elementType = complexType.getElementType();
3922 emitAllReduce(complex::ReOp::create(rewriter, loc, elementType, input));
3924 emitAllReduce(complex::ImOp::create(rewriter, loc, elementType, input));
3925 reducedValue = complex::CreateOp::create(rewriter, loc, complexType,
3926 realResult, imagResult);
3928 reducedValue = emitAllReduce(input);
3936 SmallVector<mlir::acc::GPUParallelDimAttr> inactiveParDims;
3937 MLIRContext *ctx = computeRegion->getContext();
3938 bool hasThreadX =
false;
3939 for (
auto parDim : parDimsAttr.getArray()) {
3940 if (parDim.isAnyBlock())
3942 if (parDim.isThreadX())
3944 if (computeRegion.getLaunchArg(parDim) ||
3946 inactiveParDims.push_back(parDim);
3952 if (!hasThreadX && !isSingleThreadWorkerLaunch())
3953 inactiveParDims.push_back(mlir::acc::GPUParallelDimAttr::threadXDim(ctx));
3954 Value predicate = emitPredicate(loc, inactiveParDims);
3961 bool isPerThreadPrivate = isPerThreadPrivateTarget ||
3962 isa_and_nonnull<memref::AllocaOp>(
3963 unwrapMemRefConversion(memref).getDefiningOp());
3966 if (predicate && !isPerThreadPrivate) {
3968 scf::IfOp::create(rewriter, loc, predicate,
false);
3969 Region &thenRegion = ifOp.getThenRegion();
3973 memref::StoreOp::create(rewriter, loc, reducedValue, memref,
indices);
3974 if (predicate && !isPerThreadPrivate)
3979 reductionAccumValue[memref] = reducedValue;
3982void ACCCGToGPULowering::postprocessAccumulateOp(
3983 acc::ReductionAccumulateOp op) {
3984 Location loc = op->getLoc();
3992 bool hasThreadDim =
false;
3993 SmallVector<mlir::acc::GPUParallelDimAttr> threadParDims;
3994 for (
auto parDim : op.getParDims().getArray()) {
3995 if (!parDim.isAnyBlock()) {
3996 hasThreadDim =
true;
3997 threadParDims.push_back(parDim);
4001 std::optional<arith::AtomicRMWKind> kind;
4003 FailureOr<arith::AtomicRMWKind> kindOr = getReductionKind(
4004 op.getReductionOperator(), op.getValue().getType(), loc);
4010 if (hasThreadDim && reductionSharedBuf &&
4011 op.getValue().getType() ==
4012 cast<MemRefType>(reductionSharedBuf.getType()).getElementType()) {
4013 Value val = op.getValue();
4014 Value mem = op.getMemref();
4015 Value tidY = getThreadId(loc, gpu::Dimension::y);
4016 memref::AtomicRMWOp::create(rewriter, loc, *kind, val, reductionSharedBuf,
4018 createPerRowBarrier(loc);
4020 memref::LoadOp::create(rewriter, loc, reductionSharedBuf, tidY);
4021 memref::StoreOp::create(rewriter, loc,
result, mem);
4022 reductionAccumValue[mem] =
result;
4023 }
else if (hasThreadDim) {
4024 createGPUAllReduceOp(loc, op.getValue(), op.getMemref(), *kind,
4036 bool isPerThreadPrivate = isa_and_nonnull<memref::AllocaOp>(
4037 unwrapMemRefConversion(mem).getDefiningOp());
4038 if (!isPerThreadPrivate) {
4039 SmallVector<mlir::acc::GPUParallelDimAttr> predDims;
4040 for (
auto parDim : computeRegion.getLaunchParDims())
4041 if (!parDim.isAnyBlock())
4042 predDims.push_back(parDim);
4043 if (predDims.empty()) {
4044 predDims.push_back(mlir::acc::GPUParallelDimAttr::threadXDim(
4045 computeRegion->getContext()));
4047 Value predicate = emitPredicate(loc, predDims);
4049 scf::IfOp::create(rewriter, loc, predicate,
false);
4051 memref::StoreOp::create(rewriter, loc, val, mem);
4054 memref::StoreOp::create(rewriter, loc, val, mem);
4062void ACCCGToGPULowering::postprocessLoopReduction(scf::ParallelOp parLoop) {
4063 if (parLoop.getNumReductions() == 0)
4066 for (
unsigned i = 0; i < parLoop.getNumResults(); ++i) {
4067 for (Operation *user :
4069 if (acc::ReductionAccumulateOp accumulateOp =
4070 dyn_cast<acc::ReductionAccumulateOp>(user)) {
4071 postprocessAccumulateOp(accumulateOp);
4077void ACCCGToGPULowering::processExecuteRegion(scf::ExecuteRegionOp op) {
4078 LLVM_DEBUG(llvm::dbgs() <<
"processing execute region op: ";
4079 op->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
4080 Location loc = op->getLoc();
4081 auto types = op.getResultTypes();
4082 Region &oldRegion = op.getRegion();
4084 auto executeRegionOp = scf::ExecuteRegionOp::create(rewriter, loc, types);
4085 Region ®ion = executeRegionOp.getRegion();
4089 llvm::DenseMap<Block *, Block *> blockMap;
4090 blockMap[&oldRegion.
front()] = ®ion.
front();
4094 for (
auto &oldBlock : llvm::drop_begin(oldRegion.
getBlocks())) {
4095 TypeRange argTypes = oldBlock.getArgumentTypes();
4096 size_t numArgs = argTypes.size();
4099 SmallVector<Location>(numArgs, loc));
4100 blockMap[&oldBlock] = newBlock;
4107 for (
auto [oldBlock, newBlock] :
4109 OpBuilder::InsertionGuard blockGuard(rewriter);
4111 for (
auto &bodyOp : oldBlock.getOperations()) {
4113 if (bodyOp.hasTrait<OpTrait::IsTerminator>())
4119 Operation *oldTerminator = oldBlock.getTerminator();
4121 Operation *newTerminator = rewriter.
clone(*oldTerminator, mapping);
4126 Block *newDest = blockMap.lookup(oldDest);
4127 assert(newDest &&
"Successor block must be in blockMap");
4131 mapping.
map(op->getResults(), executeRegionOp->getResults());
4135void ACCCGToGPULowering::processAccumulateOp(acc::ReductionAccumulateOp op) {
4136 LLVM_DEBUG(llvm::dbgs() <<
"processing accumulate op: " << *op <<
"\n");
4137 Value accumulateValue = op.getValue();
4138 if (reductionSharedBuf &&
4140 cast<MemRefType>(reductionSharedBuf.getType()).getElementType()) {
4141 Location loc = op->getLoc();
4142 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
4143 op.getReductionOperator(), accumulateValue.
getType(), loc);
4148 Value tidY = getThreadId(loc, gpu::Dimension::y);
4149 memref::AtomicRMWOp::create(rewriter, loc, *kind, mappedValue,
4151 createPerRowBarrier(loc);
4153 memref::LoadOp::create(rewriter, loc, reductionSharedBuf, tidY);
4154 memref::StoreOp::create(rewriter, loc,
result, memref);
4155 reductionAccumValue[memref] =
result;
4159 Operation *newOp = rewriter.
clone(*op, mapping);
4161 }
else if (isRedundantChainAccumulate(op)) {
4167 LLVM_DEBUG(llvm::dbgs() <<
" skipped: redundant chain accumulate\n");
4168 gpu::BarrierOp::create(rewriter, op->getLoc());
4172 FailureOr<arith::AtomicRMWKind> kind = getReductionKind(
4173 op.getReductionOperator(), accumulateValue.
getType(), op.getLoc());
4176 createGPUAllReduceOp(op->getLoc(), mappedValue, memref, *kind,
4185static bool isThreadVarying(Value v, ArrayRef<Value> threadIds,
4187 if (!v || !visited.insert(v).second)
4189 if (llvm::is_contained(threadIds, v))
4191 if (
auto arg = dyn_cast<BlockArgument>(v)) {
4193 unsigned dim = arg.getArgNumber();
4194 if (
auto loop = dyn_cast<scf::ParallelOp>(owner)) {
4195 if (dim >= loop.getLowerBound().size())
4197 return isThreadVarying(loop.getLowerBound()[dim], threadIds, visited) ||
4198 isThreadVarying(loop.getStep()[dim], threadIds, visited);
4200 if (
auto loop = dyn_cast<scf::ForOp>(owner))
4202 (isThreadVarying(loop.getLowerBound(), threadIds, visited) ||
4203 isThreadVarying(loop.getStep(), threadIds, visited));
4209 if (isa<gpu::ThreadIdOp, gpu::LaneIdOp>(def))
4211 return llvm::any_of(def->
getOperands(), [&](Value o) {
4212 return isThreadVarying(o, threadIds, visited);
4217static Value accumulatorRoot(Value v) {
4219 if (
auto cast = dyn_cast<memref::MemorySpaceCastOp>(op)) {
4220 v = cast.getSource();
4223 if (
auto viewLike = dyn_cast<ViewLikeOpInterface>(op)) {
4224 if (isa<MemRefType>(viewLike.getViewSource().getType())) {
4225 v = viewLike.getViewSource();
4236static Value matchAccumulatorUpdate(memref::StoreOp store, Value accum) {
4237 if (accumulatorRoot(store.getMemRef()) != accum)
4239 Operation *
combine = store.getValueToStore().getDefiningOp();
4240 if (!combine ||
combine->getNumOperands() != 2)
4242 for (
unsigned i = 0; i != 2; ++i) {
4243 auto load =
combine->getOperand(i).getDefiningOp<memref::LoadOp>();
4244 if (!
load || accumulatorRoot(
load.getMemRef()) != accum)
4246 if (!llvm::equal(
load.getIndices(), store.getIndices()))
4248 return combine->getOperand(1 - i);
4256static void atomicizeSharedAccumulatorUpdates(Value accum,
4257 arith::AtomicRMWKind kind,
4258 ArrayRef<Value> threadIds,
4259 RewriterBase &rewriter) {
4260 OpBuilder::InsertionGuard guard(rewriter);
4261 SmallVector<memref::StoreOp> stores;
4262 SmallVector<Value> worklist{accum};
4264 while (!worklist.empty()) {
4265 Value cur = worklist.pop_back_val();
4266 if (!seen.insert(cur).second)
4268 for (Operation *user : cur.
getUsers()) {
4269 if (
auto store = dyn_cast<memref::StoreOp>(user))
4270 stores.push_back(store);
4271 else if (isa<ViewLikeOpInterface, memref::MemorySpaceCastOp>(user))
4272 llvm::append_range(worklist, user->getResults());
4276 for (memref::StoreOp store : stores) {
4277 Value contribution = matchAccumulatorUpdate(store, accum);
4282 if (llvm::any_of(store.getIndices(), [&](Value idx) {
4283 DenseSet<Value> visited;
4284 return isThreadVarying(idx, threadIds, visited);
4287 Operation *
combine = store.getValueToStore().getDefiningOp();
4289 memref::AtomicRMWOp::create(rewriter, store.getLoc(), kind, contribution,
4290 store.getMemRef(), store.getIndices());
4292 if (combine &&
combine->use_empty())
4297void ACCCGToGPULowering::processAccumulateArrayOp(
4298 acc::ReductionAccumulateArrayOp op) {
4299 LLVM_DEBUG(llvm::dbgs() <<
"processing accumulate array op: " << *op <<
"\n");
4300 Location loc = op.getLoc();
4303 MemRefType memrefTy = dyn_cast<MemRefType>(memref.
getType());
4305 (void)accSupport.
emitNYI(loc,
"reduction: non-MemRefTy accumulate array");
4308 FailureOr<arith::AtomicRMWKind> kindOr = getReductionKind(
4309 op.getReductionOperator(), memrefTy.getElementType(), loc);
4312 arith::AtomicRMWKind kind = *kindOr;
4317 .getDefiningOp<acc::DataBoundsOp>();
4318 assert(boundsOp &&
"expected acc.bounds defining op for array accumulate");
4319 auto eraseDeadBounds = [&] {
4320 if (boundsOp->use_empty())
4324 auto isUnitExtent = [&](mlir::acc::GPUParallelDimAttr parDim) {
4325 std::optional<uint64_t> width =
4326 computeRegion.getKnownConstantLaunchArg(parDim);
4327 return width && *width == 1;
4331 bool hasThreadDim =
false;
4332 for (
auto pd : op.getParDims().getArray()) {
4333 if (isUnitExtent(pd))
4335 hasThreadDim |= pd.isAnyThread();
4341 if (!hasThreadDim) {
4354 auto storageIsThreadXPrivate = [&](Value v) ->
bool {
4355 acc::PrivateLocalOp privateLocal = getPrivateLocalForMemref(v);
4356 GPUParallelDimsAttr dims =
4357 privateLocal ? getPrivateParDims(privateLocal, computeRegion)
4358 : GPUParallelDimsAttr();
4360 if (Operation *root = unwrapMemRefConversion(v).getDefiningOp())
4364 llvm::any_of(dims.getArray(), [](
auto d) { return d.isThreadX(); });
4366 Operation *rootOp = unwrapMemRefConversion(memref).getDefiningOp();
4367 bool isSharedStorage = isa_and_nonnull<memref::AllocOp>(rootOp) ||
4368 isa_and_nonnull<acc::GPUSharedMemoryOp>(rootOp);
4369 if (
auto addrSpace = dyn_cast_if_present<gpu::AddressSpaceAttr>(
4370 memrefTy.getMemorySpace())) {
4372 addrSpace.getValue() == gpu::GPUDialect::getWorkgroupAddressSpace();
4374 bool isPerThreadPrivate =
4375 !isSharedStorage && storageIsThreadXPrivate(op.getMemref()) &&
4376 (memrefTy.hasStaticShape()
4377 ? canUseStackAlloca(memrefTy, loc,
options.maxThreadPrivateStack)
4378 : llvm::any_of(op.getParDims().getArray(),
4379 [](mlir::acc::GPUParallelDimAttr d) {
4380 return d.isThreadX();
4382 if (!isPerThreadPrivate) {
4387 SmallVector<Value> threadIds;
4388 if (Value xId = getGPUThreadIdFor(gpu::Processor::ThreadX))
4389 threadIds.push_back(xId);
4390 if (Value yId = getGPUThreadIdFor(gpu::Processor::ThreadY))
4391 threadIds.push_back(yId);
4392 if (Value zId = getGPUThreadIdFor(gpu::Processor::ThreadZ))
4393 threadIds.push_back(zId);
4394 atomicizeSharedAccumulatorUpdates(accumulatorRoot(memref), kind, threadIds,
4401 auto toIndex = [&](Value v) -> Value {
4404 return arith::IndexCastOp::create(rewriter, loc, rewriter.
getIndexType(),
4411 boundsOp.getLowerbound() ? toIndex(boundsOp.getLowerbound()) : zero;
4412 Value step = boundsOp.getStride() ? toIndex(boundsOp.getStride()) : one;
4417 if (boundsOp.getExtent()) {
4418 Value span = arith::MulIOp::create(rewriter, loc,
4419 toIndex(boundsOp.getExtent()), step);
4420 ub = arith::AddIOp::create(rewriter, loc, lb, span);
4422 assert(boundsOp.getUpperbound() &&
4423 "acc.bounds must specify an extent or upperbound");
4424 ub = arith::AddIOp::create(rewriter, loc, toIndex(boundsOp.getUpperbound()),
4429 auto forOp = scf::ForOp::create(rewriter, loc, lb, ub, step);
4431 OpBuilder::InsertionGuard guard(rewriter);
4433 Value iv = forOp.getInductionVar();
4434 SmallVector<Value>
indices{iv};
4435 if (memrefTy.getRank() > 1) {
4436 indices.resize(memrefTy.getRank());
4437 Value linearIndex = iv;
4438 for (int64_t dim = memrefTy.getRank() - 1; dim >= 0; --dim) {
4440 memrefTy.isDynamicDim(dim)
4441 ? memref::DimOp::create(rewriter, loc, memref, dim).getResult()
4443 memrefTy.getDimSize(dim))
4446 arith::RemUIOp::create(rewriter, loc, linearIndex, dimSize);
4449 arith::DivUIOp::create(rewriter, loc, linearIndex, dimSize);
4452 Value elem = memref::LoadOp::create(rewriter, loc, memref,
indices);
4453 createGPUAllReduceOp(loc, elem, memref, kind, op.getParDims(),
indices,
4460void ACCCGToGPULowering::processReductionOp(acc::ReductionInitOp op) {
4462 op.getRegion().walk<WalkOrder::PreOrder>([&](Operation *innerOp) {
4463 if (acc::YieldOp yieldOp = dyn_cast<acc::YieldOp>(innerOp)) {
4464 op.getResult().replaceAllUsesWith(mapping.
lookup(yieldOp.getOperand(0)));
4467 if (innerOp->getNumRegions() > 0) {
4471 rewriter.
clone(*innerOp, mapping);
4476void ACCCGToGPULowering::processReductionCombineOp(acc::ReductionCombineOp op) {
4477 LLVM_DEBUG(llvm::dbgs() <<
"processing reduction combine op: ";
4478 op->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
4479 Location loc = op.getLoc();
4480 MemRefType memrefType = dyn_cast<MemRefType>(op.getSrcMemref().getType());
4481 assert(memrefType &&
"expected memref type for reduction combine op");
4482 assert(memrefType.getRank() == 0 &&
4483 "expected scalar memref type for reduction combine op");
4484 Type elTy = memrefType.getElementType();
4485 FailureOr<arith::AtomicRMWKind> kindOr =
4486 getReductionKind(op.getReductionOperator(), elTy, loc);
4489 arith::AtomicRMWKind kind = *kindOr;
4500 bool destIsPerThreadPrivate = isa_and_nonnull<memref::AllocaOp>(
4501 unwrapMemRefConversion(destMemref).getDefiningOp());
4503 SmallVector<mlir::acc::GPUParallelDimAttr> parDims =
4509 bool destIsGridShared =
4510 !destIsPerThreadPrivate &&
4511 (llvm::any_of(parDims,
4512 [](mlir::acc::GPUParallelDimAttr parDim) {
4513 return parDim.isAnyBlock();
4515 computeRegion.launchesMultipleBlocks());
4516 if (destIsGridShared) {
4522 memref::LoadOp::create(rewriter, loc, srcMemref,
ValueRange{});
4523 pendingCombineReloads.push_back({srcMemref, srcLoad});
4524 constructAtomicAccumulation(loc, destMemref, {}, srcLoad, kind);
4530 auto srcLoad = memref::LoadOp::create(rewriter, loc, srcMemref,
ValueRange{});
4532 memref::LoadOp::create(rewriter, loc, destMemref,
ValueRange{});
4534 memref::StoreOp::create(rewriter, loc, combine, destMemref,
ValueRange{});
4537void ACCCGToGPULowering::processCombineRegionOp(
4538 acc::ReductionCombineRegionOp op) {
4539 LLVM_DEBUG(llvm::dbgs() <<
"processing combine region op: ";
4540 op->print(llvm::dbgs()); llvm::dbgs() <<
"\n");
4544 bool destIsPerThreadPrivate = isa_and_nonnull<memref::AllocaOp>(
4547 SmallVector<mlir::acc::GPUParallelDimAttr> parDims =
4549 for (
auto parDim : parDims) {
4550 if (parDim.isAnyBlock() && !destIsPerThreadPrivate) {
4554 for (Operation *user : op.getSrcVar().getUsers()) {
4555 if (acc::ReductionAccumulateOp accumulateOp =
4556 dyn_cast<acc::ReductionAccumulateOp>(user)) {
4557 Location loc = accumulateOp.getLoc();
4558 FailureOr<arith::AtomicRMWKind> kind =
4559 getReductionKind(accumulateOp.getReductionOperator(),
4560 accumulateOp.getValue().getType(), loc);
4565 auto reductionLoad =
4566 memref::LoadOp::create(rewriter, loc, srcMemref,
ValueRange{});
4567 pendingCombineReloads.push_back({srcMemref, reductionLoad});
4568 constructAtomicAccumulation(loc,
4570 {}, reductionLoad, *kind);
4578 MemRefType memrefTy = cast<MemRefType>(privateMemref.
getType());
4579 if (isa<ComplexType>(memrefTy.getElementType())) {
4580 Location loc = op.getLoc();
4581 Value reductionResult =
4582 memref::LoadOp::create(rewriter, loc, privateMemref,
ValueRange{});
4583 arith::AtomicRMWKind kind = arith::AtomicRMWKind::addf;
4584 op.getRegion().walk([&](Operation *innerOp) {
4585 if (isa<complex::MulOp>(innerOp))
4586 kind = arith::AtomicRMWKind::mulf;
4588 constructAtomicAccumulation(loc,
4590 {}, reductionResult, kind);
4595 op.getRegion().walk<WalkOrder::PreOrder>([&](Operation *innerOp) {
4596 if (acc::YieldOp yieldOp = dyn_cast<acc::YieldOp>(innerOp))
4598 if (innerOp->getNumRegions() > 0) {
4602 rewriter.
clone(*innerOp, mapping);
4607void ACCCGToGPULowering::processGenericOp(Operation *op) {
4610 LLVM_DEBUG(llvm::dbgs() <<
"processing generic op, cloning: ";
4611 op->
print(llvm::dbgs()); llvm::dbgs() <<
"\n");
4612 Operation *newOp = rewriter.
clone(*op, mapping);
4617void ACCCGToGPULowering::processGenericOpWithRegions(Operation *op) {
4619 LLVM_DEBUG(llvm::dbgs() <<
"processing generic op with regions: ";
4620 op->
print(llvm::dbgs()); llvm::dbgs() <<
"\n");
4626 for (
auto [oldRegion, newRegion] :
4627 llvm::zip(op->
getRegions(), newOp->getRegions())) {
4629 for (
auto &oldBlock : oldRegion.
getBlocks()) {
4630 TypeRange argTypes = oldBlock.getArgumentTypes();
4631 size_t numArgs = argTypes.size();
4634 rewriter.
createBlock(&newRegion, newRegion.end(), argTypes,
4635 SmallVector<Location>(numArgs, op->
getLoc()));
4641 for (
auto &innerOp : oldBlock.getOperations()) {
4642 OpBuilder::InsertionGuard guard(rewriter);
4644 processOp(&innerOp);
4655void ACCCGToGPULowering::processOp(Operation *op) {
4656 if (isDeferredBarrierFlushPoint(op))
4657 flushDeferredBarriersBefore(op);
4661 scf::ParallelOp parallelOp = cast<scf::ParallelOp>(op);
4662 processParallelOp(parallelOp);
4663 }
else if (scf::ForOp seqLoop = dyn_cast<scf::ForOp>(op)) {
4664 processSeqLoop(seqLoop);
4665 }
else if (acc::PrivatizeOp privatize = dyn_cast<acc::PrivatizeOp>(op)) {
4666 processPrivatize(privatize);
4667 }
else if (acc::PrivateLocalOp privateLocal =
4668 dyn_cast<acc::PrivateLocalOp>(op)) {
4669 processPrivateLocal(privateLocal);
4670 }
else if (acc::PredicateRegionOp predicateRegionOp =
4671 dyn_cast<acc::PredicateRegionOp>(op)) {
4672 processPredicateRegion(predicateRegionOp);
4673 }
else if (acc::ReductionAccumulateOp accumulateOp =
4674 dyn_cast<acc::ReductionAccumulateOp>(op)) {
4675 processAccumulateOp(accumulateOp);
4676 }
else if (
auto accumulateArrayOp =
4677 dyn_cast<acc::ReductionAccumulateArrayOp>(op)) {
4678 processAccumulateArrayOp(accumulateArrayOp);
4679 }
else if (acc::ReductionInitOp reductionInitOp =
4680 dyn_cast<acc::ReductionInitOp>(op)) {
4681 processReductionOp(reductionInitOp);
4682 }
else if (acc::ReductionCombineOp reductionCombineOp =
4683 dyn_cast<acc::ReductionCombineOp>(op)) {
4684 processReductionCombineOp(reductionCombineOp);
4685 }
else if (
auto combineRegionOp =
4686 dyn_cast<acc::ReductionCombineRegionOp>(op)) {
4687 processCombineRegionOp(combineRegionOp);
4688 }
else if (acc::ReductionOp accReductionOp = dyn_cast<acc::ReductionOp>(op)) {
4689 mapping.
map(accReductionOp->getResult(0), accReductionOp.getVarPtr());
4692 LLVM_DEBUG(llvm::dbgs() <<
"skipping mapped op: " << *op <<
"\n");
4693 }
else if (isa<acc::YieldOp>(op)) {
4694 for (
auto [operand,
result] :
4698 }
else if (isa<scf::ExecuteRegionOp>(op)) {
4699 processExecuteRegion(cast<scf::ExecuteRegionOp>(op));
4701 isa<acc::OpenACCDialect>(op->
getDialect())) {
4702 processGenericOp(op);
4704 processGenericOpWithRegions(op);
4709class RemoveParWidth :
public OpRewritePattern<acc::ParWidthOp> {
4710 using OpRewritePattern<acc::ParWidthOp>::OpRewritePattern;
4711 LogicalResult matchAndRewrite(acc::ParWidthOp op,
4712 PatternRewriter &rewriter)
const override {
4713 if (Value launchArg = op.getLaunchArg()) {
4724class ACCComputeRegionToGPUPattern
4725 :
public OpRewritePattern<acc::ComputeRegionOp> {
4727 ACCComputeRegionToGPUPattern(MLIRContext *context,
4728 acc::OpenACCSupport &accSupport,
4729 const ACCCGToGPUOptions &
options)
4730 : OpRewritePattern<acc::ComputeRegionOp>(context), accSupport(accSupport),
4733 LogicalResult matchAndRewrite(acc::ComputeRegionOp op,
4734 PatternRewriter &rewriter)
const override {
4735 ACCCGToGPULowering kernelOpRewriter(op, rewriter, accSupport,
options);
4736 return kernelOpRewriter.rewrite();
4740 acc::OpenACCSupport &accSupport;
4741 const ACCCGToGPUOptions &
options;
4744class ACCCGToGPU :
public acc::impl::ACCCGToGPUBase<ACCCGToGPU> {
4746 using acc::impl::ACCCGToGPUBase<ACCCGToGPU>::ACCCGToGPUBase;
4748 void getDependentDialects(DialectRegistry ®istry)
const override {
4753 void runOnOperation()
override {
4754 FunctionOpInterface funcOp = getOperation();
4755 MLIRContext *context = funcOp->getContext();
4757 assert(deviceType != mlir::acc::DeviceType::Host &&
4758 deviceType != mlir::acc::DeviceType::Multicore &&
4759 "ACCCGToGPU only supports GPU device types");
4763 WalkResult
result = funcOp->walk([](scf::ParallelOp loop) {
4764 if (!loop->getParentOfType<acc::ComputeRegionOp>() ||
4767 loop.emitOpError(
"requires an 'acc.par_dims' attribute");
4770 if (
result.wasInterrupted()) {
4771 signalPassFailure();
4776 options.deviceType = deviceType;
4777 options.maxWorkgroupSharedMemory = maxWorkgroupSharedMemory;
4778 options.maxThreadPrivateStack = maxThreadPrivateStack;
4779 options.subgroupSize = subgroupSize;
4782 std::optional<std::reference_wrapper<acc::OpenACCSupport>> cachedAnalysis =
4783 getCachedParentAnalysis<acc::OpenACCSupport>(funcOp->getParentOp());
4784 acc::OpenACCSupport &accSupport = cachedAnalysis
4785 ? cachedAnalysis->get()
4786 : getAnalysis<acc::OpenACCSupport>();
4788 RewritePatternSet patterns(context);
4789 patterns.insert<ACCComputeRegionToGPUPattern>(context, accSupport,
options);
4790 patterns.insert<RemoveParWidth>(context);
4792 target.markUnknownOpDynamicallyLegal([](Operation *) {
return true; });
4793 target.addIllegalOp<acc::ComputeRegionOp, acc::ParWidthOp>();
4794 if (
failed(applyPartialConversion(getOperation(),
target,
4795 std::move(patterns)))) {
4796 signalPassFailure();
if(failed(verifyVectorMemoryOp(getOperation(), memrefType, getVectorType()))) return failure()
static void createForAllDimensions(OpBuilder &builder, Location loc, SmallVectorImpl< Value > &values)
*if copies could not be generated due to yet unimplemented cases *copyInPlacementStart and copyOutPlacementStart in copyPlacementBlock *specify the insertion points where the incoming copies and outgoing copies
Creates a buffer in the faster memory space for the specified memref region (memref has to be non-zer...
static llvm::ManagedStatic< PassManagerOptions > options
static void rewrite(DataFlowSolver &solver, MLIRContext *context, MutableArrayRef< Region > initialRegions)
Rewrite the given regions using the computing analysis.
Block represents an ordered list of Operations.
MutableArrayRef< BlockArgument > BlockArgListType
BlockArgument getArgument(unsigned i)
unsigned getNumArguments()
RetT walk(FnT &&callback)
Walk all nested operations, blocks (including this block) or regions, depending on the type of callba...
Operation * getTerminator()
Get the terminator operation of this block.
bool mightHaveTerminator()
Return "true" if this block might have a terminator.
BlockArgListType getArguments()
Operation * getParentOp()
Returns the closest surrounding operation that contains this block.
IntegerAttr getIndexAttr(int64_t value)
IntegerAttr getI32IntegerAttr(int32_t value)
IntegerAttr getIntegerAttr(Type type, int64_t value)
FloatAttr getFloatAttr(Type type, double value)
IntegerAttr getI64IntegerAttr(int64_t value)
TypedAttr getZeroAttr(Type type)
MLIRContext * getContext() const
auto lookupOrDefault(T from) const
Lookup a mapped value within the map.
auto lookup(T from) const
Lookup a mapped value within the map.
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
bool contains(T from) const
Checks to see if a mapping for 'from' exists.
auto lookupOrNull(T from) const
Lookup a mapped value within the map.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
MLIRContext is the top-level object for a collection of MLIR operations.
RAII guard to reset the insertion point of the builder when destroyed.
This class helps build Operations.
Block::iterator getInsertionPoint() const
Returns the current insertion point of the builder.
Block * createBlock(Region *parent, Region::iterator insertPt={}, TypeRange argTypes={}, ArrayRef< Location > locs={})
Add new block with 'argTypes' arguments and set the insertion point to the end of it.
Operation * clone(Operation &op, IRMapping &mapper)
Creates a deep copy of the specified operation, remapping any operands that use values outside of the...
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
void setInsertionPointToEnd(Block *block)
Sets the insertion point to the end of the specified block.
Block * getInsertionBlock() const
Return the block the current insertion point belongs to.
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Operation * cloneWithoutRegions(Operation &op, IRMapping &mapper)
Creates a deep copy of this operation but keep the operation regions empty.
Operation is the basic unit of execution within MLIR.
Dialect * getDialect()
Return the dialect this operation is associated with, or nullptr if the associated dialect is not loa...
Value getOperand(unsigned idx)
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
unsigned getNumSuccessors()
bool isBeforeInBlock(Operation *other)
Given an operation 'other' that is within the same parent block, return whether the current operation...
result_iterator result_begin()
Block * getBlock()
Returns the operation block that contains this operation.
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
unsigned getNumRegions()
Returns the number of regions held by this operation.
Location getLoc()
The source location the operation was defined or derived from.
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
unsigned getNumOperands()
OpTy getParentOfType()
Return the closest surrounding parent operation that is of type 'OpTy'.
void print(raw_ostream &os, const OpPrintingFlags &flags={})
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
result_iterator result_end()
operand_range getOperands()
Returns an iterator on the underlying Value's.
void setSuccessor(Block *block, unsigned index)
bool isAncestor(Operation *other)
Return true if this operation is an ancestor of the other operation.
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),...
Block * getSuccessor(unsigned index)
result_range getResults()
This class contains a list of basic blocks and a link to the parent operation it is attached to.
BlockListType & getBlocks()
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
Resource * getResource() const
Return the resource that the effect applies to.
This class allows for representing and managing the symbol table used by operations with the 'SymbolT...
Operation * lookup(StringRef name) const
Look up a symbol with the specified name, returning null if no such name exists.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
auto walk(WalkFns &&...walkFns)
Walk this type and all attibutes/types nested within using the provided walk functions.
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
This class provides an abstraction over the different types of ranges over Values.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Type getType() const
Return the type of this value.
user_iterator user_begin() const
user_range getUsers() const
bool hasOneUse() const
Returns true if this value has exactly one use.
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
static WalkResult advance()
static WalkResult interrupt()
ParDimAttrT seqDim(MLIRContext *ctx) const
virtual ParDimAttrT map(MLIRContext *ctx, ParLevel level) const =0
Map an OpenACC parallelism level to target dimension.
ParDimAttrT vectorDim(MLIRContext *ctx) const
ParDimAttrT workerDim(MLIRContext *ctx) const
ParDimAttrT gangDim(MLIRContext *ctx, ParLevel level) const
Convenience methods for specific parallelism levels.
remark::detail::InFlightRemark emitRemark(Operation *op, std::function< std::string()> messageFn, llvm::StringRef category="openacc")
Emit an OpenACC remark with lazy message generation.
std::string getVariableName(Value v, VariableNameConfig config={})
Get the variable name for a given value.
InFlightDiagnostic emitNYI(Location loc, const Twine &message)
Report a case that is not yet supported by the implementation.
std::optional< TypeSizeAndAlignment > getTypeSizeAndAlignment(Type ty, ModuleOp module)
Returns the size and ABI alignment in bytes for ty.
bool tryAllocate(int64_t bytes, int64_t alignment=kDefaultAlignmentBytes)
Reserve bytes, rounding the current offset up to alignment first.
void getDependentDialects(::mlir::DialectRegistry ®istry) const override
Return the dialect that must be loaded in the context before this pass.
Specialization of arith.constant op that returns an integer of index type.
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Specialization of arith.constant op that returns an integer value.
static ConstantIntOp create(OpBuilder &builder, Location location, int64_t value, unsigned width)
static ConcreteType get(MLIRContext *ctx, Args &&...args)
SideEffects::EffectInstance< Effect > EffectInstance
Value getGPUSize(gpu::Processor processor, gpu::LaunchOp launch, const llvm::DenseMap< gpu::Processor, Value > &dimensionOps)
Return the launch dimension for processor from launch, or from dimensionOps when launch is null.
ParLevel getGangParLevel(int64_t gangDimValue)
Convert a gang dimension value (1, 2, or 3) to the corresponding ParLevel.
GPUParallelDimsAttr getParDimsAttr(Operation *op)
Obtain the parallel dimensions carried by op, if any.
MemRefType getPrivateBaseMemRefType(Type baseTy, ModuleOp module)
Returns the ranked MemRef type used to allocate privatized storage.
SmallVector< GPUParallelDimAttr > getReductionCombineParDims(ReductionCombineOp op)
Returns the parallel dimensions that participate in op's combine step.
void insertParDim(llvm::SmallVector< GPUParallelDimAttr > &parDims, GPUParallelDimAttr parDim)
Insert parDim into parDims while preserving dimension ordering.
static constexpr StringLiteral getSpecializedRoutineAttrName()
bool hasParDimsAttr(Operation *op)
Return whether op carries parallel dimensions.
std::optional< arith::AtomicRMWKind > translateACCReductionOperator(ReductionOperator redOp, Type type)
Maps an acc reduction operator to the arith atomic RMW kind for type.
bool isSpecializedAccRoutine(mlir::Operation *op)
Used to check whether this is a specialized accelerator version of acc routine function.
static bool isInsideACCSpecializedRoutine(Operation *op)
Value createIdentityValue(OpBuilder &b, Location loc, Type type, arith::AtomicRMWKind kind, bool useOnlyFiniteValue=true)
Creates the identity (neutral) value for a reduction of type and kind.
FailureOr< bool > isPrivateLocalSharedMemoryCandidate(PrivateLocalOp privateLocal, ComputeRegionOp computeRegion, ModuleOp module, const ACCToGPUMappingPolicy &policy, OpenACCSupport *support=nullptr)
True when privateLocal may be placed in shared memory.
static constexpr StringLiteral getRoutineInfoAttrName()
Value castPointerLikeTypeIfNeeded(OpBuilder &builder, Location loc, Value value, Type resultType)
Cast value to resultType via PointerLikeType::genCast when needed.
int64_t sumExistingSharedMemoryBytes(Region ®ion)
Sum aligned static_upper_bound_bytes for all acc.gpu_shared_memory in region.
Value getGPUThreadId(gpu::Processor processor, gpu::LaunchOp launch, const llvm::DenseMap< gpu::Processor, Value > &indexOps)
Return the thread/block index for processor from launch, or from indexOps when launch is null.
PrivatizeOp getPrivatizeOp(PrivateLocalOp privateLocal, ComputeRegionOp computeRegion)
Resolve the acc.privatize operation associated with a private local.
void registerAllExtensions(DialectRegistry ®istry)
Register the standard OpenACC models for GPU, LLVM and MemRef IR.
bool hasGPUBlockRedundantAttr(Operation *op)
Return whether op is marked with the acc.gpu_block_redundant attribute, i.e.
Value generateReductionOp(OpBuilder &b, Location loc, Value lhs, Value rhs, arith::AtomicRMWKind kind)
Combines two reduction partial values using the operator for kind.
void removeParDim(llvm::SmallVector< GPUParallelDimAttr > &parDims, GPUParallelDimAttr parDim)
Remove parDim from parDims if present.
void setParDimsAttr(Operation *op, GPUParallelDimsAttr attr)
Set parallel dimensions on op.
ActiveParDimsAttr getActiveParDimsAttr(Operation *op)
Obtain the active parallel dimensions carried by op, if any.
std::optional< int64_t > getPrivateLocalSharedMemoryUpperBoundBytes(PrivateLocalOp privateLocal, ComputeRegionOp computeRegion, ModuleOp module, const ACCToGPUMappingPolicy &policy, OpenACCSupport *support=nullptr)
Upper-bound byte size for a shared-memory private_local candidate, or std::nullopt when not eligible ...
static bool isThreadXPrivatize(PrivatizeOp privatize)
ACCParMappingPolicy< mlir::acc::GPUParallelDimAttr > ACCToGPUMappingPolicy
Type alias for the GPU-specific mapping policy.
InFlightDiagnostic & next(InFlightDiagnostic &diag)
Starts a new message part in an in-flight diagnostic.
LoopNest buildLoopNest(OpBuilder &builder, Location loc, ValueRange lbs, ValueRange ubs, ValueRange steps, ValueRange iterArgs, function_ref< ValueVector(OpBuilder &, Location, ValueRange, ValueRange)> bodyBuilder=nullptr)
Creates a perfect nest of "for" loops, i.e.
Value constantOne(OpBuilder &builder, Location loc, Type tp)
Generates a 1-valued constant of the given type.
OwningOpRef< spirv::ModuleOp > combine(ArrayRef< spirv::ModuleOp > inputModules, OpBuilder &combinedModuleBuilder, SymbolRenameListener symRenameListener)
Combines a list of SPIR-V inputModules into one.
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
detail::constant_int_value_binder m_ConstantInt(IntegerAttr::ValueType *bind_value)
Matches a constant holding a scalar/vector/tensor integer (splat) and writes the integer value to bin...
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
bool isMemoryEffectFree(Operation *op)
Returns true if the given operation is free of memory effects.
Value getValueOrCreateCastToIndexLike(OpBuilder &b, Location loc, Type targetType, Value value)
Create a cast from an index-like value (index or integer) to another index-like value.
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...